Как известно, 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+).
Черт!
Настолько не понравилось?)))
В GCC под убунтой -fsanitize=address очень яро ругается на стандартную библиотеку, и ко всему понять на что именно оно ругается я не могу.
Может быть, какой-то из компонентов слишком старый — у меня сейчас GCC/libstdc++ 4.9.2 и glibc 2.20. Или же Ubuntu что-то такое сделали, что всё сломалось. В любом случае было бы интересно посмотреть на тестовую программу и список ошибок.
И еще -fsanitize=undefined оно не узнает вообще.
А вот
одна из ошибокони ошибки:P.S. при прошлом запуске их было намного больше...
Если нужно сброшу код, но он просто чтобы протестировать эти все директивы.
P.P.S. тестирую под ubuntu 14.04, Code::Blocks svn build rev 10035 x64
версия GCC походу последняя.
P.P.P.S. понял, что ошибка именно в этой директиве, методом научного тыка
Это он не ругается на стандартную библиотеку, это такая же проблема, как у MikeMirzayanov ниже — компоновщик не может найти вспомогательную библиотеку для
-fsanitize=address
. Вот тут сказано, что в 14.04 только GCC 4.8: http://packages.ubuntu.com/trusty/gcc.Может, стоит попробовать 4.9? Надо выполнить инструкции из http://askubuntu.com/questions/428198/getting-installing-gcc-g-4-9-on-ubuntu и запускать
g++-4.9
вместоg++
.Да, спасибо, все заработало.
Попробовал под Windows — не всё заработало (4.9.2). Нет библиотек для линковки для
-lmcheck -fsanitize=address -fsanitize=undefined
. А под Linux казалось бы есть valgrind, который тоже много что умеет.-lmcheck
доступен только в Linux. По поводу библиотек для-fsanitize=address -fsanitize=undefined
— получается, что тот, кто делал эту сборку GCC, забыл включить их в поставку. Да, Valgrind тоже можно использовать.Thank you... I did not know about this options... It is very useful...
У меня простая программа (чтение строки) 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"
У меня получилось это воспроизвести с
-fwhole-program -D_GLIBCXX_DEBUG
. Если оставить любой один из двух, то программа работает. Так выходит?Я пока на время уберу
-fwhole-program
из общего списка.У меня падает ровно со строкой, которую я написал.
С
g++-4.9 problem.cpp -o problem -O2 -fwhole-program
все работает.Хмм, тогда получается, что баг в libstdc++, просто не всегда проявляется. Я ещё заметил, что если написать
s.reserve(1000);
перед чтением, то тогда больше не падает. Я попробую потом исследовать эту проблему и сообщить в https://gcc.gnu.org/bugzilla/.Да,
s.reserve(1000);
у меня тоже решает проблему.Для информации: у меня нe linux, но mac c "Os Yosemite"
Я изучил проблему — в моём случае виноват действительно
-fwhole-program
. Так что тут, наверное, что-то другое, хоть и проявляется так же. Может быть, вот это: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53838?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.
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.I believe one can remember much more(maybe not a full line) easily. It's much less then code template
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.
Автор, скинь пожалуйста код с реализацией всего написанного выше. Я что то не очень понимаю что и где необходимо писать. Заранее спасибо за понимание:)
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.
On Linux I can reproduce it by using
-D_GLIBCXX_DEBUG
and-fwhole-program
together. I created a bug report here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64504-fwhole-program
causes multiple definitions of the objectstd::string::_Rep::_S_empty_rep_storage
to be created, whereas there should be only one. I suspect the reason is the same in the cases you listed, but the cause is different.EDIT: There is a detailed explanation with a workaround in one of the links you posted: https://lists.apple.com/archives/cocoa-dev/2009/Sep/msg01199.html
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
Thanks, I couldn't compile without add them to linker options. Can you please explain why is it necessary?
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.
Can anyone explain why
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?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.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.
That makes much better sense, actually. This invariant is enough for
binary_search
to run successfully on a specific element.You're correct that it's O(n). Here's the source where it's checked
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/debug/debug.h#L65
I also found that {
set
,priority_queue
} insert/push operations are O(n) because the {bst, heap} property is assertedbinary_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.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
In the custom build system, there should be an array "cmd". Put these flags inside it.
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.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.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.
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?
Two notes about
-Wconversion
:int MOD;
, the expressionint x = y*(long long)z % MOD;
will raise a warning. (reported at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92220 )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 althoughlong long
is silently converted toint
when passed to the lambda.In my pc(Windows 10, i5-7th gen, 8GB RAM) -fsanitize=address -fsanitize=undefined -fno-sanitize-recover -fstack-protector is not working . It shows :
Please help me how should I solve this problem. I use FAR MANAGER.
same. any help :( ? or what is the reason?
Also getting the same error on GEANY
Seems that
-fsanitize=address
is not support by some version of GCC on windows.I've tested that before, and it's usable when using GCC on WSL.
So I guess that is the platform's problem.
GCC 10 supports static analyzer (read more) which be enabled by
-fanalyzer
flag.Sample output: catching of double free (taken from the link above).
-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.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.
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:Method Here
Or, the same thing can be achieved by declaring some alias in the ~/.bashrc file.