Wednesday, February 5, 2014

Become a GCC expert with these little-known command-line options

http://www.openlogic.com/wazi/bid/332308/become-a-gcc-expert-with-these-little-known-command-line-options


The GNU Compiler Collection (GCC) is easy to use, but it offers so many command-line options that no one can remember them all. Here are five uncommon command-line options you can use to get the most out of GCC.
To illustrate these examples, I used GCC 4.7.3 running on Ubuntu Linux 13.04 with Bash 4.2.45.

-save-temps

In simplest terms, the GCC compilation process internally follows four stages:
  • In the first stage, the preprocessor expands all the macros and header files, and strips off comments.
  • In the second stage, the compiler acts on the preprocessed code to produce assembly instructions.
  • In the third stage, the assembler converts the assembly instructions into machine-level code (object files).
  • In the final stage, the linker resolves all the unresolved symbols and combines all the object files to produce an executable.
When you compile a C/C++ source file using gcc, your final output is an executable program. But in some situations you might want to know how the preprocessor expanded a particular macro, or you might just want to take a look at the assembly instructions. To see the intermediate output produced after each of the compilation stages, use the -save-temps option.
For instance, suppose you compile the program helloworld.c using the -save-temps option:
$ gcc -Wall -save-temps helloworld.c -o helloworld
Along with the final executable, gcc produces three other files. helloworld.i is the output of the preprocessing stage, helloworld.s is the output of the compilation stage, and helloworld.o is the output of the assembly stage.

-Wextra

Many developers use the option -Wall to enable warnings during the compilation process, but -Wall does not report all possible warnings. It leaves out, for example, warnings about:
  • Missing parameter type
  • Comparison of a pointer with integer zero using >, <, >=, or <=.
  • Ambiguous virtual bases
If the compiler does not warn you about these problems, your program might produce undesired results when you run it. Consider the following code:
#include

void func(a)
{
    printf("\n func() is passed parameter [%d]\n",a);
    return;
}

int main(void)
{
    printf("\n HELLO \n");
    func(0xFFFFF);
 
    return 0;
}
As you can see, the type of the argument "a" is not specified in function func(). This could be a typo on the part of the programmer who, for example, meant to declare "a" as a "long long" integer, but without that declaration the compiler will assume the default type of variable "a" as int. If you compile this code with the -Wall option, gcc does not produce any warning, and the program could produce undesired results. For example, if a "long long" value that is larger than the maximum value that an "int" can hold is passed as an argument to func(), the program will behave incorrectly.
If you compile the same code with the -Wextra option enabled, you should see the following output:
$ gcc -Wall -Wextra helloworld.c -o helloworld
helloworld.c: In function 'func':
helloworld.c:4:6: warning: type of 'a' defaults to 'int' [-Wmissing-parameter-type]
Once you know about this problem, you can easily fix it by explicitly mentioning the type of function argument "a."
-Wextra offers similar warnings for pointer comparison problems. Consider the following code:
#include

void func()
{
    int a = -1;
    int *ptr = &a;

    if(ptr >= 0)
    {
        a = a+1;
    }
    printf("\n a = [%d]\n",a);
    return;
}

int main(void)
{
    printf("\n HELLO \n");
    func();

    return 0;
}
The pointer "ptr" is being compared with the integer zero in the function func(). This statement is useless, as ptr clearly contains the address of the variable "a," which will always be a positive value. The programmer must have missed the dereference operator * before ptr while comparing its value with zero. Just as in the previous example, if you compile this code with the -Wall option, gcc does not produce any warning, but the program will produce wrong result (a=0) in the output. On the other hand, when you use -Wextra, gcc reports:
$ gcc -Wall -Wextra helloworld.c -o helloworld
helloworld.c: In function 'func':
helloworld.c:9:12: warning: ordered comparison of pointer with integer zero [-Wextra]
As soon as you see a warning related to pointer comparison with zero, you immediately know you have a typo in your code, which you can easily fix by replacing (ptr>=0) with ((*ptr)>=0) in this case.
Read the gcc man page for other warnings -Wextra produces.

-Wfloat-equal

New programmers sometimes try to compare floating point variables using the == operator – something you should never do because of the way floating point numbers are represented internally. The gcc compiler's -Wfloat-equal option produces a warning whenever it encounters a floating point comparison. Consider:
#include

