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

Автор ramchandra, 4 года назад, По-английски

In this tutorial I'm going to share some useful compilation and debugging tips. These tips can save you a ton of time debugging and make compilation and debugging convenient.

Useful compilation flags

G++ has many useful warning flags such as

  • -Wall enables all warnings. I highly recommend using this flags.
  • -Wextra enables extra warnings.
  • -Wno-sign-conversion silences sign conversion warnings for code like x < vec.size()
  • -Wshadow enables shadowing warnings, so that if you define another variable with the same name in a local scope, you will get a warning.

Using these flags can save you time debugging silly mistakes like forgetting to return a value in a function or doing a comparison like x < vec.size()-1 where vec.size()-1 can underflow into SIZE_MAX-1.

Precompiled headers

You can use precompiled headers to substantially speed up the compilation of your code. Note that the precompiled header is only used if it is compiled with the same flags that your code is compiled with. You can store the precompiled headers in the folder bits/stdc++.h.gch and G++ will automatically find the correct precompiled header (with the correct flags). You can store multiple precompiled headers for bits/stdc++.h for different sets of flags.

sudo g++ -Wall -Wextra -Wshadow -D_GLIBCXX_ASSERTIONS -DDEBUG -ggdb3 -fmax-errors=2 -std=c++17 -o /usr/include/x86_64-linux-gnu/c++/9/bits/stdc++.h{.gch/ASSERT_DEBUG_17.gch,}

Useful debugging flags

Debugging is an important part of CP. After all, even tourist gets RTE on his submissions occasionally.

  • -g enables basic debug information
  • -ggdb3 enables extended debug information such as macros. I suggest using this generally. It slows down compilation a little bit, but has no effect on run-time, since it just adds an extra piece of data in the executable that helps the debugger map the executable file position to line numbers and other related information.
  • -D_GLIBCXX_DEBUG enables out of bounds checking and checks that your comparison operator is irreflexive (comp(a, a) should return false), and many other useful things. However, this can slow down your code runtime (and compile time) substantially.

  • -D_GLIBCXX_ASSERTIONS enables light-weight debug checks such as out of bounds checking. I suggest always using this when compiling locally, as it has negligible runtime and compile-time performance impact.

  • -fmax-errors=<n> limits the number of errors displayed to n. This stops the compiler from printing an excessive amount of error messages.

  • -DDEBUG is used in my debug macro (see below) to enable debugging. Add this to your compilation flags locally to enable debugging. Since this macro is not defined in CodeForces' compilation options, when your code is judged debugging will be disabled.

The _GLIBCXX_* macros are documented in the libstdc++ documentation.

Note that the names of these macros start with an underscore _. The flag -D<macro> defines the macro macro, so you can also use these macros by defining them in the first line before you include any headers.

Example:
#ifdef DEBUG
#define _GLIBCXX_DEBUG
#endif
#include <bits/stdc++.h>
How to use these flags

Add the compilation flag -D_GLIBCXX_DEBUG to define the macro, or likewise -D_GLIBCXX_ASSERTIONS.

Recommended compiler flags

-Wall -Wextra -Wshadow -D_GLIBCXX_ASSERTIONS -DDEBUG -ggdb3 -fmax-errors=2

AddressSanitizer and UndefinedBehaviorSanitizer

The _GLIBCXX_* macros only do debug checks for STL containers and algorithms. So you should use std::array instead of C arrays. For checking for C array out of bounds, use AddressSanitizer with -fsanitize=address. For checking undefined behavior like arithmetic overflow etc use -fsanitize=undefined. You can use them both with -fsanitize=address,undefined.

When upsolving a problem, if you solution does not AC, CodeForces runs sanitizers on it. So you can check the output to see if the sanitizers caught any error.

Debug macro

A useful tool is to have a debug macro that can print useful information like the line number, variable name, etc. Remember to add -DDEBUG to your local compilation flags to enable this debug macro.

#include <bits/stdc++.h>
using namespace std;
// === Debug macro starts here ===

int recur_depth = 0;
#ifdef DEBUG
#define dbg(x) {++recur_depth; auto x_=x; --recur_depth; cerr<<string(recur_depth, '\t')<<"\e[91m"<<__func__<<":"<<__LINE__<<"\t"<<#x<<" = "<<x_<<"\e[39m"<<endl;}
#else
#define dbg(x)
#endif
template<typename Ostream, typename Cont>
typename enable_if<is_same<Ostream,ostream>::value, Ostream&>::type operator<<(Ostream& os,  const Cont& v){
	os<<"[";
	for(auto& x:v){os<<x<<", ";}
	return os<<"]";
}
template<typename Ostream, typename ...Ts>
Ostream& operator<<(Ostream& os,  const pair<Ts...>& p){
	return os<<"{"<<p.first<<", "<<p.second<<"}";
}

// === Debug macro ends here ===

int func(vector<pair<int, string>>& vec){
    dbg(vec);
    return 42;
}
int main(){
    vector<pair<int, string>> vec{{1, "code"}, {2, "forces"}};
    dbg(func(vec));
}

Debug output

Debug output

