GCC is the default compiler in the GNU+Linux systems, and is used to compile small applications, big softwares, and the kernel. Often beginners are overwhelmed with the amount of information provided in the gcc manual. This tutorial highlights the most useful commands and describes how to use them, how to read warnings and errors from the compiler output and compile the C Language sourcecodes with gcc. This tutorial does not cover advanced options and topics, in depth details are avoided. This tutorial will give the basic idea, and after reading this i hope that the manual pages would be easier to read.

Index

  1. Get the system ready
  2. Hello GCC
  3. Naming the output file: The -o Flag
  4. Show more warnings: The -Wall, -Wextra, -Werror options
  5. Multifile compilation and making objects: The -c option
  6. Linking with system library: The -l, -L and -I options
  7. Making a Shared library: The -shared option
  8. Static Linking: The -static option
  9. A bit of Optimization: The -O switches
  10. Debug : The -g switch
  11. And More
  12. References

Get the system ready

We will learn how to compile a C Language source file with the gcc that is the GNU project C and C++ compiler or the GNU compiler collection. In order to use gcc you need to ensure that gcc is installed in your system. To check if you have gcc installed or not execute the below command from a shell.

gcc --version

If you have gcc installed then you would get the version number and a copyright message as an output, which would be similar as below.

gcc (GCC) 4.3.2 20081105 (Red Hat 4.3.2-7)
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

If you do not have gcc installed then the shell will say that the command is not found.

bash: gcc: command not found

To install gcc check your distribution’s development repository and install gcc

Now we will see the basic compilation methods with gcc.

Hello GCC

First let us write a simple C Language code, and the most common “Hello World” one. Type in the below code in a file with your favorite text editor (mine is KWrite) and save it as ‘hello.c’ .

#include <stdio.h>

int main (void)
{
  printf ("Hello GCC!\n");
  return 0;
}

To compile a code in gcc you need to pass the file name and some options to the gcc program in command line. The general format of the gcc command is

gcc <options> <files> <options>

We will compile ‘hello.c’ in the most simple manner. Compile ‘hello.c’ with the below command.

gcc hello.c

This command simply tells to compile ‘hello.c’ file. No options are applied in this command. After the command has completed you notice that a new file is created in the directory named ‘a.out’ . This is the executable file of the ‘hello.c’ source code. Run it as shown below.

./a.out

And you will get “Hello GCC!” , as output. The command tells the shell to execute the file ‘a.out’ which is in the current directory ‘.’ . (single dot is current directory, double dot is previous directory).

We have succeeded compiling a source file. But what if it contains errors? To check what it does open ‘hello.c’ again and then intentionally make a mistake , say remove the ‘;‘ (semicolon) after the printf statement, and then compile the code again. The erroneous code is shown below.

#include <stdio.h>

void main (void)
{
  printf ("Hello GCC!\n") /* Intentional Error */
  return 0;
}

If you compile the above code it will now show the below message.

hello.c: In function ‘main’:
hello.c:6: warning: ‘return’ with a value, in function returning void
hello.c:4: warning: return type of ‘main’ is not ‘int’

In the error message, the first line tells there is some problem in the main function in ‘hello.c’ file. The second line tells the problem stated above is an error in the 6th line, and the error is then described. It says expected ‘;’ before ‘return’ . The sixth line contains the return statement, and the compiler has found an error before that line and expects a semicolon ‘;‘ . Open the file and you can note where the error is easily; — no semicolon after the printf statement. The last line warns that though the return type of function ‘main’ is ‘void’ , it returns an ‘int’ .

With the line number provided in the error message you can use your text editor to directory jump to the error location and fix it. Keeping the line numbers on in the text editor helps far more to trace errors. To show the line numbers in KWrite press F11. In vim switch to command mode and key in set number to show line numbers, and set nonumber to unset. To jump to a line number say 156, press Ctrl + G in KWrite and enter 156 in the Goto box and press enter to land on that line number. In vim enter type the line number 156 and press ‘g‘ twice when in command mode.

