Блог пользователя andreyv

Автор andreyv, 9 лет назад, По-русски

Как известно, C++ принимает, что программист всегда прав, и поэтому компиляторы C++ не вставляют в программу дополнительные проверки на, например, разыменование нулевого указателя или доступ вне границ массива. У этого есть хорошая сторона: программа на C++ выполняется максимально быстро, и плохая сторона: иногда мы проводим долгое время за отладкой, чтобы в конце найти какую-то глупую ошибку. Хотелось бы, чтобы компилятор подобные ошибки находил сам. И многие компиляторы это могут! В этом посте я расскажу о различных параметрах GCC, которые это делают. Эту тему ранее уже освещал zakharvoit в этой статье.

Все параметры, которые здесь будут приведены, надо добавлять к командной строке GCC. В различных IDE это можно сделать в настройках IDE или компилятора. Многие из параметров можно применять и с Clang (например, в Xcode). Для MSVC++, пожалуй, нет ничего лучше, чем Debug-режим и /W4.  

Предупреждения GCC

Конечно, первый шаг при ловле ошибок — это включить разные предупреждения компилятора. Уже одно это часто помогает. Как необходимый минимум можно назвать -Wall -Wextra -O2. Последний параметр нужен потому, что некоторые предупреждения включаются только вместе с оптимизацией. Далее я приведу ещё несколько полезных параметров, которые не включаются сами вместе с -Wall -Wextra.

  • -pedantic — предупреждает об используемых нестандартных расширениях языка C++. Таким образом можно отсечь то, что может не поддерживаться на тестирующем сервере, и не тратить потом время на исправление кода. Лучше всего использовать вместе с -std=c++03 или -std=c++11. Например, -pedantic -std=c++03 выдаст предупреждение на
printf("%lf\n", 1.0);

— правильно писать

printf("%f\n", 1.0);
  • -Wshadow — предупреждает, если имя объявленной переменной перекрывает такое же имя на более высоком уровне. Например, такой код вызовет предупреждение:
int n;
void solve()
{
    // Solve the problem
}
int main()
{
    int n; cin >> n;
    solve();
}
  • -Wformat=2 — предупреждает, если тип аргумента printf()/scanf() не соответствует указанному в строке формата. Частично это уже включено с -Wall, но -Wformat=2 более строг.

  • -Wfloat-equal — предупреждает, если два числа с плавающей точкой сравниваются так: a == b. Обычно правильно сравнивать так: fabs(a - b) < eps.

  • -Wconversion — предупреждает, если при неявном преобразовании типов могут потеряться данные.¹ Чаще всего это случайное присваивание значения long long int в переменную типа int. У меня это предупреждение включено с тех пор, как я завалил задачу, написав pair<int, int> вместо pair<int, long long> :)

    ¹ Явное преобразование (например, (double)my_long_long_var) не вызовет предупреждения.

  • -Wlogical-op — предупреждает о подозрительном использовании логических операторов там, где GCC ожидал бы побитовые операторы.

  • -Wshift-overflow=2 — предупреждает о переполнении при операциях левого сдвига (GCC 6+).

  • -Wduplicated-cond — предупреждает, если условие в if (…) else if (…) повторяется (GCC 6+).

Есть ещё -Wcast-qual и -Wcast-align, но они бывают полезны реже (хоть и не мешают). Подробнее о предупреждениях GCC можно прочитать здесь: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html

Инструменты стандартной библиотеки

Кроме самого компилятора, есть ещё стандартная библиотека C/C++. Она тоже позволяет задать параметры, которые помогают при отладке программ.

  • -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC — эти параметры включают специальный отладочный режим стандартной библиотеки GNU C++. В этом режиме стандартные контейнеры и алгоритмы делают всевозможные проверки. Например, такой код:
int main()
{
    vector<int> v(3);
    cout << v[7] << endl;
}

в этом режиме выдаёт

 /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.

А такой код:

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

