Creating A Basic Make File for Compiling C Code

Posted on July 6 2014 02:30 PM by jatten in Linux, Education, CodeProject, Learning   ||   Comments (4)

binary-blanket-240I recently began taking a Harvard computer science course. As pretentious as that sounds, it's not as bad as it seems. I am taking Harvard CS50 on-line, for free, in an attempt to push my knowledge and expand my understanding of this thing I love so much, programming.

The course uses Linux and C as two of the primary learning tools/environments. While I am semi-capable using Linux, and the syntax of C is vary familiar, the mechanics of C compilation, linking, and makefiles are all new.

Image by quimby  |  Some Rights Reserved

If you are an experienced C developer, or if you have worked extensively with Make before, there is likely nothing new for you here. However, if you wanted to let me know if and when I am passing bad information, please do! This article will (hopefully) be helpful to those who are just getting started compiling C programs, and/or using the GNU Make utility.

In the post, we discuss some things that are specific to the context of the course exercises. However, the concepts discussed, and the examples introduced are general enough that the post should be useful beyond the course specifics.

The course makes available an "appliance" (basically, a pre-configured VM which includes all the required software and files), which I believe can be downloaded by anyone taking even the free on-line course. However, since I already know how to set up/configure a Linux box (but can always use extra practice), I figured my learning would be augmented by doing things the hard way, and doing everything the course requires manually.

For better or for worse, this has forced me to learn about Make and makefiles (this is for the better, I just opened the sentence that way to sound good).

Make File Examples on Github

In this post we will put together a handful of example Make files. You can also find them as source at my Github repo:

Compiling and Linking - The Simple

This is by no means a deeply considered resource on compiling and linking source code. However, in order to understand how make works, we need to understand compiling and linking at some level.

Let's consider the canonical "Hello World!" program, written in the C programming language. Say we have the following source file, suitably named hello.c:

Basic Hello World Implementation in C:
#include <stdio.h>
  
int main(void)
{
    printf("Hello, World!\n");
}

 

In the above, the first line instructs the compiler to include the C Standard IO library, by making reference to the stdio.h header file. This is followed by our application code, which impressively prints the string "Hello World!" to the terminal window.

In order to run the above, we need to compile first. Since the Harvard course is using the Clang compiler, the most basic compilation command we can enter from the terminal might be:

Basic Compile Command for Hello World:
$ clang hello.c -o hello

 

When we enter the above command in our terminal window, we are essentially telling the Clang compiler to compile the source file hello.c, and the -o flag tells it to name the output binary file hello.

