Coding Step 1 - Hello World and Makefiles

Stephen FriederichsFebruary 10, 20156 comments

Articles in this series:

Step 0 discussed how to install GCC and the make utility with the expectation of writing and compiling your first C program. In this article, I discuss how to use those tools we installed last time. Specifically, how to use GCC to compile a C program and how to write a makefile to automate the process.

While there are many other tutorials out there covering roughly similar ground, I hope that you (a novice with some background in programming) find this tutorial to be more useful and approachable than other tutorials. In addition, my goal with this whole series is to teach the basics of a Unix-like development environment. I've found that the Unix approach to developing code produces efficient programmers with more autonomy and a broader understanding of all the various pieces that contribute to turning code into useful programs. If you've developed code before but never been able to break away from the chains of an IDE then this is the tutorial for you. Additionally, the development tools discussed here have been adapted to a wide variety of embedded systems. You can use some variant of GCC to compile code for a wide variety of processors: AVRs, MSP430s, ARMs of all sizes, PowerPC - you name it and there's probably a GCC compiler for it. Learning to use Unix-like development tools has direct applicability to developing code for embedded systems and will make you a fundamentally better programmer. 

This article is available in PDF format for easy printing

For learning how to compile C files, it's best to start simple. Hello World is the simplest C program imaginable: 

#include <stdio.h>
main ( )
{
    printf("Hello World!");
}
    

Because it’s so simple, it’s easy to verify that it’s working correctly: if you see ‘Hello World!’ printed when you run it, you can be sure you did everything correctly. The steps to compile it are verbose at first, but they'll become second nature as you work more with code.

Compiling a C File

(Note: these steps were written for Windows 7, but the same result can be achieved on nearly all versions of Windows with only minor changes to the steps).

Save the the above source code into a file called helloWorld.c in the root of the C: drive using your favorite text editor.

 Open the Start menu and enter ‘cmd’ into the run dialog and hit enter:

Opening the command prompt

Note: If you’re running on Windows Vista or above, you’ll have to run cmd.exe as administrator by right-clicking the cmd.exe entry:

 Running command prompt as Administrator

Afterwards, agree to the UAC dialog that pops up.

 This brings up a command prompt:

Command prompt window

Type the following commands

cd C:\ ←-This will change the current directory to the root of the C: drive

dir *.c ←- This will show a listing of all of the files in the root of the C: drive with an extension of .c

 

The window should look something like this:

Proper directory listing

If you see something like this:

 File not found

It means you didn’t save your text file in the right place.

 To compile this file, type the following command: 

gcc helloWorld.c
    

 This command produces a new file in the root of the C: drive: a.exe

This is the default name given to a program compiled with GCC. To run the program, type the following command:

a.exe
    

 You should see something like this:

 Hello World!

That ‘Hello World!’ means it worked - congratulations! 

Now, it’s kinda odd for a Hello World program to be called ‘a.exe’. Luckily, we can change this by telling GCC what the executable should be called with this command: 

gcc helloWorld.c -o helloWorld.exe
    

 You’ll run this one by typing:

<strong><strong> </strong></strong>helloWorld.exe
    

 And your command window should look like this if everything went correctly:

Properly named hello world

Success!

Code Organization

Now programming is messy business. At this point you’ve got three files sitting in the root of your C: drive that weren’t there before. If the simplest C program imaginable leaves three files sitting around imagine the trash a more complex project will leave on your hard drive. Organization is key when coding, so I recommend picking a special directory to store all of your code projects, then giving each project its own directory underneath. I do this in my Dropbox so I can have quick access to all of my code across multiple computers. Each project directory gets its own organization scheme based on the one present in my article on project directory structure.

 For this project I’ve created a ‘helloWorld’ project directory in my Dropbox and given it two subdirectories:

  • src - All (one) of the source files are stored here.
  • bin - The executable gets put here.

If you save your helloWorld.c file into the ‘src’ subdirectory for your helloWorld project, then open up another command prompt and enter a similar set of commands, you’ll get the same result:

Compiling in a proper project directory

Makefiles

You might have noticed that using the command line is a lot of typing - even for a simple C program. You might be wondering - are larger C programs simply impossible to work with? 

No! Thanks to makefiles.

Basically, makefiles are a method of specifying 'recipes' for compiling source files into executables, object files, libraries and basically anything else you can think of. Makefiles automate the mundane and boring parts of compiling source files. They're actually a lot more versatile and complex than this, but for a programmer just starting off the primary thing a makefile will do is save you repetitive typing.

Let’s make a makefile to compile this source into the exe - start by saving this text as a file called ‘makefile’ in the helloWorld project directory:

all:
    gcc src/helloWorld.c -o bin/helloWorld.exe
    

 Gotchas:

  • ‘makefile’ has no extension - some text editors will try to put a .txt extension on it, so watch yourselves.
  • The indent MUST be a tab character - not three or four spaces as some text editors use.

If you don’t use a real tab character you’ll get an error when you try to use the file (as I’ll show you below).