This was a basic demonstration of error reporting. For each error or warning the source file name will be told, and then the name of the function where the error occurs, and also the line number.

Now we have learned to compile C Language programs with gcc. We will explore some more flags.

Naming the output file: The -o Flag

In the previous example after we compiled ‘hello.c’ the executable file which was created was named ‘a.out’ by default. Definitely you will not like such a name for an executable which you are going to distribute or make use. Although you can always rename the executable file name but we will now talk about the -o switch which will let you name the executable when compiling the source file. This switch is used to supply the “output” file name and location of the compiled result. We will now specify a name (and path) of the executable binary with the help of the -o switch.

We will name the executable file ‘hello‘ . To do this we will use the -o switch as below.

gcc hello.c -o hello

The above will create a binary executable named ‘hello’ in the current directory instead of a.out. Now you can execute ./hello to execute the program. You can name the binary file with any name, and can also specify a path. For example if you want to place the executable in your home/bin directory and name it ‘hi’ instead of ‘hello’ , then you should execute

gcc hello.c -o ~/bin/hi

(Note: the output file can be anything a binary executable, an object file, or assembly, or preprocessed. depending on -c -E or -S switch used.)

Show more warnings: The -Wall, -Wextra, -Werror options

Now we will use another flag -Wall (Note the uppercase ‘W’). This is used to show all the warnings. (To know what options are enabled refer the man-page). Normally, without this switch the gcc will also show warnings, as we saw in the previous example when it warned about the return type of main. This option shows even more warnings. Actually gcc has a variety of warning flags, some of which are enabled by default, and you will get them when you normally compile without the warning flags. But the -Wall flag turns all almost all the warning flags , which will show each and every suspicious activity in the code.

Consider an error enriched program to add two numbers below. Name this file ‘add.c

#include <stdio.h>

int main(void)
{
  int a, b;
  unsigned int c;

  printf ("\nEnter a: ");
  scanf ("%d",&a);

  printf ("\nEnter b: ");
  scanf ("%ld",&b);

  c = a + b;

  printf ("\n%d + %d = %d\n",a,b);

  if ((a == c) && (b == c))
    printf ("\na , b , and c are same!");

  return 0;
}
gcc add.c -o add

The compilation of the above ‘add.c’ without any flags, would be perfect, without any warnings. Run the program, enter two simple integers, say 10 and 20, and the answer would be wrong.

Now compile the program with the -Wall flag.

gcc add.c -o add -Wall

Now you can see the picture, two warnings, in main function of ‘add.c’ source file. One in line 12 another in line 16. The message is shown below.

add.c: In function ‘main’:
add.c:12: warning: format ‘%ld’ expects type ‘long int *’, but argument 2 has type ‘int *’
add.c:16: warning: too few arguments for format

Notice in line 12 the format specifier in the printf statement is %ld (long int) but the corresponding variable is of int type. In line 16 there are three format specifiers in the printf format string, but only two variables are there in the variable list. Correct these problems and compile with -Wall flag, and the compilation will be successful.

Almost all the warnings are enabled by the -Wall flag. The -Wextra flag enables some more warning condition flags, and shows you more. (To know about which warning flags are enabled by -Wextra check the gcc man-page.)

Now if you compile the corrected ‘add.c’ with the -Wextra flag you will have another warning shown.

gcc add.c -o add -Wall -Wextra
add.c: In function ‘main’:
add.c:18: warning: comparison between signed and unsigned
add.c:18: warning: comparison between signed and unsigned

The 18th line which detects if all the integers are same, compares variable a with c and b with c. a and b are signed integers and c is unsigned, so comparison between signed and unsigned integers triggers this warning. Change the variable type of c from unsigned int to int and the warning goes off. Note that the warning is shown twice in line 18. This signifies that the there two problems of the same kind in the same line (line 18).

The -Werror switch tells the compiler to interpret the warnings as errors. With this switch, each warning will be shown as if it is an error and the program will not be compiled. Copy-Paste the “error enriched” ‘add.c’ code so that the horrible warnings show again, and then compile the code with the -Werror flag to see how it behaves.