выдаёт

 /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 (только Linux/glibc) — этот параметр вставляет в программу разные проверки с уклоном в безопасность (переполнения буфера и т.д.). Например, с этим параметром такая программа:
int main()
{
    char s[9];
    strcpy(s, "too large");
    cout << s << endl;
}

выдаёт

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

Инструменты GCC

Сам компилятор GCC тоже содержит инструменты, которые помогают находить ошибки в программах.

  • -fsanitize=address (только в GCC 4.8+ и Clang) — этот параметр встраивает в программу проверки доступов к памяти и ловит многие выходы за границы массивов. Например:
int arr[3];

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

выдаёт

 =================================================================
==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 (только в GCC 4.9+ и Clang) — похожий параметр, которая ловит неопределённое поведение (undefined behavior), например, доступ по нулевому указателю. Такой код:
int main()
{
    int *p;
    cout << *p << endl;
}

выдаёт

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

Ещё -fsanitize=undefined может находить деление на ноль, неправильные битовые сдвиги, целочисленные переполнения и выход из функции без возврата значения.

Эти два параметра описаны в https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html. Есть ещё параметр -fstack-protector.

Всё вместе

Объединяя всё вместе, получаем

-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

Это всё (или по выбору) можно добавить к командной строке GCC на своём компьютере. Получается, что на своём компьютере мы запускаем отладочную версию с разными проверками, а на сервере — обычную. Естественно, что отладочная версия на своём компьютере будет работать медленнее обычной — больше всего влияют параметры -fsanitize (могут замедлить в два раза и более). Но это легко учесть, и это небольшая плата за поиск ошибок. В зависимости от версии компилятора и операционной системы не все эти параметры будут работать — их можно просто убрать из списка.

Надеюсь, этот пост поможет кому-то тратить меньше времени на отладку и больше времени на решение задач. Удачи всем :)

UPD1: С -D_GLIBCXX_DEBUG обнаружена проблема, для которой есть обходной путь.

UPD2: Обновлён список параметров:

  • Добавлен -fno-sanitize-recover (GCC 5+)
  • Удалён -lmcheck (его заменяет -fsanitize=address)
  • Удалён -ftrapv (его заменяет -fsanitize=undefined)
  • Удалён -fwhole-program (работает нестабильно и не является отладочным параметром)

UPD3: Добавлены -Wshift-overflow=2 и -Wduplicated-cond (GCC 6+).

  • Проголосовать: нравится
  • +899
  • Проголосовать: не нравится

»
9 лет назад, # |
  Проголосовать: нравится +180 Проголосовать: не нравится

Черт!

»
9 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

