Practical: Edit-Compile-Run Cycle for C Programs

When developing programs with command-line tools, you typically iterate the following steps:

  1. Edit: Use an editor to develop the code in your programming language of choice. Result: a text file with the source code of the program (e.g. hello.c)

  2. Compile: Use a compiler to translate the source code into a version understood by the machine. Result: a binary file with the executable code of the program (e.g. hello)

  3. Run: Execute the program on the machine. Result: you see the result of executing your program (e.g. messages printed by the program)

This section is a practical on how to use common Linux tools to develop simple programs (we mainly cover C here), to compile a program on the command line and to run the resulting executable. Most of the information here is typical for this Edit-Compile-Run development cycle on the command-line. If you use different languages or compilers you naturally need to adjust the specific commands (and flags), but the overall structure is the same.

This section has evolved out of short summaries about basics of compilation, and more details on this can be found in the slides here.

Editing

There are a lot of good editors for Linux out there, and some of them are discussed in the Section called Editors. In this practical we will used emacs as editor, which by default has a programming language mode for C including highlighting etc.

To get started, download the program called hello.c executing the following command from the commandline (or using this URL):


  $ wget http://www.macs.hw.ac.uk/~hwloidl/Courses/F28HS/srcs/hello.c
  
You now have the file in your current directory. You can confirm this by typing

  $ ls -ltr
  
and the last line displayed should show the filen hello.c.

Now we want to edit this file using emacs. To do so type


  $ emacs hello.c
  
which will start a new window with the editor and it will display the file hello.c in C mode, highlighting C keywords. All this program does is to display the string Hello world! in the terminal; it is intended to be a minimal test program, so that you can focus on the steps needed to edit, compile and run the program.

Use either mouse or cursor keys to navigate, and the menus for basic operations. Useful short-cut commands are CTRL-x CTRL-s for saving the current file and CTRL-x CTRL-c for exiting the editor. A quick-reference card for emacs with the main short-cuts is available here. Also Chapter 7 in Sobell's Linux Guide has a section on the most important commands.

Compiling

Before we can run the program we need to compile it. This means, we need to run a program that takes the C program as input, and produces an executable file, i.e. a file in a format that is understood by the machine. In an IDE the steps of compiling and executing are often conflated by just clicking at a run button. However, conceptually it is important to separate the two steps, because they do very different things.

Now, to compile our hello.c program, we execute the following line:


  $ gcc -o hello hello.c
  
This will translate the C program in hello.c into machine code in hello. This machine code is in binary format, and can be executed directly.

Running

Now that we have generated an executable file, we can run it like this:


    $ ./hello
    Hello world!
  
You should see the string Hello world! when pressing return. We use ./hello, rather than just hello, to specify that we want to executable called hello, in the current directory, rather than another program called hello that might be in a different directory in your PATH (see the Section called Your environment for a discussion of the PATH environment variable).

More editing

We have now gone through one cycle of edit-compile-run, where the edit phase was just the download (and viewing) of a file. Now, we want to modify our program slightly and go through this cycle again, and in doing so meet several other useful developer tools.

Our goal is to add the name of the machine we are running this program on to the string that is displayed. This is a simple step of customisation to our working environment. First we copy the file hello.c to hello0.c, for our new program, and we start the editor on this new file:


  $ cp hello.c hello0.c
  $ emacs hello0.c
  

Now we need to know how to get the name of the machine, or in short the hostname, by using the right library function that is available in C. For this we can use the Linux man pages and the man that we have met in the Section called Basic Linux Usage. The man pages have different sections to separate user commands from library functions, configuration files etc (type man man for an explanation of this structure). A good starting point for us is Section 7, which explains the main concepts. We can use the section number as an additional, first argument to the man command like this:


  $ man 7 hostname
  
This will show the documentation on hostname, explaining the concepts behind it. When you scroll to the bottom of this man page, you find under SEE ALSO a reference to gethostname. This is what we need! So, exit the man page by pressing q and type

  $ man 2 gethostname
  
This will display the man page for the library function that we need (you can also view this in your browser by following this link. The start of the man page summarises the use of the function like this:

  #include <unistd.h>
  int gethostname(char *name, size_t len);
So, to use the gethostname function, we need to include another system file unistd.h, additionally to the file stdio.h which we needed in order to use the printf function in our original hello-world program. The second line tells use that the library function gethostname needs 2 arguments: the first one is a string to write the hostname to, and the second is the maximal size of the hostname that is written to this string. This is explained in he rest of the man page, specifically:

       sethostname() sets the hostname to the value  given  in  the  character
       array  name.   The  len argument specifies the number of bytes in name.
       (Thus, name does not require a terminating null byte.)
The second argument is important for security reasons: if we omit this argument and if the string that we pass to the function cannot hold the full length of the hostname, which we don't know beforehand, then the call would overwrite values beyond the string, which might contain the contents of other variables. This is a common security pitfall, and you should always specify the maximal size of the target buffer/string if you perform a copy operation like this one.

We now have the information that we need, and we can modify the program to also print the hostname. We need to add the #include line at the beginning. In the main function we need to declare a string, or an array of characters, to hold the hostname. We fix the string to a size of 80 characters, and therefore also need to pass 80 as a second argument to gethostname, for the security reasons explained above. Finally we modify the printf call to print both lines. The screenshot below shows the emacs window with the modified C program.

More compiling

Now we want to compile our program again, typing:


  $ gcc -o hello0 hello0.c
  
This time we get an error message from the compiler, stating:

hello0.c: In function 'main':
hello0.c:10: error: invalid operands to binary + (have 'char *' and 'char *')
  

The problem here is that we try to use the operator + on strings, however unlike Java or C#, this is not possible in C, and that is what the error message is telling us. The easiest way of achieving a concatenation of strings like this, is to use the format-string capabilities of the printf command. Again, an explanation of this can be found in the man pages:


  $ man 3 printf
  
and then scroll down to the part on 'Format of the format string'. It tells us, that we can use the special sequence %s as a placeholder for a string, which will be filled by the contents of the next argument provided to the printf function. Therefore, we can change this to

  printf("Hello world!\nThis is running on machine %s\n", name);
  
This will expand to a string, that ends with hostname in the C variable name, and which we have populated by a call to gethostname. The modified program (saved in the new file hello2.c) looks like this, and can be compiled like this

  $ gcc -o hello2 hello2.c
  

More Running

Now that we have generated an executable file, we can run it like this:


    $ ./hello2
    Hello world!
    This is running on machine lxhwloidl
  
Of course your hostname will be different to the one shown here. By displaying the entire line, you can see that the program is doing the right thing.

Further Reading

This section presented a simple example of using the edit-compile-run cycle for a simple C program from the command-line. There are many more useful things that you can try out at this point. For example, if you need to debug your program to locate a problem, you can compile the program with option -g and than use the GNU debugger gdb to single step through the program. The first slide set at this C revision discussed this debugging process, and some more flags to gcc, in more detail.