gcc add.c -o add -Wall -Wextra -Werror

The compiler message is shown below, note all the warnings are now reported as errors.

cc1: warnings being treated as errors
add.c: In function ‘main’:
add.c:12: error: format ‘%ld’ expects type ‘long int *’, but argument 2 has type ‘int *’
add.c:16: error: too few arguments for format
add.c:18: error: comparison between signed and unsigned
add.c:18: error: comparison between signed and unsigned

Although keeping warnings does not bar the code to run, and most of the time it runs fine. But sometimes the cause of the warnings create some fault in the code under some specific condition (a bug), and crashes the program or makes it behave strangely. Like uninitialized variables flags, unallocated pointers etc. When compiling a code with some script or make file, say when installing an application from source, the author cannot see whats showing in the screen, the user could make some changes to the sources before he/she compiles it, and which might result in a warning, which layer could prove to be a costly one because the application will just compile and generate the binary. In this case making the make file or other script with adding the -Werror flag in the compiler flag list would forbid compilation and stop, and no binaries would be generated.

There is another switch the -w which suppresses all the warnings, even the normal and general ones.

In the previous examples we have used all the flags as we kept introducing it. But remember you not necessarily need to use all the flags when compiling. Use only those which are needed.

Multifile compilation and making objects: The -c option

Long programs are generally written as modules and the different modules with functions and all are kept in different files. When compiling we need to compile these codes at once. We can do these in some ways.
The first way and the most simple one is to pass all those multiple files to the gcc command-line. For example consider the two C Language source files below.

/* File name : "prog.c" */
#include <stdio.h>                       

/* This is the function prototype of 
 * my_function() which is defined in my_fun.c
 */
void my_function (void);

int main (void)
{
  printf ("This is the main function");
  printf ("\nNow calling \"my_function()\" .");

  /* This function is defined in my_fun.c file */
  my_function ();

  printf ("\nProgram is now in main function again");

  return 0;
}
/* File name : "my_fun.c" */
#include <stdio.h>

void my_function (void)
{
  printf ("\n0_o OmG! the function is now in \"my_function ()\" !");
  printf ("\nBut this was not in the file \"prog.c\"");
  printf ("\nIt is in \"my_fun.c\"");
  printf ("\nEnough, now returning control again to \"main ()\"");
}

The file prog.c only defines the function main and contains a function call to my_function() which is not present in that file. The function prototype is declared at the top, that tells the compiler that the call to my_function() will be done in future. The my_fun.c file contains the definition of the my_function() . If you just compile the prog.c file with gcc prog.c -o prog the it would show you a linker error. Note this is not a compiler error because the compilation was fine as we have already declared the prototype and told the compiler about the my_function(). We will now describe below on how to compiler these two files.

To compile the above multifile program we will simply pass both the files to the gcc command line as below, and you will get the binary executable prog in the current directory.

gcc prog.c my_fun.c -o prog

There is a problem for some large number of files in a big projects. You have to compile all the files each time you make an update, it may be a tiny update to only one file. If you have developed a certain module then it is obvious it will not undergo regular changes and do not need to be compiled each an every time some other module gets updated. For this separate modules/files are compiled separately and kept as object code, and then to build the application we only just need to link the object codes. For example if you already have 500 object code models, and you under go some changes in some other files which uses functions from these modules, then you only need to compile the code, and then link it to the object files in-order to make an executable.

In our test case we have two files. We will compile the prog.c and my_fun.c as objects with the -c switch, and then link the object files to make a binary. The process is described below.

Run the below commands to compile and make the object files of the C Language codes.

gcc -c prog.c -o prog.o
gcc -c my_fun.c -o my_fun.o

The -c switch compiles the source codes and then makes an object file and names them as specified. The -o switch here is used to name the output object code files. The .o extension is normally used to denote object file . Now you will have two object files named prog.o and my_fun.o in the current working directory. We will have to link these together in order to have an executable.

We can link these two files and make the executable with the command below.

gcc prog.o my_fun.o -o nprog

