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

Автор andreyv, 3 года назад, По-русски,

Как известно, 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
  • Проголосовать: не нравится  

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

Черт!

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

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

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

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

    • »
      »
      »
      3 года назад, # ^ |
        Проголосовать: нравится 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. понял, что ошибка именно в этой директиве, методом научного тыка

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

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

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

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

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

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

»
3 года назад, # |
  Проголосовать: нравится +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"

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

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

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

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

      Так выходит?

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

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

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

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

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

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

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

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

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

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

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

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

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

  • »
    »
    3 года назад, # ^ |
    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.

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

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

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

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

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

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

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

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

Standings are translated to English by matching their team names and their registration info on ICPC official website. For those schools which didn't register on ICPC official website, I have manually "translated" (in fact, Google and Copy & Paste) their http://printcalendartemplates.com/ school name to English. Inform me if you found any mistake.