One of the classic debugging approaches is print-based debugging. Strategically-placed print statements let you see what is happening in your program at “critical” points in time and can help you pinpoint where your program is going wrong.
Below, we will take a look at some examples of print-based debugging helping us locate errors.
#include <stdio.h>
int main(void)
{
int array1[10];
int array2[10];
int n = 10;
// Initialize all elements of array1 to 5
// and all elements of array2 to 10.
for (int i = 1; i <= n; i++)
{
[i] = 5;
array1[i] = 10;
array2}
return 0;
}
*** stack smashing detected ***: terminated
Aborted
“Stack smashing detected”? Where did that come from? Hmm, maybe I’ll add in a few print statements to see if I can track the problem down.
#include <stdio.h>
int main(void)
{
int array1[10];
int array2[10];
int n = 10;
// Initialize all elements of array1 to 5
// and all elements of array2 to 10.
for (int i = 1; i <= n; i++)
{
[i] = 10;
array2[i] = 5;
array1}
// Print all the array values.
for (int i = 1; i <= n; i++)
{
("array1[%d] = %d\n", i, array1[i]);
printf}
("\n");
printf
for (int i = 1; i <= n; i++)
{
("array2[%d] = %d\n", i, array2[i]);
printf}
return 0;
}
array1[1] = 5
array1[2] = 5
array1[3] = 5
array1[4] = 5
array1[5] = 5
array1[6] = 5
array1[7] = 5
array1[8] = 5
array1[9] = 5
array1[10] = 5
array2[1] = 10
array2[2] = 10
array2[3] = 10
array2[4] = 10
array2[5] = 10
array2[6] = 10
array2[7] = 10
array2[8] = 10
array2[9] = 10
array2[10] = 10
*** stack smashing detected ***: terminated
Aborted
Wait a minute, shouldn’t the arrays be starting at index 0? Oh, now I see what’s going on.
The problem here is due to an off-by-one error. While we did declare arrays of length 10, arrays are zero-indexed, meaning the initial element is found at index 0. The final loop gave us an error because we read past the bounds of the array, trying to access memory that doesn’t belong to the array.
We can fix this by setting all of our loops to start at 0 and run while they are less than 10 (i.e., we only access indexes 0-9).
#include <stdio.h>
int main(void)
{
int array1[10];
int array2[10];
int n = 10;
// Initialize all elements of array1 to 5
// and all elements of array2 to 10.
for (int i = 0; i < n; i++)
{
[i] = 5;
array1[i] = 10;
array2}
// Print all the array values.
for (int i = 0; i < n; i++)
{
("array1[%d] = %d\n", i, array1[i]);
printf}
("\n");
printf
for (int i = 0; i < n; i++)
{
("array2[%d] = %d\n", i, array2[i]);
printf}
return 0;
}
Running that code, we see that the error has gone away.
array1[0] = 5
array1[1] = 5
array1[2] = 5
array1[3] = 5
array1[4] = 5
array1[5] = 5
array1[6] = 5
array1[7] = 5
array1[8] = 5
array1[9] = 5
array2[0] = 10
array2[1] = 10
array2[2] = 10
array2[3] = 10
array2[4] = 10
array2[5] = 10
array2[6] = 10
array2[7] = 10
array2[8] = 10
array2[9] = 10
One important thing to note: the stack smashing error occurred at the
end of the function. The lesson here is that off-by-one errors
won’t always cause issues immediately, so your program could fail
unexpectedly long after a mistake was made. Be careful with the values
you use as your start and end arguments, and check whether or not you
want to reach your end argument (e.g., <=
,
>=
vs. <
, >
).
#include <stdio.h>
#include <string.h>
int main(void)
{
int i, len;
char name[100] = "banana";
= strlen(name);
len
// This should print the name as a grid with 5 rows like:
// b a n a n a
// b a n a n a
// b a n a n a
// b a n a n a
// b a n a n a
for (i = 0; i < 5; i++)
{
for (i = 0; i < len; i++)
{
("%c ", name[i]);
printf}
("\n");
printf}
return 0;
}
b a n a n a
Hey, what gives? I wanted five bananas, not one! Maybe if I take a
look at i
, I’ll find a clue.
#include <stdio.h>
#include <string.h>
int main(void)
{
int i, len;
char name[100] = "banana";
= strlen(name);
len
// This should print the name as a grid with 5 rows like:
// b a n a n a
// b a n a n a
// b a n a n a
// b a n a n a
// b a n a n a
for (i = 0; i < 5; i++)
{
for (i = 0; i < len; i++)
{
("%c ", name[i]);
printf}
("\n");
printf("i = %d\n", i);
printf}
return 0;
}
b a n a n a
i = 6
i = 6
? How did it go from being 0 before the inner loop
to 6 afterward? i
should still be equal to 0. Wait a
minute–I’m using i
to control both loops. That
means when the inner loop ends, i
is at 6, causing the
condition for the outer loop, i < 5
, to evaluate to
false and end the loop.
If we want to see all five bananas, we need to use a separate
variable for the inner loop. Here, we’ll use a variable j
as its counter.
#include <stdio.h>
#include <string.h>
int main(void)
{
int i, j, len;
char name[100] = "banana";
= strlen(name);
len
// This should print the name as a grid with 5 rows like:
// b a n a n a
// b a n a n a
// b a n a n a
// b a n a n a
// b a n a n a
for (i = 0; i < 5; i++)
{
for (j = 0; j < len; j++)
{
("%c ", name[j]);
printf}
("\n");
printf}
return 0;
}
If we run that, we get our expected result:
b a n a n a
b a n a n a
b a n a n a
b a n a n a
b a n a n a
#include <stdio.h>
int main(void)
{
double change = 0.00;
int dollars = 0, quarters = 0, dimes = 0, nickels = 0, pennies = 0;
("Enter the amount of change: ");
printf("%lf", &change);
scanf
while (change != 0)
{
if (change >= 1)
{
++;
dollars-= 1;
change }
else if (change >= 0.25)
{
++;
quarters-= 0.25;
change }
else if (change >= 0.10)
{
++;
dimes-= 0.10;
change }
else if (change >= 0.05)
{
++;
nickels-= 0.05;
change }
else if (change >= 0.01)
{
++;
pennies-= 0.01;
change }
}
("%d Dollars\n", dollars);
printf("%d Quarters\n", quarters);
printf("%d Dimes\n", dimes);
printf("%d Nickels\n", nickels);
printf("%d Pennies\n", pennies);
printf
return 0;
}
Enter the amount of change: 4.39
Why isn’t my program printing anything? Maybe something is going wrong inside of the loop? Let me put in a few print statements toward the end of the loop and see what they show.
In the pennies
check, I’ll print two decimal places to
see how many cents I have left, then at the end of the loop, I’ll print
20 decimals places. It’s more than I need, but maybe it will show me
something I’m missing.
#include <stdio.h>
int main(void)
{
double change = 0.00;
int dollars = 0, quarters = 0, dimes = 0, nickels = 0, pennies = 0;
("Enter the amount of change: ");
printf("%lf", &change);
scanf
while (change != 0)
{
if (change >= 1)
{
++;
dollars-= 1;
change }
else if (change >= 0.25)
{
++;
quarters-= 0.25;
change }
else if (change >= 0.10)
{
++;
dimes-= 0.10;
change }
else if (change >= 0.05)
{
++;
nickels-= 0.05;
change }
else if (change >= 0.01)
{
("pennies = %d\n", pennies);
printf("change = %0.2lf\n", change);
printf++;
pennies-= 0.01;
change }
("change = %0.20lf\n", change);
printf}
("%d Dollars\n", dollars);
printf("%d Quarters\n", quarters);
printf("%d Dimes\n", dimes);
printf("%d Nickels\n", nickels);
printf("%d Pennies\n", pennies);
printf
return 0;
}
Enter the amount of change: 4.39
change = 3.38999999999999968026
change = 2.38999999999999968026
change = 1.38999999999999968026
change = 0.38999999999999968026
change = 0.13999999999999968026
change = 0.03999999999999967470
pennies = 0
change = 0.04
change = 0.02999999999999967276
pennies = 1
change = 0.03
change = 0.01999999999999967082
pennies = 2
change = 0.02
change = 0.00999999999999967061
change = 0.00999999999999967061
change = 0.00999999999999967061
change = 0.00999999999999967061
change = 0.00999999999999967061
...
Okay, so it is going inside of the pennies
statement, but it eventually says there’s less than one cent
left over? How is that possible?
Now would be a good time to discuss floating-point numbers. Floating-point numbers, also known as “real numbers”, are numbers that have a fractional portion (e.g., 3.14, 2.37, 1.33, 4.5). Computers represent floating-point numbers in binary, but the representation is not exact.
Just as integers are represented by combining powers of two (e.g., , , , etc.), the fractional portion of numbers are represented by combining the reciprocals of powers of two, starting with (e.g., , , , etc.).
For example, 13 is 1101 in binary. In other words, it is equal to the sum of , , and . 0.75 is represented by 0.11 in binary and is the sum of and .
However, not all floating-point values can be represented exactly;
most are only approximations. As such, comparisons and arithmetic
involving floating-point values can produce unexpected and inaccurate
results. In our case, the 0.39 cents has an approximate
representation, so repeated subtraction of 0.01 left a remainder,
0.00999999999999967061
, rather than 0. Since that value is
greater than zero but less than 0.01, there is no way to break out of
the loop.
One way we can avoid this issue is by moving the decimal points of
our input and conditional values further to the right so that we can
deal only with integers. Since we care about change
only to
the hundredths place, we can move the decimal point two places to the
right by multiplying all of the relevant values by 100.
In addition, we need to ensure the floating-point value will round to
the correct integer value. If you look at the output above, you will
notice that 0.39 is represented roughly as 0.389, which means that we
need to round up to get 0.39 rather than 0.38 cents. A simple integer
cast won’t work because that will discard the fractional part of the
number and round the number down (i.e., toward zero). However, in the
<math.h>
library, there is a function called
round()
that we can use to get the correct result to assign
to an int
variable.
Note: if you ever use the <math.h>
library, you need to compile your source file with the -lm
flag (e.g., gcc change.c -lm
).
#include <stdio.h>
#include <math.h> // Contains the round() function.
int main(void)
{
double temp = 0.00;
int change = 0, dollars = 0, quarters = 0, dimes = 0, nickels = 0, pennies = 0;
("Enter the amount of change: ");
printf("%lf", &temp);
scanf
("change initially = %0.20lf\n", temp);
printf= round(temp * 100);
change ("change multiplied by 100 and rounded = %d\n", change);
printf
while (change != 0)
{
if (change >= 100)
{
++;
dollars= change - 100;
change }
else if (change >= 25)
{
++;
quarters= change - 25;
change }
else if (change >= 10)
{
++;
dimes= change - 10;
change }
else if (change >= 5)
{
++;
nickels= change - 5;
change }
else if (change >= 1)
{
("pennies = %d\n", pennies);
printf("change = %d\n", change);
printf++;
pennies= change - 1;
change }
("change = %d\n", change);
printf}
("%d Dollars\n", dollars);
printf("%d Quarters\n", quarters);
printf("%d Dimes\n", dimes);
printf("%d Nickels\n", nickels);
printf("%d Pennies\n", pennies);
printf
return 0;
}
Enter the amount of change: 4.39
change initially = 4.38999999999999968026
change multiplied by 100 and rounded = 439
change = 339
change = 239
change = 139
change = 39
change = 14
change = 4
pennies = 0
change = 4
change = 3
pennies = 1
change = 3
change = 2
pennies = 2
change = 2
change = 1
pennies = 3
change = 1
change = 0
4 Dollars
1 Quarters
1 Dimes
0 Nickels
4 Pennies
Dealing with floating-point numbers can be tricky, especially when using them in comparison statements and arithmetic operations. As seen above, printing more decimal places than you think you need will show you how a number is actually being represented and can be used to guide any further steps you take.