Writing unorganised code for something to work is an easy task, just put anything anywhere and see if the result is coming or not. If the result comes out in first attempt then you are really lucky but normal people like me are not. Then what we should do to check where the problem is occuring, we put some print statements (or console.log) to check the result in various places and observe where the output is deviating from its path. Well, it itself is a good practise of debugging the code but it does not work always especially not with large projects. So what should we do then ? According to me, we should give debugger a chance to find the bugs in our program. Debugging is an important part of writing programs because it teaches you to write unit tests for the parts of the program where it can fail.
To perform debugging, we need a debugger and thankfully we have one, gdb (GNU debugger). In this article we will start by seeing some basic debugging techniques which involves logging the output and assertions. Then in the next article, we'll use gdb which will do the heavy lifting for us and will tell us what is going in our program internally. So the topics we'll be covering in this article will be :-
Method of debugging and what to avoid
The basic approach to debugging our program follows the sequence of following steps :
- Know what your program is supposed to do.
- Detect when it doesn’t.
- Fix it.
Basic debugging
Usually there are programs which consist only 50-100 lines of code and 2-3 functions then there basic debugging techniques can be applied to find the bug easily. If basic debugging doesn't help you then move straight to use gdb instead of applying these techniques too much. The 2 basic methods of basic debugging are:
- Logging output to console
- Assertions
Output Logging
Output logging basically means printing the output our program generates bewteen various parts. In C, we use printf
function to print out the variables and we can see their state in between our program lifecycle. But priting the variables using printf has some disadvantages. Some of these disadvantages are:
- We can't change the output without editing the code.
- A lot of print statements can create mess and we'll get confused which part is generating which output.
- The output can be misleading: in particular, printf output is usually buffered, which means that if your program dies suddenly there may be output still in the buffer that is never flushed to stdout. This can be very confusing, and can lead you to believe that your program fails earlier than it actually does
- Use
fprintf
: Instead of printf statement, usefprintf(stderr, ...)
. It will keep your regular output seperate from debugging output. - Flush the output: If you still want to use
printf
then callfflush(stdout)
to prevent buffering problem. - Use
#ifdef
: Wrap your code inside#ifdef
It will allow you to turn your debugging code on/off according to your needs.
#include<stdio.h> #define DEBUG void qsort(int *arr, int l, int r) { int i ; if(r>l) { int index = partition(arr, l, r ) ; #ifdef DEBUG for(i=0 ; i<5 ; i++){ printf("%d ", arr[i]) ; } printf("\n") ; fflush(stdout) ; #endif qsort(arr, l, index) ; qsort(arr, index, r) ; } } int partition(int *arr, int l, int r) { int i=l, j=l ; int pivot = arr[r-1] ; while(j<r) { if(arr[j]<pivot) { int temp = arr[i] ; arr[i++] = arr[j] ; arr[j] = temp ; } j++ ; } int temp = arr[r-1] ; arr[r-1] = arr[i] ; arr[i] = temp ; return i ; } int main(){ int i, arr[5] ; for(i=0 ; i<5 ; i++){ scanf("%d", &arr[i]) ; } qsort(arr, 0, 5) ; for(i=0 ; i<5 ; i++){ printf("%d ", arr[i]) ; } return 0 ; }This is a simple quicksort function but there is some error in it. To debug it, i have added a loop which prints my array after each partition in the
qsort
function. Also i have wrapped that piece of code inside a #ifdef
block. I have switched on the debugging by defining DEBUG using #define DEBUG
. You can comment that line if you want to turn off debugging(which means the code inside #ifdef
block won't execute). Now if you try to run it then you will infinite array prints. Also those arrays are all sorted. So it suggests that our code is working fine but it is not stopping after sorting the array. What might be the reason for endless recursion. One can think may be our recursion condition is not right. And that is the bug in our program. If you change the recursion condition if(r>l>
to if(r>l+1)
, you program will start behaving correctly. Now you can turn the debugging off by commenting #define debug
.
Assertions
We just saw the ouput logging technique and how to use it smartly and carefully. Now lets see another technique of basic debugging. In this technique we'll use C assert
library. Every non-trivial C program should include <assert.h>
, which gives you the assert macro. The assert macro tests if a condition is true and halts your program with an error message if it isn’t. A basic example could be:
#include<stdio.h> #include<assert.h> int main(){ assert(2+2 == 5) ; return 0 ; }When you will run the above program, the assertion will fail and you will see an error message. Assertions are useful because you won't have to change your code to get more debugging output. Assertions can be used to verify assumptions made by the program and print a diagnostic message if this assumption is false.
So far we have seen how can we debug our sweet simple programs but when our programs grow in size, they become more nasty. And to handle those, we'll be needing a more powerful tool to help us. In the next article, we'll see that mighty tool (gdb) and will see how to use it, when to use it and other tools to use along with gdb. If you have any query then drop a comment below and i'll be willing to help you out.
Comments
Post a Comment
Comment on articles for more info.