Using GNU ccdoc

By Danielle Greene,2014-11-05 21:30
21 views 0
Using GNU ccdoc

Using GNU cc

    by Kurt Wall


    • Features of GNU cc 40

    • A Short Tutorial 40

    • Common Command-line Options 43

    • Optimization Options 47

    • Debugging Options 48

    • GNU C Extensions 49

GNU cc (gcc) is the GNU project’s compiler suite. It compiles programs written in C,

    C++, or Objective C. gcc also compiles Fortran (under the auspices of g77). Front-ends for Pascal, Modula-3, Ada 9X, and other languages are in various stages of development. Because gcc is the cornerstone of almost all Linux development, I will discuss it in some depth. The examples in this chapter (indeed, throughout the book unless noted otherwise), are based on gcc version

    Features of GNU cc

    gcc gives the programmer extensive control over the compilation process. The compilation process includes up to four stages:

    • Preprocessing

    • Compilation Proper

    • Assembly

    • Linking

    You can stop the process after any of these stages to examine the compiler’s output at

    that stage. gcc can also handle the various C dialects, such as ANSI C or traditional (Kernighan and Ritchie) C. As noted above, gcc happily compiles C++ and Objective C. You can control the amount and type of debugging information, if any, to embed in the resulting binary and, like most compilers, gcc can also perform code optimization. gcc allows you to mix debugging information and optimization. I strongly discourage doing so, however, because optimized code is hard to debug: Static variables may vanish or loops may be unrolled, so that the optimized program does not correspond line-for-line with the original source code.

    gcc includes over 30 individual warnings and three ―catch-all‖ warning levels. gcc is

    also a cross-compiler, so you can develop code on one processor architecture that will be run on another. Finally, gcc sports a long list of extensions to C and C++. Most of these extensions enhance performance, assist the compiler’s efforts at code optimization, or

    make your job as a programmer easier. The price is portability, however. I will mention some of the most common extensions because you will encounter them in the kernel header files, but I suggest you avoid them in your own code.

    A Short Tutorial

    Before beginning an in-depth look at gcc, a short example will help you start using gcc productively right away. For the purposes of this example, we will use the program in Listing 3.1.

    LISTING 3.1 C

    1 /*

    2 * Listing 3.1

    3 * hello.c Canonical ―Hello, world!‖ program 4 4 */

    5 #include <stdio.h>


7 int main(void)

    8 {

    9 fprintf(stdout, ―Hello, Linux programming world!\n‖);

    10 return 0;

    11 }

    To compile and run this program, type

    $ gcc hello.c -o hello

    $ ./hello

    Hello, Linux programming world!

    The first command tells gcc to compile and link the source file hello.c, creating an executable, specified using the -o argument, hello. The second command executes the program, resulting in the output on the third line.

    A lot took place under the hood that you did not see. gcc first ran hello.c through the preprocessor, cpp, to expand any macros and insert the contents of #included files. Next, it compiled the preprocessed source code to object code. Finally, the linker, ld, created the hello binary.

    You can re-create these steps manually, stepping through the compilation process. To tell gcc to stop compilation after preprocessing, use gcc’s -E option:

    $ gcc -E hello.c -o hello.cpp

    Examine hello.cpp and you can see the contents of stdio.h have indeed been inserted into the file, along with other preprocessing tokens. The next step is to compile hello.cpp to object code. Use gcc’s -c option to accomplish this:

    $ gcc -x cpp-output -c hello.cpp -o hello.o

    In this case, you do not need to specify the name of the output file because the compiler creates an object filename by replacing .c with .o. The -x option tells gcc to begin compilation at the indicated step, in this case, with preprocessed source code.

    How does gcc know how to deal with a particular kind of file? It relies upon file extensions to determine how to process a file correctly. The most common extensions and their interpretation are listed in Table 3.1.


    Extension Type

    .c C language source code

    .C, .cc C++ language source code

    .i Preprocessed C source code

    .ii Preprocessed C++ source code

    .S, .s Assembly language source code

    .o Compiled object code

    .a, .so Compiled library code

    Linking the object file, finally, creates a binary:

    $ gcc hello.o -o hello

    Hopefully, you will see that it is far simpler to use the ―abbreviated‖ syntax we used

    above, gcc hello.c -o hello. I illustrated the step-by-step example to demonstrate that you can stop and start compilation at any step, should the need arise. One situation in which you would want to step through compilation is when you are creating libraries. In this case, you only want to create object files, so the final link step is unnecessary. Another circumstance in which you would want to walk through the compilation process is when an #included file introduces conflicts with your own code or perhaps with another #included file. Being able to step through the process will make it clearer which file is introducing the conflict.

    Most C programs consist of multiple source files, so each source file must be compiled to object code before the final link step. This requirement is easily met. Suppose, for example, you are working on killerapp.c, which uses code from helper.c. To compile killerapp.c, use the following command:

