Wednesday 26 November 2014

Debugging code in a competitive setting, Part 1


Writing code in a competitive setting is an especially tiring and frustrating task, for a multitude of reasons. The rush of wanting to code fast prevents one from applying good coding standards and leads to trivial errors. Also, there is no time to set up a testing framework etc. How do you prevent (or at least find and correct) any errors in the code you write?

What I personally do is to use a combination of gdb and a custom debug message function.

The first part of this series of posts will explain the function

First, I always put the following code at the start of my programs:

#ifdef DEBUGIT
  #define DEBUG(X) cerr << ">>> DEBUG(" << __LINE__ << ") " << #X << " = " << X << endl
#else
  #define DEBUG(X) (void)0
#endif

Now, whenever there is a need to check the value of a variable (or expression), all that is left to be done is to call the DEBUG() function like so:

DEBUG(x); // debugging variable x
DEBUG((a<<3)+2); // debugging an expression

The way that the "function" (or macro, to be precise) is defined, whenever DEBUGIT is defined at compilation, debug statements are shown, and otherwise, they are just skipped.

Hence, whenever you want to look at the debug statements, use g++ -DDEBUGIT to compile instead of just g++.

Here's an example program and its output:

#include <iostream>

using namespace std;

#ifdef DEBUGIT
  #define DEBUG(X) cerr << ">>> DEBUG(" << __LINE__ << ") " << #X << " = " << X << endl
#else
  #define DEBUG(X) (void)0
#endif

int main() {
  int x = 5, y = 10;
  DEBUG(x);
  DEBUG(y);
  ++x += y++;
  DEBUG(y+1);
  cout << x << endl;
}

Output:

>>> DEBUG(13) x = 5
>>> DEBUG(14) y = 10
>>> DEBUG(16) y+1 = 12
16

The advantage with the DEBUGIT technique is that you can directly submit code (without any modifications) and the grader will not run the DEBUG statements at all.

I have added the following line to my .bashrc file:

alias d++='g++ -DDEBUGIT'

Hence, I can directly run d++ for compiling with the debug statements.

In my next post, I will explain how to use gdb to further make life easier.

2 comments:

  1. I think you forgot to post about gdb

    ReplyDelete
  2. Yeah, I no longer competitively program as much and completely forgot about writing a part 2 :D

    Here's a quick short trick, just to get some idea of how useful gdb can be in investigating where a certain program crashed:

    1. Compile your program using the -g option (i.e. if you were compiling using 'g++ test.c', then you say 'g++ -g test.c' instead)
    2. Run 'ulimit -c unlimited' in your terminal before executing your code.
    3. Give it input that makes it crash.
    4. Run 'gdb a.out core' (or whatever your program was named after compilation, and core-SOMETHING if your system generates those kind of core files)
    5. Run 'backtrace' inside gdb to know what functions were called and how.
    6. ?
    7. Profit!

    Note: The question mark above refers to using logic to figure out what you can do about the crash. In the field I work in currently, that question mark would be to figure out if I can force the crash into gaining complete control over the system. But maybe that's for another day :D

    Cheers!

    ReplyDelete

Note: only a member of this blog may post a comment.