This works well enough for a simple task like compiling Hello World. Files from the C Standard Library are linked automatically, and the only compiler flag we are using is -o to name the output file (if we didn't do this, the output file would be named a.out, the default output file name).

Compiling and Linking - A Little More Complex

When I say "Complex" in the header above, it's all relative. We will expand on our simple Hello World example by adding an external library, and using some additional important compiler flags.

The Harvard CS50 course staff created a cs50 library for use by students in the course. The library includes some functions to ease folks into working with C. Among other things, the staff have added a number of functions designed to retreive terminal input from the user. For example, the cs50 library defines a GetString() function which will accept user input as text from the terminal window.

In addition to the GetString() function, the cs50 library also defines a string data type (which is NOT a native C data type!).

We can add the cs50 library to our machine by following the instructions from the cs50 site. During the process, the library will be compiled, and the output placed in the /usr/local/lib/ directory, and the all-important header files will be added to our usr/local/include/ directory.

NOTE: You don't need to focus on using this course-specific library specifically here - this is simply an example of adding an external include to the compilation process.

Once added, the various functions and types defined therein will be available to us.

We might modify our simple Hello World! example as follows by referencing the cs50 library, and making use of the GetString() function and the new string type:

Modified Hello World Example:
// Add include for cs50 library:
#include <cs50.h>
#include <stdio.h>
  
int main(void)
{
    printf("What is your name?");
    // Get text input from user:
    string name = GetString();
      
    // Use user input in output striing:
    printf("Hello, %s\n", name);
}

 

Now, if we try to use the same terminal command to compile this version, we will see some issues:

Run Original Compile Command:
$ clang hello.c -o hello

 

Terminal Output from Command:
/tmp/hello-E2TvwD.o: In function `main':
hello.c:(.text+0x22): undefined reference to `GetString'
clang: error: linker command failed with exit code 1 
    (use -v to see invocation)

 

From the terminal output, we can see that the compiler cannot find the GetString() method, and that there was an issue with the linker.

Turns out, we can add some additional arguments to our clang command to tell Clang what files to link:

$ clang hello.c -o hello -lcs50

 

By adding the -l flag followed by the name of the library we need to include, we have told Clang to link to the cs50 library.

Handling Errors and Warnings

Of course, the examples of using the Clang compiler and arguments above still represent a very basic case. Generally, we might want to direct the compiler to add compiler warnings, and/or to include debugging information in the output files. a simple way to do this from the terminal, using our example above, would be as follows:

Adding Additional Compiler Flags to clang Terminal Command:
clang hello.c -g -Wall -o hello -lcs50

 

Here, we have user the -g flag, which tells the compiler to include debugging information in the output files, and the -Wall flag. -Wall turns on most of the various compiler warnings in Clang (warnings do not prevent compilation and output, but warn of potential issues).

A quick skimming of the Clang docs will show that there is potential for a great many compiler flags and other arguments.

As we can see, though, our terminal input to compile even the still-simple hello application is becoming cumbersome. Now imagine a much larger application, with multiple source files, referencing multiple external libraries.

What is Make?

Since source code can be contained in multiple files, and also make reference to additional files and libraries, we need a way to tell the compiler which files to compile, which order to compile them, and how a link to external files and libraries upon which our source code depends. With the additional of various compiler options and such required to get our application running, combined with the frequency with which we are likely to use the compile/run cycle during development, it is easy to see how entering the compile commands manually could rapidly become cumbersome.

Enter the Make utility.

GNU Make was originally created by Richard M. Stallman ("RMS") and Roland McGrath. From the GNU Manual:

"The make utility automatically determines which pieces of a large program need to be recompiled, and issues commands to recompile them."

When we write source code in C, C++, or other compiled languages, creating the source is only the first step. The human-readable source code must be compiled into binary files in order that the machine can run the application.

Essentially, the Make utility utilizes structured information contained in a makefile in order to properly compile and link a program. A Make file is named either Makefile or makefile, and is placed in the source directory for your project.

An Example Makefile for Hello World

Our final example using the clang command in the terminal contained a number of compiler flags, and referenced one external library. The command was still doable manually, but using make, we can make like much easier.

In a simple form, a make file can be set up to essentially execute the terminal command from above. The basic structure looks like this:

Basic Makefile Structure:
# Compile an executable named yourProgram from yourProgram.c
all: yourProgram.c
<TAB>gcc -g -Wall -o yourProgram yourProgram.c

 

In a makefile, lines preceded with a hash symbol are comments, and will be ignored by the utility. In the structure above, it is critical that the <TAB> on the third line is actually a tab character. Using Make, all actual commands must be preceded by a tab.

For example, we might create a Makefile for our hello program like this:

Makefile for the Hello Program:
# compile the hello program with compiler warnings, 
# debug info, and include the cs50 library
all: hello.c
	clang -g -Wall -o hello hello.c -lcs50

 

This Make file, named (suitably) makefile and saved in the directory where our hello.c source file lives, will perform precisely the same as the final terminal command we examined. In order to compile our hello.c program using the makefile above, we need only type the following into our terminal:

Compiling Hello Using Make:
$ make

 

Of course, we need to be in the directory in which the make file and the hello.c source file are located.

A More General Template for Make Files

Of course, compiling our Hello World application still represents a pretty simplistic view of the compilation process. We might want to avail ourselves of the Make utilities strengths, and cook up a more general template we can use to create make files.

The Make utility allows us to structure a makefile in such a way as to separate the compilation targets (the source to be compiled) from the commands, and the compiler flags/arguments (called rules in a make file). We can even use what amount to variables to hold these values. 

For example, we might refine our current makefile as follows:

General Purpose Makefile Template:
# the compiler to use
CC = clang
  
# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS  = -g -Wall
  
#files to link:
LFLAGS = -lcs50
  
# the name to use for both the target source file, and the output file:
TARGET = hello
  
all: $(TARGET)
  
$(TARGET): $(TARGET).c
	$(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c $(LFLAGS)

 

As we can see in the above, we can make assignments to each of the capitalized variables, which are then used in forming the command (notice that once again, the actual command is preceded by a tab in the highlighted line). While this Make File is still set up for our Hello World application, we could easily change the assignment to the TARGET variable, as well as add or remove compiler flags and/or linked files for a different application.

Again, we can tell make to compile our hello application by simply typing:

Compile Hello.c Using the modified Makefile:
$ make

 

A Note on Tabs Vs. Spaces in Your Editor

If you, like me, follow the One True Coding Convention which states:

"Thou shalt use spaces, not tabs, for indentation"

Then you will have a problem with creating your make file. If you have your editor set to convert tabs to spaces, Make will not recognize the all-important Tab character in front of the command, because, well, it's not there.

Fortunately, there is a work-around. If you do not have tabs in your source file, you can instead separate the compile target from the command using a semi-colon. With this fix in place, our Make file might look like this:

Makefile with no Tab Characters:
# Compile an executable named yourProgram from yourProgram.c
all: yourProgram.c
<TAB>gcc -g -Wall -o yourProgram yourProgram.c
# compile the hello program with spaces instead of Tabs
  
# the compiler to use
CC = clang
  
# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS  = -g -Wall
  
#files to link:
LFLAGS = -lcs50
  
# require that an argument be provided at the command line for the target name:
TARGET = hello
  
all: $(TARGET)
$(TARGET): $(TARGET).c ; $(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c $(LFLAGS)

 

In the above, we have inserted a semi-colon between the definition of dependencies definition of the target and the command statement structure (see highlighted line).

Passing the Compilation Target Name to Make as a Command Line Argument

Most of the time, when developing an application you will most likely need a application-specific Makefile for the application. At least, any substantive application which includes more than one source file, and/or external references.

However, for simple futzing about, or in my case, tossing together a variety of one-off example tidbits which comprise the bulk of the problem sets for the Harvard cs50 course, it may be handy to be able to pass the name of the compile target in as a command line argument. The bulk of the Harvard examples include the cs50 library created by the program staff (at least, in the earlier exercises), but otherwise would mostly require the same sets of arguments.

For example, say we had another code file, goodby.c in the same directory.

We could simply pass the target name like so:

Passing the Compilation Target Name as a Command Line Argument:
make TARGET=goodbye.c

 

As we can see, we assign the target name to the TARGET variable when we invoke Make. In this case, if we fail to pass a target name, by simply typing make as we have done previously, Make will compile the program hard-coded into the Makefile - in this case, hello. Despite our intention, the wrong file will be compiled.

We can make one more modification to our Makefile if we want to require that a target be specified as a command line argument:

Require a Command Line Argument for the Compile Target Name:
# compile the hello program with spaces instead of Tabs
  
# the compiler to use
CC = clang
  
# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS  = -g -Wall
  
#files to link:
LFLAGS = -lcs50
  
# require that an argument be provided at the command line for the target name:
TARGET = $(target)
  
all: $(TARGET)
$(TARGET): $(TARGET).c ; $(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c $(LFLAGS)

 

With that change, we can now run make on a simple, single-file program like so:

Invoke Make with Required Target Name:
$ make target=hello

 

Of course, now things will go a little haywire if we forget to include the target name, or if we forget to explicitly make the assignment when invoking Make from the command line.

Only the Beginning

This is one of those posts that is mainly for my own reference. As I become more fluent with C, compilation, and Make, I expect my usage may change. For now, however, the above represents what I have figured out while trying to work with the examples in the on-line Harvard course.

If you see me doing anything idiotic in the above, or have suggestions, I am all ears! Please do comment below, or reach out at the email described in my "About the Author" blurb at the top of this page.

Additional Resources and Items of Interest

 

Posted on July 6 2014 02:30 PM by jatten     

Comments (4)

Git: Setting Sublime Text as the Default Editor for Git (Linux Mint/Ubuntu)

Posted on September 10 2013 08:58 PM by jatten in CodeProject, Git, Linux   ||   Comments (0)

Setting up Sublime Text 2 (or the new Beta Release of version 3)  as the default editor used by Git is not overly challenging, but not necessarily obvious either. Really, we're still simply setting up the .gitconfig file with a path to sublime text. However, given that there is not, presently, a standard installation directory for Sublime Text, we first need to know where to point our .gitconfig file. Also, there are some non-obvious flags which need to be set as part of the configuration, or Sublime Text will not work properly as the Git editor.

Installing Sublime Text 2, or installing the beta release of Sublime Text 3 is not too challenging either, but again, to this point neither Linux Mint nor Ubuntu offer Sublime as part of the Synaptic Package Manager for either distro. For additional help on this, refer to my previous posts:

If you used one of the methods above, you should now have a bash script or alias named subl in your /usr/bin/ directory which allows you to refer to Sublime Text as subl from the terminal. We will point the .gitconfig file to this.

Tell Git Where Sublime Text Lives

To set the default editor in our .gitconfig file from the terminal, we can use the following command:

$ git config --global core.editor "subl -n -w"

 

Notice the –n and –w flags at the end? These arguments are passed to Sublime Text, and essentially tell it to run without loading any previously open windows (the –n flag), and to wait until the user exits to pass execution back to the calling process (the –w flag, for "wait"). In this case, the calling process is Git.

Trouble Shooting

If the above doesn't work for you, then you may not have the alias or Bash script in your /usr/bin/ directory, or you may have installed Sublime Text to a different directory altogether. In this case, the command is the same, but you need to change it to include either the script name you chase, or the the full-path to Sublime Text itself:

$ git config --global code.editor "<Full Path or Script> -n -w"

 

Of course, you can always open the .gitconfig file in Sublime Text itself (or any other editor on your system), and edit the file directly, but if you are learning Linux, where's the fun in THAT? Smile

If you have other issues, feel free to mention them in the comments or email me at the address in the "Author" section of this blog.

That's it. The biggest issue most people run into with this is not knowing about the –n and –w flags, which must be included in the quotes. Without these flags, Sublime Text will open in response to a prompt from Git, but will immediately return execution to Git. Therefore, your merge edits, interactive rebase, or other such activity will fail, because Git will never see your changes.

Other Articles of Interest to Git or Linux Newcomers

 

Posted on September 10 2013 08:58 PM by jatten     

Comments (0)

Install Sublime Text 3 (beta) on Linux Mint or Ubuntu

Posted on August 25 2013 10:10 AM by jatten in Learning Linux, Linux, CodeProject   ||   Comments (1)

LKeyboard240Surprisingly, one of my more popular articles last year was a short, very basic walk-thru detailing how to install Sublime Text 2 on Ubuntu-based machines (this includes Linux Mint).

Now that beta version of Sublime Text 3 has become more and more stable, I am going to post an updated walk-thru for the new version, since the commands differ in a few places, and I have learned a few things in the intervening months (slowly – baby steps here . . .).

Image by Nick Ares / Some Rights Reserved

As noted in the previous article regarding Sublime Text 2, Sublime Text 3 is not currently part of the Synaptic Package Management system on Linux Mint (or Ubuntu). Therefore, there is no magical apt-get install command as you might use to install other software on your Linux system, so we have to do a little more work.

Installing Sublime Text on Linux Mint/Ubuntu from Tarball

You can manually download the latest build (Build 3047 as of this writing) of Sublime Text 3 for either 32 bit or 64 bit architectures from the Sublime Text 3 page, unpack, and locate in the directory of your choice. This can be done manually, or from the terminal as described below.

If you are less-than-familiar with the Bash command line, be sure to visit my previous posts. While these were originally part of a series on using Git for Windows developers, the basic Bash commands required for this install are clearly explained:

This method is described on the Sublime Text Site/Support/Linux/Installation page. Simply open a terminal in the directory you use for applications, and enter the following command (use the appropriate version fro your machine):

Note: As of this writing, Sublime Text 3 build 3059 is the most recent beta release. If the release is updated, the URL's in the links below will change, and you will need to copy the updated URL from the Sublime Text site.

Download the Linux 32-Bit Version of Sublime Text 3:
$ wget http://c758482.r82.cf2.rackcdn.com/sublime_text_3_build_3059_x32.tar.bz2

 

Download the Linux 64-Bit Version of Sublime Text 3:
wget http://c758482.r82.cf2.rackcdn.com/sublime_text_3_build_3059_x64.tar.bz2

 

Extract the "Sublime Text 2.0.1.tar.bz2" file (this will be sublime_text_3_build_3059_x64.tar.bz2 for the 64 bit version):

Extract the Sublime Text .tar file:
tar vxjf "sublime_text_3_build_3059_x32.tar.bz2"

 

It is common to locate 3rd-party applications in the /opt/ directory, so move the extracted files there:

Move Extracted Sublime Text 3 Files to Opt Directory:
$ sudo mv Sublime_text_3 /opt/

 

Creating a Bash Script to use Sublime Text 3 from the Terminal

Now that we have located the files in an appropriate directory, let's make a Bash script which will allow us to execute Sublime Text 3 with a simple command. To do this, we are going to stay in our terminal, and perform a very simple edit using vi.

First, change to the /opt/sublime_text_3/ directory:

Move into the Sublime Text 3 directory in /opt/
$ cd /opt/sublime_text_3

 

Next, open vi into a new file named subl3 (this is a convenient shortcut to use from the terminal):

Open Vi Into New File suble3:
$ vi subl3

 

If you are new to editing text from the terminal, don't panic, this is not too bad, and also is something you should learn how to do, if only for occasions like this). There are a few key things to know about vi:

  • vi opens in Command Mode – you cant type any text yet. To enter Insert Mode, type the asterisk character followed by capital I ( *I ). This will allow text entry on the current line.
  • To return to command mode, hit the <escape> key.
  • to save the current file, from command mode, type the Colon character, followed by lower-case w and lower case q, then hit enter ( :wq <enter> ).

Now, you should be looking at a blank screen in vi. Enter Insert Mode ( *I ) and type the following two lines:

Type Bash Script Into Vi that Creates an Alias for Sublime Text 3
#!/bin/sh
exec /opt/sublime_text/sublime_text "$@"

 

Then exit Insert Mode ( <esc> ) and save the file ( :qw ).

Congratulations. You have now created a Bash script. Now, let's make sure the access permissions on our script are appropriate. Set the access permissions as follows:

Set Access Permissions on new Bash Script:
$ sudo chmod 755 subl3

 

This allows anyone to read or execute the script, but only the owner can write to it.

Now, we need to move the script file to the /usr/bin/ directory, so that it can properly respond to Bash commands:

Move Script to Proper Directory:
$ sudo mv subl3 /usr/bin/

 

Now, having done all that, let's look at the easier way to accomplish all of the above steps.

Install Sublime Text 3 on Linux Mint /Ubuntu Using Personal Package Archive (PPA)

This is what I consider the better way to do this.

Canonical, the company which supports Ubuntu, has created the Launchpad.net site which, among other things, hosts a repository for Personal Package Archives (PPA's). Here, individuals and teams can upload their own software and installation packages, and it is possible to find deployment packages for software that is not included in the Ubuntu or Linux Mint Synaptic Package Manager for your specific distribution. It is also possible to add the PPA to your Synaptic catalog, so that you can then run apt-get install, apt-get update and the like to keep your package up to date.

Or, at least as up to date as the package maintainer at Launchpad keeps theirs.

The WebUpd8team at Launchpad has created (among other things) a PPA for Sublime Text 3 (currently still in beta as of this writing). To add Sublime Text 3 to your Synaptic catalog, and install according to the install script published with the PPA, follow these steps:

Add the Sublime Text 3 Repository to your Synaptic Package Manager:
sudo add-apt-repository ppa:webupd8team/sublime-text-3

 

Update:
sudo apt-get update

 

Install Sublime Text:
sudo apt-get install sublime-text-installer

 

Next, check the usr/bin directory. You should see a file named subl. This is the alias you can use to invoke Sublime Text 3 from the command line, just like we created manually above.

There you have it. You can now use Sublime Text 3 from you command line. Also, you should see it available in your GUI in the applications menu.

This has been a long post about a relatively simple operation. My goal has been to explain the concepts as fully as possible, under the assumption that there are those out there, like myself, new enough to Linux to need the extra handholding.

In addition, if you first followed the manual instructions, you may have learned a few more of the basics in working with Linux (an vi).

Thanks for reading!

Other Topics of Interest

 

Posted on August 25 2013 10:10 AM by jatten     

Comments (1)

About the author

My name is John Atten, and my "handle" on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, JavaScript/Node, and databases of many flavors. Actively learning always. I dig web development. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten at typecastexception dot com

Web Hosting by