Jump to content

Linux Applications Debugging Techniques/The compiler

From Wikibooks, open books for an open world

When requested to optimize (-O2 and above), the compiler is granted a "total license" to modify it: treats undefined behavior (by the standard) as it cannot happen. Thus, the resulting code behaves differently in release-optimized mode than debug mode. Such differences in behavior cannot be found by static analyzers or code reviews.

Signed Integer Overflow

[edit | edit source]

1. This is C99 undefined behavior and the compiler assumes overflow cannot occur. Thus, any checks for overflow are discarded and the following is an infinite loop in optimized code:

int i, j=0;
for (i = 1; i > 0; i += i) {
    ++j;
}

2. The following:

    (i * 2000) / 1000

is optimized into:

    i * 2
  • Use -Wstrict-overflow=N with N>=3 to signal cases where code is optimized away.
  • Use -fwrapv

If lucky enough to use gcc 4.9 and later, you can use the ubsan sanitizer:

  • compile and link the program with -fsanitize=undefined option
  • use other available flags as needed.

Unsigned Wrap Around

[edit | edit source]

The compiler assumes unsigned integers do not wrap. Keep the unsigned variables in the range [INT_MIN/2, INT_MAX/2] to avoid surprises.


"Dead Code" Removed

[edit | edit source]

The memset() call could be removed because the compiler deems buf unused at that point in the code and after:

void do_something(void {
    char buf[123];
    ... use buf...
    /* Clear it. But removed by gcc: */
    memset(buf, 0, sizeof(buf));
}
  • Use #pragma optimize() directives to force the code in.
  • Use volatile.


volatile Pitfalls

[edit | edit source]

Code can be moved around:

volatile int flag = 0;
char buf[123];
void init_buf() {
    for (size_t i=0; i<123; ++i) {
        buf[i] = 0; //Do something
    }
    flag = 1; // Can move!
}

could be optimized into:

volatile int flag = 0;
char buf[123];
void init_buf() {
    flag = 1; // Moved!
    for (size_t i=0; i<123; ++i) {
        //Do something
    }
}
  • Use compiler intrinsic barriers to prevent the flag moving.


Loops could be optimized into one read call only:

void *ptr = address;
while ( *((volatile int*)ptr) & flag ) {}


Pointers

[edit | edit source]
  • Use restrict
  • Use -fno-delete-null-pointer-checks. Null pointer checks are deleted if placed after the first use of the pointer.


_STD_ANALYZABLE_

[edit | edit source]

References

[edit | edit source]