This is the same command to compile two C Language source files, but instead we use the object files. The -o tag here also has the same meaning. You can execute the binary now.

Let us change some code in the main function and add another line to print. We will add a line before the my_function() call, as shown below.

/* File name : "prog.c" */
#include <stdio.h>                       

/* This is the function prototype of my_function() which is defined 
 * in my_fun.c . The prototype could also be inside a header file and
 * you can include it here
 */
void my_function (void);

int main (void)
{
  printf ("This is the main function");
  printf ("\nNow calling \"my_function()\" .");

  /* This function is defined in my_fun.c file */
  my_function ();

  /* The below line was inserted */
  printf ("\n\tHello i am the new line!!");
  printf ("\nProgram is now in main function again");

  return 0;
}

Making this change in main() located in file ‘prog.c’ does not change the file ‘my_fun.c’ , so we only need to compile the ‘prog.c’ file, where the change occurred, and make an object file reflecting the changes. Then we will link the new updated ‘prog.o’ file with the old unmodified ‘my_fun.o’ object file and make the executable binary. Thus we do not need to compile both the files.

If you has some 1000 files and you have modified only 1 file then instead to compile 1000 files you need to compile 1 file and then link it with the object files to update the binary. This saves a huge compile time. To make this use far more easy and let some software handle which file to compile and which not (depending on which was updated) read about the make files.

Linking with system library: The -l, -L and -I options

Consider the below program “sqroot.c” containing a math.h function.

/* File name : "sqroot.c" */
#include <stdio.h>
#include <math.h>

int main (void)
{
  double a,roota;
  
  printf ("Enter an integer: ");
  scanf ("%lf",&a);
  
  roota = sqrt (a);
  
  printf("The Square Root of \"%lf\" is \"%lf\"\n",a,roota);
  return 0;
}

This program takes an input and prints out it square roots value. Compile it normally and you will get a linker error as shown below.

/tmp/ccqFNKdP.o: In function `main':
power.c:(.text+0x51): undefined reference to `sqrt'
collect2: ld returned 1 exit status

From this error many people come to a conclusion that “gcc do not have a math library”, and which is really funny and ridiculous. What you have to do is just to link the libc math library to your program with the -l option, as shown below.

gcc sqroot.c -o sqroot -lm

This command links the libm.so object file with the sqroot.c sourcecode and thus the sqrt () function can be linked. similarly if you have ncurses functions in your code you need to link it with libcurses.so file by using -lcurses option. The -l surrounds the library name with lib and .a/.o/.so . Like if you give -llol to link then the compiler will find liblol.o or liblol.so or liblol.a files in the default directories and link.

for example you can see which libraries are your code is linked with the ldd tool as shown below:

$ ldd sqroot
        linux-gate.so.1 =>  (0x00bd4000)
        libm.so.6 => /lib/libm.so.6 (0x00d1d000)
        libc.so.6 => /lib/libc.so.6 (0x00110000)
        /lib/ld-linux.so.2 (0x00b82000)

If you have the library files in some location which is not a system default and link them with the -l option, then you need to tell that location to the compiler explicitly by the -L option and then the path to the directory. Like if you have your a library file /home/user/libs/libhoard.so then you need to pass -L/home/user/libs and -lhoard. So the commands becomes.

gcc source.c -o source.bin -L/home/user/libs -lhoard


Making a Shared library: The -shared option

We go back to our ‘prog.c’ example and will make libmyfun.so library file of our own, and Then if you copy this file into /usr/lib . You will need superuser permission to copy.

gcc my_fun.c -o libmyfun.so -shared
cp libmyfun.so /usr/lib

Now when compiling ‘prog.c’ or any file calling my_function() you need only to link the object file with the source-file with the -lmyfun option. As shown below.

gcc prog.c -o prog -lmyfun

You can run the ldd command on this executable and check out that it is linked to /usr/lib/libmyfun.so file.

