Table of Contents
Checking for Out of BoundsUnspecified Evaluation OrderGCC Warning Options-Wall-Wextra-Wconversion-Wshadow-Wfloat-equalGCC Debug Options-fsanitize=undefinedExample: Array Out of BoundsExample: Vector Out of BoundsExample: Integer OverflowExample: Detecting Multiple Errors-fsanitize=addressExample: Vector Out of BoundsExample: Array Out of Bounds-D_GLIBCXX_DEBUGUsing the LLDB DebuggerDebugging C++
Authors: Benjamin Qi, Aryansh Shrivastava
Debugging tips specific to C++.
Prerequisites
Table of Contents
Checking for Out of BoundsUnspecified Evaluation OrderGCC Warning Options-Wall-Wextra-Wconversion-Wshadow-Wfloat-equalGCC Debug Options-fsanitize=undefinedExample: Array Out of BoundsExample: Vector Out of BoundsExample: Integer OverflowExample: Detecting Multiple Errors-fsanitize=addressExample: Vector Out of BoundsExample: Array Out of Bounds-D_GLIBCXX_DEBUGUsing the LLDB DebuggerChecking for Out of Bounds
Writing to an out of bounds array index is known as buffer overflow. C++ may or may not produce a runtime error upon buffer overflow. For example, the following code results in a runtime error on ide.usaco.guide, but outputs 4 on my computer.
#include <iostream>#include <vector>using namespace std;int main() {vector<int> invalid_vec{1};vector<int> valid_vec{1234};cout << valid_vec[0] << "\n"; // outputs 1234for (int i = 0; i < 10; i++) {invalid_vec[i] = i; // may or may not error}cout << valid_vec[0] << "\n"; // may output 4}
To ensure that an error is raised when accessing an out of bounds index, you can use vector::at instead of vector::operator[] like so:
#include <iostream>#include <vector>using namespace std;int main() {vector<int> invalid_vec{1};vector<int> valid_vec{1234};cout << valid_vec.at(0) << "\n"; // outputs 1234for (int i = 0; i < 10; i++) {invalid_vec.at(i) = i; // throws std::out_of_range}cout << valid_vec.at(0) << "\n";}
C++ will now check the bounds when we access the vectors and will produce the following output:
1234 terminate called after throwing an instance of 'std::out_of_range' what(): vector::_M_range_check: __n (which is 1) >= this->size() (which is 1) 1 zsh: abort ./$1 $@[2,-1]
Line Numbers
Note that the output above does not contain the line number where the runtime
error occurred. To output the line number, you can use a debugger such as gdb
or lldb
. See the section on debuggers for more information.
Unspecified Evaluation Order
Here is some unexpected behavior you might come across when trying to create a trie or a persistent segment tree.
#include <bits/stdc++.h>using namespace std;vector<int> res{-1};int add_element() {res.push_back(-1);return res.size() - 1; // index of added element}
Compiling and running the above code with -std=c++17
gives the intended output:
0 1 1 2 2 3 3 4 4 5
But compiling and running with -std=c++14
gives something unexpected:
0 -1 1 -1 2 3 3 -1 4 5
For both -std=c++17
and -std=c++14
, the intended output is produced
if the result of add_element()
is saved to a temporary variable.
#include <bits/stdc++.h>using namespace std;vector<int> res{-1};int add_element() {res.push_back(-1);return res.size() - 1; // index of added element}
The problem is that res[i] = add_element();
only works if add_element()
is
evaluated before res[i]
is. If res[i]
is evaluated first, and then
add_element()
results in the memory for res
being reallocated, then res[i]
is invalidated. The order in which res[i]
and add_element()
are evaluated is
unspecified (at least before C++17).
See this StackOverflow post for some discussion about why this is the case (here's a similar issue).
GCC Warning Options
In this section and the following one we'll go over options you can add to your
g++
compile command to aid in debugging.
Resources | ||||
---|---|---|---|---|
CF | Includes all the options below. | |||
GCC | Official documentation for the options below. |
Here are the warning options that Ben uses:
-Wall -Wextra -Wshadow -Wconversion -Wfloat-equal -Wduplicated-cond -Wlogical-op
We give examples for some of these below.
For Users of USACO Guide IDE
You can customize your compilation options in ide.usaco.guide.
-Wall
Enables many (but not all) warning options, including -Wuninitialized
and
-Wunused-variable
.
#include <bits/stdc++.h>using namespace std;int main() {int x;cout << x;}
Compile Output:
main.cpp: In function ‘int main()’: main.cpp:6:10: warning: ‘x’ is used uninitialized in this function [-Wuninitialized] 6 | cout << x; | ^
-Wextra
Enables some warning options not enabled by -Wall
, such as
-Wmissing-field-initializers
.
#include <bits/stdc++.h>using namespace std;struct s {int f, g, h;};int main() { s x = {3, 4}; }
Compile Output:
main.cpp: In function ‘int main()’: main.cpp:7:18: warning: missing initializer for member ‘s::h’ [-Wmissing-field-initializers] 7 | s x = { 3, 4 }; | ^
-Wconversion
Warns for implicit conversions that may alter a value.
#include <bits/stdc++.h>using namespace std;int main() {double x = 5.5;int y = x;cout << y;}
Compile Output:
main.cpp: In function ‘int main()’: main.cpp:6:13: warning: conversion from ‘double’ to ‘int’ may change value [-Wfloat-conversion] 6 | int y = x; | ^
-Wshadow
Resources | ||||
---|---|---|---|---|
LCPP |
#include <bits/stdc++.h>using namespace std;int x;int main() {int x = 5;cout << x;}
Compile Output:
main.cpp: In function ‘int main()’: main.cpp:7:6: warning: declaration of ‘x’ shadows a global declaration [-Wshadow] 7 | int x = 5; | ^ main.cpp:4:5: note: shadowed declaration is here 4 | int x; | ^
-Wfloat-equal
Warns if floating-point values are used in equality comparisons.
#include <bits/stdc++.h>using namespace std;int main() {double x = 1.0 / 49 * 49;cout << (x == 1.0); // 0}
Compile Output:
main.cpp: In function ‘int main()’: main.cpp:6:16: warning: comparing floating-point with ‘==’ or ‘!=’ is unsafe [-Wfloat-equal] 6 | cout << (x == 1.0); | ~~^~~~~~
GCC Debug Options
Warning!
These can slow down compilation time even runtime, so don't enable these when speed is of the essence (e.g., for Facebook Hacker Cup).
Warning!
-fsanitize
flags
don't work with MinGW. If
you're using Windows but still want to use these flags, consider using
an online compiler (or installing Linux) instead.
-fsanitize=undefined
Example: Array Out of Bounds
Without -fsanitize=undefined
, this program executes successfully and outputs
garbage:
#include <bits/stdc++.h>using namespace std;int main() {int v[5];cout << v[5] << endl; // may output an arbitrary integer}
With -fsanitize=undefined
, this program still executes successfully,
but the following runtime error is printed to standard error:
main.cpp:6:13: runtime error: index 5 out of bounds for type 'int [5]' main.cpp:6:13: runtime error: load of address 0x7ffc4efaf2d4 with insufficient space for an object of type 'int' 0x7ffc4efaf2d4: note: pointer points here 11 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0 15 40 00 00 00 00 00 00 00 00 00 00 00 00 00 ^
Example: Vector Out of Bounds
The code below produces a segmentation fault:
#include <bits/stdc++.h>using namespace std;int main() {vector<int> v;cout << v[-1] << endl;}
Output:
/tmp/program/run.sh: line 1: 71 Segmentation fault ./prog Command exited with non-zero status 139
With -fsanitize=undefined
, a slightly more informative error message is
produced:
/opt/rh/devtoolset-10/root/usr/include/c++/10/bits/stl_vector.h:1046:34: runtime error: applying non-zero offset 18446744073709551612 to null pointer /tmp/program/run.sh: line 1: 1845 Segmentation fault ./prog Command exited with non-zero status 139
Example: Integer Overflow
#include <bits/stdc++.h>using namespace std;int main() {int x = 1 << 30;cout << x + x << endl;}
With -fsanitize=undefined
, this program still executes successfully,
but the following runtime error is printed to standard error:
main.cpp:6:14: runtime error: signed integer overflow: 1073741824 * 2 cannot be represented in type 'int'
Example: Detecting Multiple Errors
By default, the undefined behavior sanitizer will attempt to continue after
detecting an error. For example, the following program with
-fsanitize=undefined
produces multiple runtime errors:
#include <bits/stdc++.h>using namespace std;int main() {cout << (1 << 32) << endl;cout << (1 << 32) << endl;cout << (1 << 32) << endl;}
Standard Error:
main.cpp:5:13: runtime error: shift exponent 32 is too large for 32-bit type 'int' main.cpp:6:13: runtime error: shift exponent 32 is too large for 32-bit type 'int' main.cpp:7:13: runtime error: shift exponent 32 is too large for 32-bit type 'int'
To disable this behavior and exit after the first detected error, we can use
-fsanitize=undefined
with -fno-sanitize-recover
.
Standard Error:
main.cpp:5:13: runtime error: shift exponent 32 is too large for 32-bit type 'int' Command exited with non-zero status 1
-fsanitize=address
Resources | ||||
---|---|---|---|---|
GCC | documentation for -g |
Example: Vector Out of Bounds
Recall that this example from the previous subsection gives a segmentation fault:
#include <bits/stdc++.h>using namespace std;int main() {vector<int> v;cout << v[-1] << endl;}
Compiling with -fsanitize=address
gives:
Standard Error
For more helpful information we should additionally compile with the -g
flag,
which generates a file containing debugging information based on the line
numbering of the program.
Standard Error
Example: Array Out of Bounds
#include <bits/stdc++.h>using namespace std;int main() {int v[5];cout << v[5] << endl;}
With -fsanitize=address -g
:
Standard Error
-D_GLIBCXX_DEBUG
This enables debug mode, which replaces each STL container with its corresponding debug container.
Resources | ||||
---|---|---|---|---|
GCC | documentation for -D_GLIBCXX_DEBUG |
Recall that the following program gives a segmentation fault.
#include <bits/stdc++.h>using namespace std;int main() {vector<int> v;cout << v[-1] << endl;}
With -D_GLIBCXX_DEBUG
the following output is produced:
Debug
Using the LLDB Debugger
Resources | ||||
---|---|---|---|---|
LLVM |
Recall the example from Checking Out of Bounds
section where the output didn't contain the number of the line where the
runtime error occurred. Below, we show how to use lldb
to output the line
number. Assume the C++ source code is named prog.cpp
and the executable
is named prog
.
- Add
-g
to the compile command and compile. - Start debug mode on
prog
usinglldb prog
. - Start running the program using
r
. - Show the stack backtrace using
bt
.
Output
As mentioned in the previous module, there are many more things you can do with debuggers, but they aren't particularly useful for competitive programming.
Module Progress:
Join the USACO Forum!
Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!