void func(float a, float b)
{
    printf("\n Inside func() \n");
    if(a == b)
    {
        printf("\n a == b\n");
    }
    return;
}


int main(void)
{
    printf("\n HELLO \n");
    func(1.345, 1.345678);

    return 0;
}
Here, the float arguments to the function func() are being compared using the == operator. When you compile this code without using the -Wfloat-equal option, you'll see no warning, but with it, you should see output like this:
$ gcc -Wfloat-equal helloworld.c -o helloworld
helloworld.c: In function 'func':
helloworld.c:7:10: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]
If you see that your code is directly comparing floats, you should drop the direct comparison and think of better logic to solve the problem.

-g

If you use the GNU debugger (GDB) to debug, or Valgrind to detect memory leaks in your program, always compile the program with the -g option, which produces debugging information in the operating system's native format. Other tools can use this information to produce detailed output.
To see how it works, suppose the source file helloworld.c contains following code:
#include
#include
#include

void func()
{
    char *p = (char*) malloc(10);
    printf("\n Inside func() \n");
    return;
}

int main(void)
{
    printf("\n HELLO \n");
    func();

    return 0;
}
If you compile the code without the -g option and run Valgrind's memcheck tool, you'll see a problem – a memory leak:
$ valgrind --tool=memcheck --leak-check=yes ./helloworld
==3471== Memcheck, a memory error detector
==3471== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==3471== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==3471== Command: ./helloworld
==3471==

 HELLO

 Inside func()
==3471==
==3471== HEAP SUMMARY:
==3471==     in use at exit: 10 bytes in 1 blocks
==3471==   total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==3471==
==3471== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3471==    at 0x4C2CD7B: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3471==    by 0x40058D: func (in /home/himanshu/practice/helloworld_dir/helloworld)
==3471==    by 0x4005B6: main (in /home/himanshu/practice/helloworld_dir/helloworld)
==3471==
==3471== LEAK SUMMARY:
==3471==    definitely lost: 10 bytes in 1 blocks
==3471==    indirectly lost: 0 bytes in 0 blocks
==3471==      possibly lost: 0 bytes in 0 blocks
==3471==    still reachable: 0 bytes in 0 blocks
==3471==         suppressed: 0 bytes in 0 blocks
==3471==
==3471== For counts of detected and suppressed errors, rerun with: -v
==3471== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)
The memcheck tool is able to detect the memory leak, but it is unable to say where the leak actually takes place. Without that information, you could have a big problem tracking down the leak when you're working on projects that contain large source files.
If instead you compile the code with the -g option before you run memcheck, the tool can pinpoint the problem:
$ valgrind --tool=memcheck --leak-check=yes ./helloworld
==3517== Memcheck, a memory error detector
==3517== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==3517== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==3517== Command: ./helloworld
==3517==

 HELLO

 Inside func()
==3517==
==3517== HEAP SUMMARY:
==3517==     in use at exit: 10 bytes in 1 blocks
==3517==   total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==3517==
==3517== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3517==    at 0x4C2CD7B: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3517==    by 0x40058D: func (helloworld.c:7)
==3517==    by 0x4005B6: main (helloworld.c:16)
==3517==
==3517== LEAK SUMMARY:
==3517==    definitely lost: 10 bytes in 1 blocks
==3517==    indirectly lost: 0 bytes in 0 blocks
==3517==      possibly lost: 0 bytes in 0 blocks
==3517==    still reachable: 0 bytes in 0 blocks
==3517==         suppressed: 0 bytes in 0 blocks
==3517==
==3517== For counts of detected and suppressed errors, rerun with: -v
==3517== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)
You might also want to profile your program. Code profiling can tell you things such as how much time each function consumes, how many times a function gets called, and which parts of your program are slow and need improvement. In Linux, a popular code profiling tools is the GNU profiler, or gprof. This tool requires the code to be compiled (and linked) using gcc's -pg option. GNU gprof produces detailed profiling information in form of flat profile and call graph.

@file

All of these options may be useful, and you may want to use some or all of them together for all of your compiles. If you find yourself using many command-line options while compiling your programs, you can put all the options in a file and pass the file name to gcc to use all the flags in the file together. For instance, you could create a file named options that contains the line -Wall -Wextra -Wfloat-equal, then pass the file name as a command-line option to gcc:
$ gcc @options helloworld.c -o helloworld
Keeping your gcc compiler options in an options file makes managing multiple command-line options easy.

No comments:

Post a Comment