Note that the debug output is indented based on the recursion level. Also, the output is in red to aid in differentiating debug output from regular output.

Nifty isn't it?

How does the debug macro work?

(you may skip these technical details)

The Ostream template parameter is used so that this operator<< overload has low priority. The operator<< template is also constrained by enable_if so that it does not get called when std::bitset::operator<< is intended to be called.

Debugging using gdb


To debug using gdb, this bash command is useful:

(echo "run < a.in" && cat) | gdb -q a

This automatically runs a with input from a.in.

Quick gdb tutorial

  • start starts your code, pausing execution at main()
  • run runs your code until a breakpoint is hit
  • break <function> pauses execution when a function is called
  • Use up and down to move up and down the stack.
  • jump <line_no> can be used to jump to a line number.
  • continue can be used to continue execution
  • next goes to the next line in the current function
  • step steps into the next line executed (so if you call a function it enters the function's code)
  • You can type abbreviations of these commands, such as r for run.

Adding these flags and commands to ~/.bashrc

If you use the terminal to compile, you can add these functions to your .bashrc file in your home folder (e.g. C:\Users\my_username on Windows). This shell script in this file is executed by bash when you open the terminal. Note that you need to restart your terminal or enter . .bashrc for the changes to take effect.

function compile {
    g++ -Wall -Wextra -Wshadow -D_GLIBCXX_ASSERTIONS -DDEBUG -ggdb3 -fmax-errors=2 -o $1{,.cpp}
}
function debug {
    (echo "run < $1.in" && cat) | gdb -q $1
}

Using these bash functions:

compile a # Compiles a.cpp to a
debug a # Runs a in the debugger with input from a.in

I hope you found this tutorial useful. Feel free to ask any questions or give feedback.

EDIT: Added section on precompiled headers.

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

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

Thank you for the nice article.
Btw does compile function assumes that our .cpp file exists in the same directory as .bashrc file(it seems so to me), and if it is then how to make it work such that .cpp file can be anywhere?

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

    The compile function can be used for compiling files outside the home directory. You just need to pass the file path without the .cpp extension.

    Example: compile my_contest/my_program

    This will compile my_contest/my_program.cpp and produce an executable my_contest/my_program

    The compile function substitutes the 1st argument given for $1 in the function's code.

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

      If this is the way, then instead of passing the directory manually the better thing to use would be:
      compile $(pwd)/fie_name.

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

        You don't need to do that. compile file_name will compile file_name.cpp from the current directory.

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

          It wasn't working for me then that's why i asked that question in my first comment but now it does(idk what was the problem) btw thanks.

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

I would recommend using start <$1.in instead of run <$1.in. Otherwise, the program will start running automatically and it might skip the important things you wanted to examine and need to restart it.

You can replace the echo, cat and pipe trick. The same thing can be done with gdb -q $1 -ex "run <$1.in".

Other than that, I can recommend the option gdb --tui. It splits the gdb window into two parts, the normal interactive prompt for the commands, and a code window that shows the current position in the code (this avoids running l over and over and helps orientation a lot).

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

codeforces supports C++17, why not to use it?

template<typename Ostream, typename Cont>
enable_if_t<is_same_v<Ostream,ostream>, Ostream&> operator<<(Ostream& os, const Cont& v){
	os<<"[";
	for(auto& x:v){os<<x<<", ";}
	return os<<"]";
}
  • »
    »
    4 года назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится

    Yes, you could make it a bit more compact with C++17, but I made the code work on C++11 so that it can be used on other judges.

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

      You could put these definitions inside the #ifdef DEBUG and cpp version of the server won't matter.

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

Really informative! Thanks for the blog!

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

How can I detect a Runtime Error quickly? Using a GDB User Interface really helps but it's a little inconvenient to me (I am using Sublime text).

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

    When you run your code outside the debugger an error message saying you indexed out of bounds will be displayed.

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

Hi, really nice blog. One small note:

typename enable_if<is_same<Ostream,ostream>::value, Ostream&>::type
// is equivalent to 
enable_if_t<is_same<Ostream, ostream>, Ostream &>

And after all, why don't to use ostream &?

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

-Wno-sign-conversion silences sign conversion warnings for code like x < vec.size()

Why should we use this flag if it silences the warnings?

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

    The warnings are sometimes useful in cases like -1 < v.size(), but usually they create excessive warnings (which are distracting), unless you have a macro/function which returns the signed size.

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

i've found valgrind to typically be more useful to me in a contest setting that gdb, since you don't need to set up breakpoints or anything.

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

Could you give me a suggestion? I wonder that in competitive programming debugging with gdb will improve efficiency or not? Now I just use IDE such as Dev Cpp, print something for debugging. Is necessary for me to learn gdb and use it in debugging for competitive programming? If you could gvie me advice, I would so grateful.

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

Nice blogpost! To make code look cleaner, you could also store the debug macros and functions in a separate "debug.hpp" file and link it in your code skeleton/template.

#ifdef DEBUG
    #include </home/path/to/debug.hpp>
#else
    #define dbg(x) 42  // Or anything!
#endif