Once you’ve saved the file, type this in the command prompt to run make and then the helloWorld executable (assuming you're still in the directory you earlier created for the helloWorld project):

mingw32-make
.\bin\helloWorld.exe
    

 And you’ll get this result:

Using a makefile

As I mentioned earlier, you’ll get an error like this if you didn’t use a real tab character in the file:

Didn't use a tab

Some explanation is in order here. Typing ‘mingw32-make’ executes the make utility. The make utility will automatically look for a file called ‘makefile’ or ‘makefile.mak’ in the current directory. If it finds this file it will try to build the target called ‘all’.  What’s a target? Let’s go back to the text of the makefile:

all:
    gcc src/helloWorld.c -o bin/helloWorld.exe
    

The first line is the name of the target: ‘all’ with a colon after it. The next line is tabbed to the right and contains the command needed to build the ‘all’ target. This is the same command that you used to type in manually to build the executable. This is how make works: commands build targets. A target can have more than one command - as long as they’re all tabbed it will execute them in order from top to bottom whenever it rebuilds the target. By default, make will always attempt to build the ‘all’ target if you don’t specify a different one. Makefiles can contain multiple targets which can be chosen from on the command line:

<strong><strong> mingw32-</strong></strong>make <target>
    

 Thus, typing in either of the following:

mingw32-make
mingw32-make all
    

will both run the same command - the first time because the ‘all’ target is assumed and the second time because the ‘all’ target is explicitly specified on the command line. Any target name can replace the ‘all’ on the command line.

Typically there are multiple targets in a makefile that you can specify via the command line. These targets can either be identifiers (the ‘all’ target is an identifier) or they can be file names. We can add a file target to the makefile by adding these lines at the end of the makefile:

bin\helloWorld.exe:
    gcc src/helloWorld.c -o bin/helloWorld.exe
    

 Add these lines to the end of the makefile, save it and then type this at the command prompt:

<strong><strong> </strong></strong>mingw32-make bin\helloWorld.exe
    

 And then you should get this result:

helloWorld is up to date

That’s pretty confusing for someone who’s never used make before. Luckily I’ve been around the block a few times. Here’s what’s happening: 

Make isn’t just a glorified batch file - it has intelligence. One of the intelligent things that it does is track dependencies. For example, helloWorld.exe is generated from helloWorld.c - helloWorld.exe is dependent on helloWorld.c. Make will never go to the trouble of rebuilding an output (in this case, helloWorld.exe) if none of the dependencies of helloWorld.exe have changed. If none of the dependencies have changed, the output is up to date (as the output of make told us) and recompiling the source file would produce the same output - so why do it? It might seem like this is a case of premature optimization and for this project, yes it is. But for a large project with hundreds or thousands of source files, only rebuilding the outputs that need to be rebuilt can easily save hours!

The problem with our makefile is that we haven’t specified any dependencies for our target. Because of this, make doesn’t know what files are needed to rebuild helloWorld.exe, so it assumes that if the file exists then there must be no other work to do.  The ‘all’ target doesn’t have this problem because it’s not a file - it’s a ‘phony’ target. Whenever you rebuild the ‘all’ target it always performs the command(s).  To ensure that make will rebuild the helloWorld.exe file, change the target definition to this:

bin\helloWorld.exe: src\helloWorld.c
    gcc src/helloWorld.c -o bin/helloWorld.exe
    

 We’ve added ‘src\helloWorld.c’ to the right of the colon after the target name. By doing this, we’ve specified that the helloWorld.c file is a dependency of helloWorld.exe: the one is needed to make the other. You can have as many dependencies to the right of the colon as you want - just separate them by spaces. Dependencies can be other targets (such as ‘all’) or they can be files as is shown above. By adding this dependency, make will know when it has to rebuild helloWorld.exe and when it can avoid doing so to save time.

 So, with the dependency added, re-run the previous command on the command line:

mingw32-make bin\helloWorld.exe
    

 And now, you get this result:

Still didn't build

Which is exactly the same as before. What gives?

The problem here is that make determines whether it has to rebuild a target from its dependencies based on timestamps. If helloWorld.exe has later timestamp than helloWorld.c, that means that helloWorld.exe must have been built from the current helloWorld.c. If there haven’t been any changes to the dependencies, there’s no reason to update the target - hence, it’s up to date. However, if we change the timestamp on helloWorld.c then make will know to rebuild helloWorld.exe.  Edit helloWorld.c so that it looks like this:

#include <stdio.h>
main ( )
{
   printf("I changed it okay?");
}
    

 Your program has suffered a change in attitude, but more importantly it’s suffered from a change in timestamp: the timestamp on helloWorld.c is now later than that of helloWorld.exe. Watch what make does now when we try to build the target (using the same command as shown before):

 Now it rebuilds!

And now it works! But now you have a problem: if you try to build bin\helloWorld.exe again, it won’t do anything because it’s up to date. Sometimes you want to force the target to be rebuilt. There’s two good ways to do this:

Specify a new target with the name ‘FORCE’ with no command after it. Then, make ‘FORCE’ a dependency of the file you always want to be rebuilt. The makefile would look like this thereafter: 

all:
    gcc src/helloWorld.c -o bin/helloWorld.exe
bin\helloWorld.exe: src\helloWorld.c FORCE
    gcc src/helloWorld.c -o bin/helloWorld.exe
FORCE:
    

 Now it will always rebuild helloWorld.exe every time you invoke it. This is an effective but not flexible solution. There’s an easy way to allow you to force a rebuild whenever you want without it happening all the time: the clean target. 

The clean target is a common target in many makefiles. The point of the target is that it deletes all of the output files at once which forces everything to be rebuilt afterwards. Using the clean target approach, the makefile would look like this:

all:
    gcc src/helloWorld.c -o bin/helloWorld.exe
bin\helloWorld.exe: src\helloWorld.c
    gcc src/helloWorld.c -o bin/helloWorld.exe
FORCE:
clean:
    del bin\helloWorld.exe
    

 To force a rebuild, type the following commands on the command line: 

  1. mingw32-make clean
  2. mingw32-make bin\helloWorld.exe

 And you’ll see this result:

 Cleaning up

Works like a charm! Now you have the capability to force a rebuild any time. You’ll notice that I left the FORCE target in the makefile - it’s a common target that will most likely be useful to you in the future. Also, unused targets don’t really have any downside as long as they don’t make the makefile harder to read - certainly that isn’t the case yet.

 That’s a good amount of basic knowledge about compiling C files and writing makefiles: you can write a C file, compile it and write a makefile to handle all of those tasks for you. I’ve literally only scratched the surface of both of those topics - there’s plenty of fodder left for future articles. I don’t want to wear you out on a single topic for too long though - that’s why the next article will focus on basic source control with Git.

 See you next time!


Previous post by Stephen Friederichs:
   Coding - Step 0: Setting Up a Development Environment
Next post by Stephen Friederichs:
   Coding Step 2 - Source Control

Comments:

[ - ]
Comment by AzdinMay 1, 2015
Hi Stephen!
your tutorial is so clear and very helpful. Many thanks!
I downloaded and installed the GCC etc...; I went on with the step1 to compile the helloWorld.c program a popup window tells me that the "libiconv procedure entry point in missing in the libiconv-2.dll.
Can you please tell me what it means .Could the libiconv-2.dll itself be corrupted?
Thanks for your work anyway.
[ - ]
Comment by AzdinMay 2, 2015
Hello again Stephen!
Yesterday I told you about a problem in libiconv-2.dll that would have prevented my helloWorld.c from compiling.
Well,Good news! With the help of "DLL-Files Fixer" that file got fixed and I could compile the program which became helloWorld.exe.
Now I'll try on with the Makefiles Chapter.
THANKS again Stephen!
[ - ]
Comment by wheeler8September 14, 2015
Hi Stephen !

I found (at least I guess so) a problem in your makefile.When the target is clean, I had to change the command to rm bin/helloWorld.exe, from del bin/helloWorld.exe to work.
[ - ]
Comment by SFriederichsSeptember 14, 2015
Yes I've had similar problems in the past. Your solution is one remedy that works - another might be to ignore errors on the 'del' command. Good catch!
[ - ]
Comment by Yy92October 16, 2015
So, here is one thing I must say.

First, let me get this out of the way. I use Windows system daily. The system from which I am replying from now is a Windows 8 professional system.

With the above said, I feel it is a waste of time developing software on a Windows PC. Instead, I feel is it much more productive spending time developing software on Linux ( or UNIX ) using GNU tools such as gcc, make, autotools, etc. Why ? Well because much of the embedded programming going on now days is done using Linux. Embedded Linux to be precise. Not only that, many of the very useful tools are available on Linux systems for free.

Yes, yes, there are many tools which can be used on Windows as well. Then some of these are very easy to use, and perhaps are even free. But what do these tools really teach us ? How to do things specific for Microsoft platforms only ? Or do they really teach us about the lower level intricacies of that platform ? I would argue that they do not. Using various libc or POSIX api's on the other hand can and often do work cross platform, and offer a much broader insight as to how things really work.

Or do we only really care about such things as WIN32_LEAN_AND_MEAN ?
[ - ]
Comment by SFriederichsOctober 23, 2015
I don't disagree with you that ideally we'd all be doing this on a POSIX-based system, but realistically the majority of people are using Windows and there are still embedded development tools that are Windows-only. It's not going away and I think a lot of people aren't comfortable making the Linux switch or even dual-booting. By helping people use these Unix/Linux-like tools available for Windows (and especially by instructing them on using the command-line) I think it will certainly increase people's familiarity with the Unix/Linux world and break down some barriers. Baby steps...

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Registering will allow you to participate to the forums on ALL the related sites and give you access to all pdf downloads.

Sign up

I agree with the terms of use and privacy policy.

Yes, I want to subscribe to your world famous newsletter and see for myself how great it is. I also understand that I can unsubscribe VERY easily!
or Sign in