$ gcc killerapp.c helper.c -o killerapp

    gcc goes through the same preprocess-compile-link steps as before, this time creating object files for each source file before creating the binary, killerapp. Typing long commands

    like this does become tedious. In Chapter 4, ―Project Management Using GNU

    make,‖ we will see how to solve this problem. The next section will begin introducing

    you to the multitude of gcc’s command-line options.

    Common Command-line Options

    The list of command-line options gcc accepts runs to several pages, so we will only look at the most common ones in Table 3.2.


    Option Description

    -o FILE Specify the output filename; not necessary when compiling to object code. If FILE is not specified, the default name is a.out.

    -c Compile without linking.

    -DFOO=BAR Define a preprocessor macro named FOO with a value of BAR on the command-line.

    -IDIRNAME Prepend DIRNAME to the list of directories searched for include files.

    -LDIRNAME Prepend DIRNAME to the list of directories searched for library files. By default, gcc links against shared libraries. -static Link against static libraries.

    -lFOO Link against libFOO.

    -g Include standard debugging information in the binary.

    -ggdb Include lots of debugging information in the binary that only the GNU debugger, gdb, can understand.

    -O Optimize the compiled code.

    -ON Specify an optimization level N, 0<=N<= 3.

    -ansi Support the ANSI/ISO C standard, turning off GNU extensions that conflict with the standard (this option does not

    guarantee ANSI-compliant code).

    -pedantic Emit all warnings required by the ANSI/ISO C standard. -pedantic-errors Emit all errors required by the ANSI/ISO C standard. -traditional Support the Kernighan and Ritchie C language syntax (such as the old-style function definition syntax). If you

    don’t understand what this means, don’t worry about it.

    -w Suppress all warning messages. In my opinion, using this switch is a very bad idea!


    Option Description

    -Wall Emit all generally useful warnings that gcc can provide. Specific warnings can also be flagged using -W{warning}.

    -werror Convert all warnings into errors, which will stop the compilation. -MM Output a make-compatible dependency list.

    -v Show the commands used in each step of compilation.

    We have already seen how -c works, but -o needs a bit more discussion. -o FILE tells gcc to place output in the file FILE regardless of the output being produced. If you do not

    specify -o, the defaults for an input file named FILE.SUFFIX are to put an executable in a.out, object code in FILE.o, and assembler code in FILE.s. Preprocessor output goes to standard output.

    Library and Include Files

    If you have library or include files in non-standard locations, the -L{DIRNAME} and

    -I{DIRNAME} options allow you to specify these locations and to insure that they are searched before the standard locations. For example, if you store custom include files in /usr/local/include/killerapp, then in order for gcc to find them, your gcc invocation would be something like

    $ gcc someapp.c -I/usr/local/include/killerapp

    Similarly, suppose you are testing a new programming library, (.so is the normal extension for shared libraries more on this subject in Chapter 24, ―Using

    Libraries‖) currently stored in /home/fred/lib, before installing it as a standard system

    library. Suppose also that the header files are stored in /home/fred/include. Accordingly, to link against and to help gcc find the header files, your gcc command line should resemble the following:

    $gcc myapp.c -L/home/fred/lib -I/home/fred/include -lnew

    The -l option tells the linker to pull in object code from the specified library. In this example, I wanted to link against A long-standing UNIX convention is that libraries are named lib{something}, and gcc, like most compilers, relies on this convention. If you fail to use the -l option when linking against libraries, the link step will fail and gcc will complain about undefined references to ―function_name.‖

    By default, gcc uses shared libraries, so if you must link against static libraries, you have to use the -static option. This means that only static libraries will be used. The following example creates an executable linked against the static ncurses. Chapter 27, ―Screen

    Manipulation with ncurses,‖ discusses user interface programming with ncurses:

    $ gcc cursesapp.c -lncurses -static

    When you link against static libraries, the resulting binary is much larger than using shared libraries. Why use a static library, then? One common reason is to guarantee that users can run your programin the case of shared libraries, the code your program

    needs to run is linked dynamically at runtime, rather than statically at compile time. If the shared library your program requires is not installed on the user’s system, she will get

    errors and not be able to run your program.

    The Netscape browser is a perfect example of this. Netscape relies heavily on Motif, an expensive X programming toolkit. Most Linux users cannot afford to install Motif on their system, so Netscape actually installs two versions of their browser on your system; one that is linked against shared libraries, netscape-dynMotif, and one that is statically linked, netscape-statMotif. The netscape ―executable‖ itself is actually a shell script

    that checks to see if you have the Motif shared library installed and launches one or the other of the binaries as necessary.

    Error Checking and Warnings

    gcc boasts a whole class of error-checking, warning-generating, command-line options. These include -ansi, -pedantic, -pedantic- errors, and -Wall. To begin with, -pedantic tells gcc to issue all warnings demanded by strict ANSI/ISO standard C. Any program using forbidden extensions, such as those supported by gcc, will be rejected. -pedantic-errors behaves similarly, except that it emits errors rather than warnings. -ansi, finally, turns off GNU extensions that do not comply with the standard. None of these options, however, guarantee that your code, when compiled without error using any or all of these options, is 100 percent ANSI/ISO-compliant.

    Consider Listing 3.2, an example of very bad programming form. It declares main() as returning void, when in fact main() returns int, and it uses the GNU extension long long to declare a 64-bit integer.


    1 /*

    2 * Listing 3.2

    3 * pedant.c - use -ansi, -pedantic or -pedantic-errors

    4 */

    5 #include <stdio.h>


    7 void main(void)

    8 {

    9 long long int i = 0l;

    10 fprintf(stdout, ―This is a non-conforming C program\n‖);

    11 }

    Using gcc pedant.c -o pedant, this code compiles without complaint. First, try to compile it using -ansi:

    $ gcc -ansi pedant.c -o pedant

    Again, no complaint. The lesson here is that -ansi forces gcc to emit the diagnostic messages required by the standard. It does not insure that your code is ANSI C[nd]compliant. The program compiled despite the deliberately incorrect declaration of main(). Now, -pedantic:

    $ gcc -pedantic pedant.c -o pedant

    pedant.c: In function `main’:

    pedant.c:9: warning: ANSI C does not support `long long’

    The code compiles, despite the emitted warning. With -pedantic- errors, however, it does not compile. gcc stops after emitting the error diagnostic:

    $ gcc -pedantic-errors pedant.c -o pedant

    pedant.c: In function `main’:

    pedant.c:9: ANSI C does not support `long long’

    $ ls

    a.out* hello.c helper.h killerapp.c

    hello* helper.c killerapp* pedant.c

    To reiterate, the -ansi, -pedantic, and -pedantic-errors compiler options do not insure ANSI/ISO-compliant code. They merely help you along the road. It is instructive to point out the remark in the info file for gcc on the use of -pedantic: ―This option is not intended to be useful; it exists only to satisfy pedants who would otherwise claim that GNU CC fails to support the ANSI standard. Some users try to use `-pedantic’ to check programs for strict ANSI C conformance. They soon find that it does

    not do quite what they want: it finds some non-ANSI practices, but not allonly those

    for which ANSI C requires a diagnostic.‖

    Optimization Options

    Code optimization is an attempt to improve performance. The trade-off is lengthened compile times and increased memory usage during compilation.

    The bare -O option tells gcc to reduce both code size and execution time. It is equivalent to -O1. The types of optimization performed at this level depend on the target processor,