В GCC под убунтой -fsanitize=address очень яро ругается на стандартную библиотеку, и ко всему понять на что именно оно ругается я не могу.

  • »
    »
    9 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Может быть, какой-то из компонентов слишком старый — у меня сейчас GCC/libstdc++ 4.9.2 и glibc 2.20. Или же Ubuntu что-то такое сделали, что всё сломалось. В любом случае было бы интересно посмотреть на тестовую программу и список ошибок.

    • »
      »
      »
      9 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      И еще -fsanitize=undefined оно не узнает вообще.
      А вот одна из ошибок они ошибки:

      === Build file: "no target" in "no project" (compiler: unknown) ===  
      /home/vlad/files/test.o||In function `_GLOBAL__sub_I_00099_1_main':  
      test.cpp:(.text.startup+0x125)||undefined reference to `__asan_init_v1'  
      test.cpp:(.text.startup+0x138)||undefined reference to `__asan_register_globals'  
      /home/vlad/files/test.o||In function `_GLOBAL__sub_D_00099_0_main':  
      test.cpp:(.text.exit+0xb)||undefined reference to `__asan_unregister_globals'  
      error: ld returned 1 exit status  
      === Build failed: 4 error(s), 0 warning(s) (0 minute(s), 1 second(s)) ===  
      

      P.S. при прошлом запуске их было намного больше...
      Если нужно сброшу код, но он просто чтобы протестировать эти все директивы.
      P.P.S. тестирую под ubuntu 14.04, Code::Blocks svn build rev 10035 x64
      версия GCC походу последняя.
      P.P.P.S. понял, что ошибка именно в этой директиве, методом научного тыка

»
9 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Попробовал под Windows — не всё заработало (4.9.2). Нет библиотек для линковки для -lmcheck -fsanitize=address -fsanitize=undefined. А под Linux казалось бы есть valgrind, который тоже много что умеет.

  • »
    »
    9 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    -lmcheck доступен только в Linux. По поводу библиотек для -fsanitize=address -fsanitize=undefined — получается, что тот, кто делал эту сборку GCC, забыл включить их в поставку. Да, Valgrind тоже можно использовать.

»
9 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

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

»
9 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

У меня простая программа (чтение строки) http://ideone.com/waGBnT при компиляции строкой "g++-4.9 problem.cpp -o problem -O2 -D_GLIBCXX_DEBUG" падает при исполнении: "problem(64488,0x7fff75663300) malloc: *** error for object 0x106e28080: pointer being freed was not allocated"

  • »
    »
    9 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    У меня получилось это воспроизвести с -fwhole-program -D_GLIBCXX_DEBUG. Если оставить любой один из двух, то программа работает. Так выходит?

    Я пока на время уберу -fwhole-program из общего списка.

    • »
      »
      »
      9 лет назад, # ^ |
        Проголосовать: нравится +5 Проголосовать: не нравится

      Так выходит?

      У меня падает ровно со строкой, которую я написал.

      С g++-4.9 problem.cpp -o problem -O2 -fwhole-program все работает.

      • »
        »
        »
        »
        9 лет назад, # ^ |
          Проголосовать: нравится +8 Проголосовать: не нравится

        Хмм, тогда получается, что баг в libstdc++, просто не всегда проявляется. Я ещё заметил, что если написать s.reserve(1000); перед чтением, то тогда больше не падает. Я попробую потом исследовать эту проблему и сообщить в https://gcc.gnu.org/bugzilla/.

        • »
          »
          »
          »
          »
          9 лет назад, # ^ |
          Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

          Да,s.reserve(1000); у меня тоже решает проблему.

          Для информации: у меня нe linux, но mac c "Os Yosemite"

          • »
            »
            »
            »
            »
            »
            9 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

            Я изучил проблему — в моём случае виноват действительно -fwhole-program. Так что тут, наверное, что-то другое, хоть и проявляется так же. Может быть, вот это: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53838?

»
9 лет назад, # |
  Проголосовать: нравится +13 Проголосовать: не нравится

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.

  • »
    »
    9 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    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.

    • »
      »
      »
      9 лет назад, # ^ |
        Проголосовать: нравится +16 Проголосовать: не нравится

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

  • »
    »
    9 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится +5 Проголосовать: не нравится

    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.

»
9 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Автор, скинь пожалуйста код с реализацией всего написанного выше. Я что то не очень понимаю что и где необходимо писать. Заранее спасибо за понимание:)

»
9 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

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.

»
9 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

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

  • »
    »
    4 года назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Thanks, I couldn't compile without add them to linker options. Can you please explain why is it necessary?

»
7 лет назад, # |
  Проголосовать: нравится +18 Проголосовать: не нравится

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.
»
7 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

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?

  • »
    »
    7 лет назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится

    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.

    • »
      »
      »
      7 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      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.

  • »
    »
    6 лет назад, # ^ |
      Проголосовать: нравится +5 Проголосовать: не нравится

    binary_search does not require the range to be sorted, only that it is partitioned with respect to the argument, which makes perfect sense. More info here.

»
6 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Can anyone help me, where shall I add above debug flags mentioned in blog, in sublime text ? I use Sublime Text 3 build system for compiling my code on Ubuntu 16.04

  • »
    »
    6 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    In the custom build system, there should be an array "cmd". Put these flags inside it.

»
6 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

One very useful option that is not listed in this post is -Wsign-conversion. Just -Wconversion doesn't warn about implicit conversion between unsigned and signed types.

»
6 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится

I want to make a small correction to the blog with information that I checked in my machine:

-fsanitize=undefined does not find divisions by zero and leaving a function without a return value,-Wall -Wextra -O2 does this. But -fsanitize=undefined does warn about integer overflow.

Also just to add some extra information that may be helpful for others:

-fsanitize=address checks for out of bounds in both arrays and STL containers

-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC checks for out of bounds only in stl containers, not in arrays. But it also does other stuff regarding STL though.

  • »
    »
    5 лет назад, # ^ |
      Проголосовать: нравится +4 Проголосовать: не нравится

    Is it possible to get the line number in the code when the program raises an exception? While it is definitely helpful to know that there is a bug, it would be extremely valuable to know where it has occurred.

    • »
      »
      »
      5 лет назад, # ^ |
        Проголосовать: нравится +8 Проголосовать: не нравится

      Try adding debug info with the -g option. The sanitizers will pick this up and show line numbers in their error messages.

      That doesn't work for _GLIBCXX_DEBUG, but in that case, you can run the program under a debugger and print the backtrace there.

      andreyv can you please add this info to the article?

»
4 года назад, # |
  Проголосовать: нравится +17 Проголосовать: не нравится

Two notes about -Wconversion:

  • For int MOD;, the expression int x = y*(long long)z % MOD; will raise a warning. (reported at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92220 )
  • Conversions in system headers won't be reported. While this is expected (because the C++ library is not warning-free), some real bugs can be hidden.
    Example: std::array<long long,1> b{{111111111111LL}}; std::count_if(begin(b), end(b), [](int x){ return x > 0; }); will not raise any warning although long long is silently converted to int when passed to the lambda.
»
4 года назад, # |
Rev. 2   Проголосовать: нравится -8 Проголосовать: не нравится

In my pc(Windows 10, i5-7th gen, 8GB RAM) -fsanitize=address -fsanitize=undefined -fno-sanitize-recover -fstack-protector is not working . It shows :

c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe:
cannot find lasan                                       
c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: 
cannot find -lubsan                                      
collect2.exe: error: ld returned 1 exit status                                                                

Please help me how should I solve this problem. I use FAR MANAGER.

»
4 года назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

GCC 10 supports static analyzer (read more) which be enabled by -fanalyzer flag.

Sample output: catching of double free (taken from the link above).

$ gcc -c -fanalyzer double-free-1.c
double-free-1.c: In function ‘test’:
double-free-1.c:6:3: warning: double-‘free’ of ‘ptr’ [CWE-415] [-Wanalyzer-double-free]
    6 |   free(ptr);
      |   ^~~~~~~~~
  ‘test’: events 1-2
    |
    |    5 |   free(ptr);
    |      |   ^~~~~~~~~
    |      |   |
    |      |   (1) first ‘free’ here
    |    6 |   free(ptr);
    |      |   ~~~~~~~~~
    |      |   |
    |      |   (2) second ‘free’ here; first ‘free’ was at (1)
    |
»
4 года назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC: Is it possible to print the line from the source code where out of bounds access is happening to the console? It currently prints the line number from vector file.

»
4 года назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Can someone share their sublime build file for these flags on windows, specifially the one that Errichto uses on his geany setup on linux ?

Here is the link to his setup -> https://github.com/Errichto/youtube/wiki/Linux-setup

I want mainly those sanitizers in the sublime build for c++.

It would be a great help if someone can possibly share it.

»
3 года назад, # |
Rev. 6   Проголосовать: нравится 0 Проголосовать: не нравится

If anyone is using using Linux terminal to compile their code probably like this: g++ program.cpp or gcc program.c Then we can compile same program with above flags using simple syntax below:

compile program.cpp

Method Here