We'll now go through a typical process of building a simplified scientific computing code, where we start off with buggy code, try to compile it, and fix the various errors based on the compiler error messages and runtime errors. Note that we use GCC (GNU C Compiler), so other compilers may have rather different error messages. Nevertheless, any compiler should be informative in diagnosing the problems. Note also that some of the runtime errors would be well suited to using a debugger, such as gdb; however, learning to use a debugger is not within the scope of this workshop topic.

The code below is supposed to calculate the sum of the squares of all integers from 1 to N using two different methods, then compare the results returned by the two. The first method is a brute force approach using arrays. The second is a closed-form solution using a simple formula. If correctly implemented, both methods should produce the same result. But our initial code, which we call squares-with-bugs.c, will require some debugging to work properly.

After entering the above code into a text editor and saving it (or downloading the source code directly), it can be compiled at the command line with a compiler such as gcc. Assuming the above program is saved in a file with the suggested name "squares-with-bugs.c", we can type in the gcc command as follows (everything after the "$") to compile it into a binary file named "squares-with-bugs":

$ gcc squares-with-bugs.c -o squares-with-bugs

This results in several compile-time errors (edited for the sake of brevity):

squares-with-bugs.c: In function "main":
squares-with-bugs.c:11:15: error: expected "=", ",", ";", "asm" or "__attribute__" before "a_sum"
squares-with-bugs.c:19:5: error: "a" undeclared (first use in this function)
squares-with-bugs.c:19:5: note: each undeclared identifier is reported only once for each function it appears in
squares-with-bugs.c:24:5: error: "a_sum" undeclared (first use in this function)
squares-with-bugs.c:28:3: error: "a_sum_closed" undeclared (first use in this function)

Although it looks like a somewhat long list of errors, there is only a single-character typo causing the problem. The first error message we see tells us the C parser expected to see a symbol on line 11 that it didn"t see. Can you spot the problem?

Hopefully you detected the error: there is a comma missing between a[N] and a_sum. The most important information in the error message is the line number (immediately following the filename and ":"), which gives us a hint as to where the error occurred. Additionally, gcc gives us a column number (after the second ":") to help us pinpoint exactly where the error occurs, which can be helpful in some cases.

Having corrected this typo, you should now be able to compile the program successfully. In other words, running the compiler command again shouldn"t result in an error message, and you will typically be left with a working executable "squares-with-bugs". (If you didn"t use -o to specify an output name, the executable will usually be named a.out or a.exe; see your compiler documentation for details.) Execute this file and check the resulting output, which should be similar to the following (the first number may be different):

$ ./squares-with-bugs
sum: 338350 = closed form: 100

We observe that the brute-force solution produces vastly different results from the closed-form solution, even though they should produce identical results. Why is this? The formulas appear to be mathematically correct, when we inspect the code, so there must still be a bug somewhere (maybe more than one!). First, let"s check the result obtained by calc_sum_squares_upto(n), because it looks too small. It is very easy to check this number manually by entering the closed-form expression in some other calculator. For n = 100, you should get 338350, not 100. Therefore, we want to focus our attention on calc_sum_squares_upto(n) initially.

We can try to ask the compiler to warn us of anything that looks a bit strange, but isn"t strictly a syntax error, by introducing warning flags for gcc:

$ gcc -Wall -Wextra squares-with-bugs.c -o squares-with-bugs
squares-with-bugs.c: In function "calc_sum_squares_upto":
squares-with-bugs.c:34:18: warning: statement with no effect [-Wunused-value]
   n*(n+1)*(2*n+1)/6;
   ~~~~~~~~~~~~~~~^~
squares-with-bugs.c:35:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^

After seeing "warning: statement with no effect" and "warning: control reaches end of non-void function" in function "calc_sum_squares_upto", hopefully we will recall that the function should be returning an int, which requires us to use the "return" keyword explicitly. Note that if no return statement is present in a function, the return value is undefined by the C standard. So we change:

to

While you are learning C, and perhaps even after you have become expert at it, enabling the two gcc flags "-Wall -Wextra" (or their equivalent for another compiler) is a great thing to try if you need to figure out why your code is behaving strangely, or if you just want to catch errors early.

After fixing this second issue, compile and run again. This time you should get the correct closed-form result, 338350. But if you get that same result for the brute-force sum, you are merely lucky: there is yet another bug lurking in the code, and it too causes undefined behavior! Run the code a few more times and see if you happen to get a different sum. (With a different compiler or OS, or just with a different version of the same compiler, you may get a wrong result consistently.)

The gcc compiler can give us more help, but we need to make it take a closer look at the code. The way to do this is to enable optimization with -O. This lets us spot the potential problem:

$ gcc -O -Wall -Wextra squares-with-bugs.c -o squares-with-bugs
squares-with-bugs.c: In function "main":
squares-with-bugs.c:24:11: warning: "a_sum" may be used uninitialized in this function [-Wmaybe-uninitialized]
     a_sum = a_sum + a[i-1];
     ~~~~~~^~~~~~~~~~~~~~~~

The issue is that we have not initialized a_sum to be zero. In C, local (automatic) variables are not initialized to any particular value, so a_sum may not be 0 when it is first used. Insert the following line before the second for loop, and the behavior should now be correct:

We should now have a correct program, with the following result:

$ ./squares-with-bugs
sum: 338350 = closed form: 338350

If you had any trouble getting the right answers for this exercise, take a look at the solution file squares.c.

Bonus. If we replace a[i-1] by a[i] in both of the loops and leave everything else unchanged, we are actually exceeding the array"s bounds in C, since the maximum index of a[N] (where N == 100) is 99. Enabling compiler warnings won"t necessarily help here because this is allowed in C, though it may cause a runtime error. Sometimes, nothing bad will happen, but the behavior is not guaranteed. Feel free to try this to see if you get an error. But note that just because you don"t get a runtime error is not a reason to leave such a programming error in the code.

 
©   Cornell University  |  Center for Advanced Computing  |  Copyright Statement  |  Inclusivity Statement