Debugging (Language-Specific)
Authors: Benjamin Qi, Aaron Chew, Aryansh Shrivastava
Identifying errors within your program and how to avoid them in the first place.
Resources | |||
---|---|---|---|
AryanshS | some parts taken from here | ||
LCPP | adding print statements |
Style
Resources | |||
---|---|---|---|
CF | don't agree with everything but important to read nonetheless |
It helps to write readable code ...
Printing Variables
Basic Print Statements
The most basic way that you might debug is adding a print statement. This is great and serves the purpose for the most part. For instance, we can write the below to check the value of x
at a point in our code.
1#include <iostream>2using namespace std;34int x = 10; // some important variable56void dbg() { cout << "x is " << x << "\n"; }78int main() {9 dbg();10 x = 5000;
Such print statements are great on a basic level, and we can comment or define them out of our main code when we need to compile and execute a more final version of our code.
However, as great as print statements are, they are annoying to work with and efficiently separate from the actual parts of our code. This is important for example when we want an online judge (OJ) to read our output.
Standard Error Stream
The standard error stream (cerr
in C++) is a quick fix to this. Instead of printing in standard iostream, we actually generate a whole new stream of data called the error stream. Simply replace all instances of cout
with cerr
. For example:
1void dbg() { cerr << "x is " << x << "\n"; }23int main() {4 dbg();5 x = 5000;6 dbg();7}
Try running this program and you might be confused about the difference. You will be able to see the output of cerr
right next to regular cout
outputs. But this is the beauty of it! And if we use freopen to open up file pipes or submit this to an OJ, the program will not include the error stream.
Warning!
Printing too much content to stderr
can still cause TLE when submitting to an OJ.
Debug Template
This section is not complete.
mention line # macros
Although not feasible if you have to write all code from scratch, this template is very helpful for simplifying input / output / debug output. Note that dbg()
only produces debug output when -DLOCAL
is included as part of the compilation command, so you don't need to comment out those lines before submitting.
Warning!
You are not allowed to use pre-written code for USACO contests, so dbg()
should only be used for other online contests.
This section is not complete.
examples for all of input, output, debug?
Stress Testing
If your code is getting WA, one option is to run your buggy code against another that you're relatively confident is correct on randomly generated data until you find a difference. See the video for details.
Resources | |||
---|---|---|---|
Errichto | using a script to stress test | ||
Errichto | some parts from above video | ||
Benq | script from Errichto's video |
Script that was mentioned in video:
# A and B are executables you want to compare, gen takes int # as command line arg. Usage: 'sh stress.sh' for((i = 1; ; ++i)); do # if they are same then will loop forever echo $i ./gen $i > int ./A < int > out1 ./B < int > out2 diff -w out1 out2 || break # diff -w <(./A < int) <(./B < int) || break done
We can modify this to work for other situations. For example, if you have the input and output files (ex. 1.in
, 1.out
, 2.in
, 2.out
, ..., 10.out
for old USACO problems) then you can use the following:
# A is executable you want to test for((i = 1; i <= 10; ++i)); do echo $i ./A < $i.in > out diff -w out $i.out || break done echo "ALL TESTS PASSED"
The following will break on the first input file such that the produced output file is empty.
for((i = 1; ; ++i)); do echo $i ./gen $i > int ./A < int > out if ! [[ -s "out" ]] ; then echo "no output" break fi ; done
If you want to learn how to write these scripts yourself, you can check here (but take this advice with a grain of salt since I'm not so proficient myself ...).
Warning!
This won't work if you're using Windows. Instead, you can use what tourist does:
:: save this in test.bat @echo off gen > in your_sol out correct_sol correct_out fc out correct_out if errorlevel 1 exit test
This section is not complete.
scripts for multiple languages? can this be modified to break on an assertion failure?
C++
Assertions & Warnings
Resources | |||
---|---|---|---|
LCpp | includes static_assert and #define NDEBUG | ||
GFG | subset of above | ||
GCC | #warning, #error |
GCC Compilation Options
Resources | |||
---|---|---|---|
CF | includes all the options mentioned below |
You can also check what options Errichto and ecnerwala use.
Warning Options
Resources | |||
---|---|---|---|
GCC | documentation for options below |
Such as the following:
-Wall -Wextra -Wshadow -Wconversion -Wfloat-equal -Wduplicated-cond -Wlogical-op
You can check the CF blog above for descriptions of these.
-Wextra
Some extra warning flags that are not enabled by
-Wall
Doesn't seem so useful in a contest setting? Except maybe the following:
-Wunused-parameter -Wunused-but-set-parameter
-Wshadow
Resources | |||
---|---|---|---|
LCPP |
Avoid variable shadowing!
Other Options
Let's give some examples of what each of these do.
Warning!
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
Resources | |||
---|---|---|---|
GCC | documentation for -fsanitize |
The following prog.cpp
gives a segmentation fault.
1#include <bits/stdc++.h>2using namespace std;34int main() {5 vector<int> v;6 cout << v[-1];7}
g++ prog.cpp -o prog -fsanitize=undefined && ./prog
produces:
/usr/local/Cellar/gcc/9.2.0_1/include/c++/9.2.0/bits/stl_vector.h:1043:34: runtime error: pointer index expression with base 0x000000000000 overflowed to 0xfffffffffffffffc zsh: segmentation fault ./prog
Another example with prog.cpp
as the following:
1#include <bits/stdc++.h>2using namespace std;34int main() {5 int v[5];6 cout << v[5];7}
g++ prog.cpp -o prog -fsanitize=undefined && ./prog
produces:
prog.cpp:6:13: runtime error: index 5 out of bounds for type 'int [5]' prog.cpp:6:13: runtime error: load of address 0x7ffee0a77a94 with insufficient space for an object of type 'int' 0x7ffee0a77a94: note: pointer points here b0 7a a7 e0 fe 7f 00 00 25 b0 a5 0f 01 00 00 00 b0 7a a7 e0 fe 7f 00 00 c9 8c 20 72 ff 7f 00 00 ^
-fsanitize=undefined
also catches integer overflow. Let prog.cpp
be the following:
1#include <bits/stdc++.h>2using namespace std;34int main() {5 int x = 1<<30;6 cout << x+x;7}
g++ prog.cpp -o prog -fsanitize=undefined && ./prog
produces:
prog.cpp:6:15: runtime error: signed integer overflow: 1073741824 * 2 cannot be represented in type 'int'
We can also use -fsanitize=undefined
with -fsanitize-recover
. Error recovery for -fsanitize=undefined
is turned on by default, but
The
-fno-sanitize-recover=
option can be used to alter this behavior: only the first detected error is reported and program then exits with a non-zero exit code.
So if prog.cpp
is as follows:
1#include <bits/stdc++.h>2using namespace std;34int main() {5 cout << (1<<32) << endl;6 cout << (1<<32) << endl;7 cout << (1<<32) << endl;8}
then
g++ -fsanitize=undefined prog.cpp -o prog && ./prog
produces:
prog.cpp: In function 'int main()': prog.cpp:5:12: warning: left shift count >= width of type [-Wshift-count-overflow] 5 | cout << (1<<32) << endl; | ~^~~~ prog.cpp:6:12: warning: left shift count >= width of type [-Wshift-count-overflow] 6 | cout << (1<<32) << endl; | ~^~~~ prog.cpp:7:12: warning: left shift count >= width of type [-Wshift-count-overflow] 7 | cout << (1<<32) << endl; | ~^~~~ prog.cpp:5:12: runtime error: shift exponent 32 is too large for 32-bit type 'int' 0 prog.cpp:6:12: runtime error: shift exponent 32 is too large for 32-bit type 'int' 0 prog.cpp:7:12: runtime error: shift exponent 32 is too large for 32-bit type 'int' 0
while
g++ -fsanitize=undefined -fno-sanitize-recover prog.cpp -o prog && ./prog
produces:
prog.cpp: In function 'int main()': prog.cpp:5:12: warning: left shift count >= width of type [-Wshift-count-overflow] 5 | cout << (1<<32) << endl; | ~^~~~ prog.cpp:6:12: warning: left shift count >= width of type [-Wshift-count-overflow] 6 | cout << (1<<32) << endl; | ~^~~~ prog.cpp:7:12: warning: left shift count >= width of type [-Wshift-count-overflow] 7 | cout << (1<<32) << endl; | ~^~~~ prog.cpp:5:12: runtime error: shift exponent 32 is too large for 32-bit type 'int' zsh: abort ./prog
-fsanitize=address -g
Warning!
According to this issue, AddressSanitizer does not appear to be available for MinGW.
Resources | |||
---|---|---|---|
GCC | documentation for -g, -ggdb |
The following prog.cpp
gives a segmentation fault.
1#include <bits/stdc++.h>2using namespace std;34int main() {5 vector<int> v;6 cout << v[-1];7}
g++ prog.cpp -o prog -fsanitize=address && ./prog
produces:
AddressSanitizer
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. -fsanitize=address
can then access the file at runtime and give meaningful errors. This is great because it helps diagnose (or "sanitize" if you will) errors such as out of bounds, exceptions, and segmentation faults, even indicating precise line numbers. Feel free to delete the debug file after the run of course.
AddressSanitizer with -g
Another example with prog.cpp
as the following:
1#include <bits/stdc++.h>2using namespace std;34int main() {5 int v[5];6 cout << v[5];7}
g++ prog.cpp -o prog -fsanitize=address -g && ./prog
produces:
AddressSanitizer with -g
-D_GLIBCXX_DEBUG
Resources | |||
---|---|---|---|
GCC | documentation for -D_GLIBCXX_DEBUG |
The following prog.cpp
gives a segmentation fault.
1#include <bits/stdc++.h>2using namespace std;34int main() {5 vector<int> v;6 cout << v[-1];7}
g++ prog.cpp -o prog -D_GLIBCXX_DEBUG && ./prog
produces:
Debug
Warning!
I don't know if there's a way to print the line number where the error occurs.
Debuggers
Resources | |||
---|---|---|---|
LCPP |
Using a debugger varies from language to language and even IDE to different IDE. For now I will describe the basic operations of a debugger.
A debugger allows you to pause a code in its execution and see the values as a given point in the debugger.
To do this, set a "breakpoint" at a certain line of code. When the code runs to that breakpoint, it will pause and you will be able to inspect all the different variables at that certain instance.
There are two more useful and common operations. Once you are at the breakpoint, you may want to see what happens after the current line is executed. This would be the "Step Over" button that will allow you to move to the next line. Say you are at a line with the following code: dfs(0,-1)
, if you click "step over" the debugger will ignore showing you what happens in this function and go to the next line. If you click "step in," however, you will enter the function and be able to step through that function.
In essense, a debugger is a tool to "trace code" for you. It is not much different from just printing the values out at various points in your program.
Pros of using a debugger:
- No need to write print statements so you save time
- You can step through the code in real time
Cons of using a debugger:
- You cannot see the overall "output" of your program at each stage. For example, if I wanted to see every single value of
i
in the program, I could not using a debugger. - Most advanced competitive programmers do not use debuggers; it is usually not very efficient to use one during a contest.
This section is not complete.
How to use gdb, valgrind for C++?
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!