This links with the library you just created. Similarly if you keep the libmyfun.so in some other place than the system defaults directory then you will need to tell the compiler the location with the -L option as we told above. If the libmyfun.so is in /home/user/project/lib/libmyfun.so and the source-file is in /home/user/project/src/prog.c then you need to pass the absolute or relative path (relative to current working directory) to the compiler. If we are inside /home/user/project/src/ directory then we should execute

gcc prog.c -o prog -L/home/user/project/lib -lmyfun

or when you are inside /home/user/project/src the relative path will be

gcc prog.c -o prog -L../lib -lmyfun

Try these yourselves.

Let us keep some header files in a certain directory which is not a system default location, say in /home/user/project/include we are keeping all the header files of the project. We have a source file scrabble.c inside /home/user/project/src directory. the two files include two header files ‘header1.h’ and ‘header2.h’ like below.

#include <header1.h>
#include <header2.h>

Note the conic brackets will only search the files in system default directories, (even with double quotes the .h files won’t be found in the current directory). To compile there files we need to tell the compiler about our include directory with the -I switch as below.

gcc scrabble.c -o scrabble -I/home/user/project/include

or the same with the below

gcc scrabble.c -o scrabble -I../include

Static Linking: The -static option

By default all the executable binary files are compiled as dynamic. This way when you will tun the binary it will refer to the linked libraries and run the code from there. Like it you include a math function in a code then when executing the code will access that particular math function code from the glibc math library. This is the same case when you are linking some binary code with some of your library or any 3rd party library. Compiling the code as static removes all the references and packs all the codes, which needed to be referred at runtime, inside the binary executable itself. Thus the binary executable size grows, but it becomes a standalone code which do not (almost) depend on any thing. To make static code the -static switch is used as below.

gcc hello.c -o hello_static -static

Note the difference in size of ‘hello’ and ‘hello_static’ below, as shown by ls , and the output of the ldd command on the executable

$ ls -lh hello hello_static
-rwxrwxr-x 1 Phoxis Phoxis 4.8K 2009-11-01 13:27 hello
-rwxrwxr-x 1 Phoxis Phoxis 610K 2009-11-01 13:28 hello_static
$ ldd a.out
        not a dynamic executable

If you run the nm tool on both the static and shared binaries you would see that the shared one has very few symbols in it and the rest of them are linked to the glibc library. In a static one there are no external references and no undefined symbols. Static and Dynamic linking is not described more in this article, maybe in some other article. While developing a code and in general you should use dynamic linked binaries.

A bit of Optimization: The -O switches

at last we will get introduced to the basic code optimization switches. GCC has a lot of compiler optimization switches, which cleverly upgrades the performance of the code, shortens the size etc. There are three optimization switches the -O1, -O2, -O3, Os
The first three increases the execution performance, the last one optimized for size of the executable. These switches actually enables and disables a bunch of other optimization flags at once to give different levels of optimization. To check what switches are turned on or off check the manual page. There are a lot of optimization switches with which fine tuning can be done to the executable. Default is -O0 which reduces the compilation time and helps debug process. When developing and for most of normal coding these optimization switches are not needed. You can try out measuring execution times with the flags and without the flags.

Debug : The -g switch

Use the -g switch when compiling to add debug information to the binary. By doing this you could use a debugged like gdb to debug your code and see what the code is actually doing. This is useful when you are in development stage and using the executable with a debugger. Check the manual to know more advanced debugging options.

And More

There are tons of switches to set what your exact compilation need is. Not all switches are discussed in detail to avoid complexity. To know more about gcc read the man page and the info page of gcc by executing the commands below

man gcc
info gcc

References

  • GCC manual pages
  • GCC info pages
Advertisements

9 thoughts on “Beginners Guide to GCC

  1. All that I can say about this article is that it has been written very beautifully :D Very good for beginners who want to migrate to GCC from window’s Turbo C++ (which is used in schools and most colleges). Hoping for more Linux tutrials in future.

  2. Thanks for a simplified instruct set for us newbies; Great way to get started by your very detailed tutorials. Will be back for more.

    1. This article was written to get beginners an overview of gcc to the beginners, such that they get the idea of different flags available without diving into the sea of flags in the manual page.

      Nice to know that you liked it. Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s