andreyv's blog

By andreyv, 3 years ago, translation, In English,

As you know, the C++ language assumes that the programmer is always correct. Therefore C++ compilers don't add additional checks to the program, such as checks for null pointer dereference or out-of-bounds array access. This is good, because C++ programs run as fast as possible, and this is bad, because sometimes we may spend a long time debugging some silly mistake. We would want that the compiler can find such mistakes automatically. And many compilers can! In this post I will show various GCC options that do this. Previously zakharvoit already wrote about this here.

All options that will follow should be added to the GCC command line. In various IDEs you can do it in IDE or compiler settings. Many of the options can also be used with Clang (for example, in Xcode). For MSVC++, I think, there is nothing better than Debug mode and /W4.  

GCC warnings

Of course, the first step to debugging is to enable compiler warnings. Even this alone often helps. As a bare minimum I should name -Wall -Wextra -O2. The last option is needed because some warnings are only enabled together with optimization. Below I will list some useful options that are not enabled with -Wall -Wextra.

  • -pedantic — warns about non-standard C++ language extensions. This way you can eliminate stuff that might not be supported by the testing server, and save time needed to rewrite the code. This is best used together with -std=c++03 or -std=c++11. For example, -pedantic -std=c++03 will warn about
printf("%lf\n", 1.0);

— the correct way is

printf("%f\n", 1.0);
  • -Wshadow — warns if a declared name shadows the same name in some outer level. For example, this will cause a warning:
int n;
void solve()
{
    // Solve the problem
}
int main()
{
    int n; cin >> n;
    solve();
}
  • -Wformat=2 — warns if an argument type in printf()/scanf() does not correspond to the format string. This is partially enabled by -Wall, but -Wformat=2 is more strict.

  • -Wfloat-equal — warns if two floating point values are compared directly: a == b. Usually the correct way is: fabs(a - b) < eps.

  • -Wconversion — warns if data can be lost in an implicit conversion.¹ Most often it is accidental assignment of a long long value to an int variable. I have this warning enabled since I failed a problem by writing pair<int, int> instead of pair<int, long long> :)

    ¹ An explicit cast (for example, (double)my_long_long_var) will not trigger this warning.

  • -Wlogical-op — warns about logical operators in places where GCC expects bitwise operators.

  • -Wshift-overflow=2 — warns about left shift overflows (GCC 6+).

  • -Wduplicated-cond — warns about repeated conditions in if (…) else if (…) (GCC 6+).

There are also -Wcast-qual и -Wcast-align, but they are less useful (though don't hurt). You can read more about GCC warnings here: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html

Standard library tools

Besides the compiler itself, there is also the standard C/C++ library. It also has some options that aid debugging.

  • -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC — these options turn on a special debug mode in the GNU C++ standard library. In this mode standard containers and algorithms apply various checks. For example, this code
int main()
{
    vector<int> v(3);
    cout << v[7] << endl;
}

prints

 /usr/include/c++/4.9.2/debug/vector:357:error: attempt to subscript 
container with out-of-bounds index 7, but container only holds 3
elements.

And this code:

int main()
{
    int arr[] = { 3, 1, 2 };
    cout << binary_search(arr, arr+3, 2) << endl;
}

prints

 /usr/include/c++/4.9.2/bits/stl_algo.h:2267:error: elements in iterator
range [__first, __last) are not partitioned by the value __val.
  • -D_FORTIFY_SOURCE=2 (only on Linux/glibc) — this option inserts security checks, such as checks for buffer overflows, into the program. For example, this program:
int main()
{
    char s[9];
    strcpy(s, "too large");
    cout << s << endl;
}

when compiled with this option, prints

 *** buffer overflow detected ***: ./a.out terminated 

GCC tools

The GCC compiler also contains tools that help find mistakes in programs.

  • -fsanitize=address (only in GCC 4.8+ and Clang) — this option inserts memory access checks into the program, such as checks for out-of-bounds accesses. This program:
int arr[3];

int main()
{
    for (int i = 0; i <= 3; i++)
    {
        arr[i] = i;
        cout << arr[i] << " ";
    }
    cout << endl;
}

prints

 =================================================================
==15496==ERROR: AddressSanitizer: global-buffer-overflow on address 0x00000060152c at pc 0x400ad4 bp 0x7fffbac43e00 sp 0x7fffbac43df0
WRITE of size 4 at 0x00000060152c thread T0
. . .
  • -fsanitize=undefined -fno-sanitize-recover (only in GCC 4.9+ and Clang) — a similar option that catches undefined behavior, for example, a null pointer dereference. This code:
int main()
{
    int *p;
    cout << *p << endl;
}

prints

 x.cpp:12:14: runtime error: load of null pointer of type 'int' 

-fsanitize=undefined can also find divisions by zero, undefined integer shifts, signed integer overflows and leaving a function without a return value.

These two options are described in https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html. There is also the -fstack-protector option.

Everything together

Putting everything together, we get

-Wall -Wextra -pedantic -std=c++11 -O2 -Wshadow -Wformat=2 -Wfloat-equal -Wconversion -Wlogical-op -Wshift-overflow=2 -Wduplicated-cond -Wcast-qual -Wcast-align -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC -D_FORTIFY_SOURCE=2 -fsanitize=address -fsanitize=undefined -fno-sanitize-recover -fstack-protector

All this (or selected parts) can be added to GCC command line on the local computer. Therefore, we run a debug-enabled version on our computer, and the usual version on the server. Naturally, the local debug version will be slower — the biggest impact is from the -fsanitize options (can cause 2× and more slowdown). But you can easily account for this, and it is a small price to pay. Depending on the compiler version and operating system, not every option from this list will work — just take such options out.

I hope that this post will help people spend less time debugging and more time solving problems. Good luck :)

