Make or Break your C

Tim Mcmacken Jr.
5 min readFeb 6, 2020
You’re very first C program!

Well, here we are, you’ve finally put aside the FUD and done it, you came, you coded, you conquered C. Congratulations on this momentous occasion! Now let’s just run this baby and bask in our greatness….

Ruh Roh…

WTH?!!? But our code is perfect. We followed all the tutorials, triple checked each step, and yet Bash is giving us sass when we try to run our newly minted C program. Well, here of course, is our teachable moment and why I am reaching across the internet to share a bit of learning with you. To kick things off a bit of pedantic but useful information. There are two types of coding languages…Compiled and Interpreted.

Interpreted coding languages:

Some popular interpreted languages

An interpreted coding language is one where the code is translated from human-readable to machine code, line by line, as it is read by the system that is executing the code. This is a little like picking up a book in a language that you don’t speak and sitting down with a friend that knows that language, then going through the book line by line with your friend translating for you. As you can imagine, this could be a seriously arduous task if you were perhaps taking on Dostoyevsky’s Crime and Punishment in the original Russian. You are going to owe your Russian speaking friend big time. Similarly, in software interpreted languages used to have a pretty severe speed disadvantage over compiled languages, but thanks to the just-in-time compilation, they are catching up.

Compiled Languages:

Some popular compiled languages

Compiled languages, on the other hand, need to be transformed into machine code before you attempt to execute them. This is done using a compiler like GCC. If you are jonesing for some dark Russian tragedy but ты не говоришь на языке, than it’s best to pick up an English version of the book. In much the same way, your computer doesn’t know C or Rust, it just knows binary, so if you want to ask it to perform tasks, we need to get on the same page as our CPU.

So here we have the solution to our C conundrum. We need to compile before we execute, but how do we go about doing so? Well, with a compiler of course! Luckily our Linux BASH terminal comes with GCC (GNU Compiler Collection) ready to go. If you are sick of my schtick, go back to your terminal and run: gcc -hello.c -o hello and have a great rest of your day.

Still here? Well, if you’re curious how the compiler actual works, let’s plow forward! There are four significant steps to taking a file from .c to executable: Preprocessing, Compiling, Assembly, and Linking.

How it all works!

Let’s take it from the top. You type gcc -E hello.c -o hello and your output in hello is going to be a preprocessed version of your hello.c file. Preprocessing includes: Removing any comments (the machine doesn’t care if you remember what your code means, though you really should, so always document your code), includes header code, and replaces macro names with their values. It looks a little something like this:

Our preprocessed hello.c which is now hello

Next up is the compiler. The compiler translates your code from what you may still recognize into a language that your assembler will recognize. Now assembly code is not the same as machine code, at this point your, hardware still would have no idea what you want it to do, but your assembler is on the job and ready to translate. If we take a look at assembly code using gcc -c hello.c it will look like this:

This is code nightmare fuel for the uninitiated.

Next up comes the assembler, which will take the code from assembly language into machine code, which is ready for translation into the binary that your computer can understand and work with. If you’d like to see this in action for yourself, you can always use gcc -S hello.c and your output will be hello.s.

After the Assembler translates from assembly to machine code, we’re here.

Finally, we have linking and output. The linker brings in any external files or libraries you may have used and adds them to the file so that it can access the bits it needs to be executed. Libraries like stdio.h or math.h as well as if your project is big enough to have other files linked are added in binary before the code if finally processed and release as an executable.

You can view the file with xxd -b hello

And now, we are finally ready to say hello world!

Well, hello there.

A word on make

While we have been sweating away over a hot compiler with GCC, there is a short cut to doing everything you want to do when compiling, and that’s make. Make is the one-stop-shop for taking your raw C and banging out ready to rock executable and is excellent if you are willing to relinquish some control for expedience. Now right off the bat, we should note that Make is a tool while GCC is a compiler. Make is a set of instructions that accesses the compiler and tells it just what you want it to do. From your viewpoint, all the steps we just went through with GCC boils down to: make hello. That simple two-word command will check your file for error in the c code, run it all the way through preprocessing, compiling, assembly, and linking, as well as output an executable file named hello. All this, and you don’t even need to give it the exact file name for your C code. Make, like most dev tools, is a great time-saver, but like most tools using it means you have less control and less overall knowledge of what’s going on under the hood. This is why it is excellent to learn GCC first, so you know what you are doing.

Well, that’s all from me for now. If you have questions or if I missed (or messed up anything here), drop me a line @CMDMine on twitter. Happy Coding!

--

--