UPD1: There is a problem with -D_GLIBCXX_DEBUG, but a workaround is known. (If string s; cin >> s; fails for you, try removing -fwhole-program from the command line or adding s.reserve(...); in the code).

UPD2: Updated options:

  • Added -fno-sanitize-recover (GCC 5+)
  • Removed -lmcheck (replaced by -fsanitize=address)
  • Removed -ftrapv (replaced by -fsanitize=undefined)
  • Removed -fwhole-program (unstable and is not a debug option)

UPD3: Added -Wshift-overflow=2 and -Wduplicated-cond (GCC 6+).

 
 
 
 
  • Vote: I like it  
  • +899
  • Vote: I do not like it  

»
3 years ago, # |
  Vote: I like it 0 Vote: I do not like it

Thank you... I did not know about this options... It is very useful...

»
3 years ago, # |
  Vote: I like it +13 Vote: I do not like it

Although these options is good in some aspects, there are some notable things. In real contests, such as IOI, APIO, or National contests, we won't have time to add all of these options. Therefore, we shouldn't overly depend on them.

  • »
    »
    3 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    I agree. I think only -Wall -Wextra -O2 or similar can be remembered and entered quickly. And of course using these options does not mean we should be careless when writing code.

    • »
      »
      »
      3 years ago, # ^ |
        Vote: I like it +16 Vote: I do not like it

      I believe one can remember much more(maybe not a full line) easily. It's much less then code template

  • »
    »
    3 years ago, # ^ |
    Rev. 2   Vote: I like it +5 Vote: I do not like it

    Anyway, we may do at home a lot of 'mistakes' that may go unnoticed (for example, giving you AC anyway, even when you use an undefined behaviour that just happens to work the way you intended in that case with the judge compiler).

    I believe that having the compiler always point out this stuff for you can teach you a lot and help you get rid of 'bad habits', improving your coding for live contests even if you won't have all those flags there.

»
3 years ago, # |
  Vote: I like it 0 Vote: I do not like it

I am affected by the bug of -D_GLIBCXX_DEBUG causing crash in cin or getline to an empty string. The bug happens to me on Windows( Cygwin32 ,Cygwin64 and MinGW32) but not Linux and not Windows Nuwen MinGW . I wrote a question on stackoverflow (http://stackoverflow.com/questions/28708802/cin-not-working-with-empty-string-when-glibcxx-debug-on-windows) . Then I discovered that GCC 4.2 on OS X suffered the same bug (http://lists.apple.com/archives/cocoa-dev/2009/Sep/msg01096.html) and (http://stackoverflow.com/questions/1962685/xcode-stl-c-debug-compile-error?lq=1)

Does anyone know if this bug is reported to GCC or not? or what is the root cause of it? If anyone has good information about this bug please report if it is not reported.

»
3 years ago, # |
  Vote: I like it 0 Vote: I do not like it

On Code::blocks these command-line flags goes to:

Settings Menu ->Compiler ->Compiler Settings-> Other options tab

and another copy of the same flags to:

Settings Menu ->Compiler ->Linker Settings->Other linker options textbox

»
7 months ago, # |
  Vote: I like it +18 Vote: I do not like it

We should clearly state that those flags are not for ordinary usage and are targeted at "finding errors".

Some of those flags should be used with caution and with lucid understanding of the consequences. It can be important outside of competitive programming, and, maybe, inside.

  1. glibcxx debug mode, ASAN / UBSAN yield a significant slowdown and also in glibcxx debug mode many algorithm complexity requirements are not satisfied (e.g. sorting could become O(n^2)).
  2. ASAN / UBSAN seeks for -O1, not -O2, otherwise the reporting might be reduced.
»
5 months ago, # |
Rev. 2   Vote: I like it 0 Vote: I do not like it

Can anyone explain why

vector<int> x={2,1,3};
cout<<binary_search(x.begin(),x.end(),3);

does not show error and instead prints 1 even with -D_GLIBCXX_DEBUG and -D_GLIBCXX_DEBUG_PEDANTIC (while binary searching 2 does show error)? How is checking done if the container is sorted or not?

  • »
    »
    5 months ago, # ^ |
      Vote: I like it +3 Vote: I do not like it

    I believe the formal answer is "because GCC has no obligation whatsoever to do so".

    Deeper reason is probably that debug checks inside binary_search ensure order of elements which are looked at by the binary search (e.g. the middle element), and nothing else. It may be done in order to preserve logarithmic complexity of binary search. And that may be done in order to prevent significant slowdown of applications in debug mode.

    • »
      »
      »
      5 months ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      I experimented with binary_search with debug mode on, and it looks like O(n). Another hypothesis I can put up is, it checks if the container is partitioned by the element to be searched. That is, if x[i]<p<x[j], then j>i where p is the value to be searched. This is probably what makes it O(n). To check this, I shuffled x[0..k) and x[k+1..n) and searched for x[k] and it worked fine. For other elements, it raised an error.

      • »
        »
        »
        »
        5 months ago, # ^ |
          Vote: I like it 0 Vote: I do not like it

        That makes much better sense, actually. This invariant is enough for binary_search to run successfully on a specific element.