Printf debugging
From C
Contents |
Introduction
One of the simplest and most powerful techniques out there is printing the state of the program as it executes, in turn helping you narrow your problems down.
The key point of printing the state out is to verify that your understanding of the code matches what's really happening. As your skills grow you will learn how to place good printfs immediately hinting the problem. Try to think about what would be useful to print. For instance if you have a function consisting of a loop where the terminating condition is "data[i] == '\0'" and you get unexpected results, print both data[i] and i itself on each iteration.
If this does not get you anywhere your best weapon is diligence and inspection of all variables involved. Thus investigation employed is slow and methodical, as opposed to trying to skip various steps.
An important caveat to note is that in some cases printf can distort the result. This rarely happens and printf is almost always a good first thing to try. If it does not get you anywhere, try valgrind and/or a debugger.
The following will print the value of the variable var and the string str points to:
#include <stdio.h> int main(void) { int var = 42; char *str = "meh"; printf("var is %d, str is [%s]\n", var, str); return 0; }
Running this results in:
var is 42, str is [meh]
"%d" in the format string tells printf to expect an int as an argument, and "%s" to expect a pointer to char. Both arguments are provided in the order they were specified. Square brackets around %s are not necessary, but their significance is explained down below.
format string cheat sheet
modifier | argument you can pass |
%d | int |
%s | const char * |
%c | char |
%p | void * |
%zu | size_t |
See printf(3) ("man 3 printf") or your C book for more information.
End the format string with a newline: \n. This will also normally force the program to print it out immediately.
When printing strings, enclose them in some way e.g. with square brackets. This catches surprises like whitespaces and other "invisible" characters which you may have in the string.
Examples
Programs below show real issues as encountered by beginners, sometimes including the way they got described.
seemingly identical strings are different?
OP says they have a string. The program asks the user to input a string, which is compared with the one already in the program. However, no matter what is entered, there seems to be no match.
The code is:
#include <stdio.h> #include <string.h> int main(void) { char *mystring = "meh"; char buf[80]; printf("Enter something: \n"); if (fgets(buf, sizeof buf, stdin) == NULL) return 1; if (strcmp(buf, mystring) == 0) printf("strings match\n"); else printf("there is no match\n"); return 0; }
And indeed:
$ ./a.out Enter something: meh there is no match
The first step is to printf both mystring and buf to try to spot the difference, thus the following is added prior to strcmp:
printf("mystring=[%s] buf=[%s]\n", mystring, buf);
Then we get:
$ ./a.out Enter something: meh mystring=[meh] buf=[meh ] there is no match
Mystery resolved - buf includes the newline character, resulted from pressing Enter.
How to solve the problem? One example solution is to overwrite the \n character with 0.
a function mysteriously not being executed?
OP says that they have a function, let's call it myfunc. The code runs perfectly fine when used as a main function in a standalone program. However, putting it in a function myfunc in a different program results in the code not being executed for some reason.
The code is:
#include <stdio.h> static void myfunc(void) { printf("yay, called!\n"); } static int get_input(void) { int input; do { printf("enter either 1 or 2\n"); scanf("%i", &input); } while (input != 1 && input != 2); return input; } int main(void) { int input; input = get_input(); switch (input) { case '1': myfunc(); break; case '2': printf("meh\n"); break; } return 0; }
Let's try it out. We expect to get "yay called" for 1 and "meh" for 2.
$ ./a.out enter either 1 or 2 8 enter either 1 or 2 1
But 1 should have result in "yay called". What gives?
Well, how about we printf what was obtained and what are comparing against, just after get_input:
printf("input=%d compared to=%d\n", input, '1');
$ ./a.out enter either 1 or 2 1 input=1 compared to=49
49? What 49? At this point you may notice get_input was comparing against actual 1 and 2, but main is comparing against '1', which as we see is a different thing. It happens to be character code for that number, and not the number itself. Mystery solved.
As as side note, even with this addressed, the code is wrong on at least 2 accounts:
- it uses scanf to read user-formatted input
- does not use the default label in switch, which would serve as a great initial indicator that something is wrong
Function interleaving 2 strings is malfunctioning
The goal is to write a function which takes 2 strings and interleaves their contents. That is, for "AAAAA" and "BBB" we expect to get "ABABABAA"
#include <stdio.h> #include <string.h> int interleave(char *s1, char *s2, char *out, size_t out_size) { size_t i, j, k; if (strlen(s1) + strlen(s2) >= out_size) return 1; i = j = k = 0; do { if (s1[i] != '\0') { out[k++] = s1[i]; i++; } if (s2[i] != '\0') { out[k++] = s2[j]; j++; } } while (s1[i] != '\0' || s2[i] != '\0'); out[k] = '\0'; return 0; } int main(int argc, char **argv) { char buf[100]; int ret; if (argc != 3) return 1; ret = interleave(argv[1], argv[2], buf, sizeof(buf)); printf("ret=%d [%s]\n", ret, ret == 0 ? buf : NULL); return 0; }
It works fine if strings are of the same length: $ ./a.out AAA BBB ret=0 [ABABA]
But things go wrong for input like this: $ ./a.out AAA BBBB segmentation fault (core dumped)
Let's investigate. We can start by inspecting the state of relevant variables at the beginning of iteration of the loop. We infer i indexes s1, while j indexes s2.
$ ./a.out AAA BBBB s1[0]=41 s2[0]=42 k=0 s1[1]=41 s2[1]=42 k=2 s1[2]=41 s2[2]=42 k=4 s1[3]=0 s2[3]=42 k=6 s1[3]=0 s2[4]=0 k=7 s1[3]=0 s2[5]=58 k=8 s1[3]=0 s2[6]=44 k=9 s1[3]=0 s2[7]=47 k=10 s1[3]=0 s2[8]=5f k=11 s1[3]=0 s2[9]=56 k=12 [snip]
Whoa, what's happening? Why did not it stop earlier? We clearly see the line "s1[3]=0 s2[4]=0 k=7" which shows that we reach the null terminator in both strings.
Let's add a printf at the end of the loop showing us exactly the terminating condition.
printf("end of the loop: s1[%zu]=%x s2[%zu]=%x condition=%d\n", i, s1[i], j, s2[j], s1[i] != '\0' || s2[i] != '\0');
The s2[j], s1[i] != '\0' || s2[i] != '\0' comparison from the loop was *copied*.
$ ./a.out AAA BBBB s1[0]=41 s2[0]=42 k=0 end of the loop: s1[1]=41 s2[1]=42 condition=1 s1[1]=41 s2[1]=42 k=2 end of the loop: s1[2]=41 s2[2]=42 condition=1 s1[2]=41 s2[2]=42 k=4 end of the loop: s1[3]=0 s2[3]=42 condition=1 s1[3]=0 s2[3]=42 k=6 end of the loop: s1[3]=0 s2[4]=0 condition=1 [snip]
What gives? We do have zeroes, so the condition should be false. Why is it true then? Since the condition has 2 parts, let's extend the printf to tell us the value of each one.
printf("end of the loop: s1[%zu]=%x s2[%zu]=%x condition=%d(%d %d)\n", i, s1[i], j, s2[j], s1[i] != '\0' || s2[i] != '\0', s1[i] != '\0', s2[i] != '\0');
$ ./a.out AAA BBBB s1[0]=41 s2[0]=42 k=0 end of the loop: s1[1]=41 s2[1]=42 condition=1(1 1) s1[1]=41 s2[1]=42 k=2 end of the loop: s1[2]=41 s2[2]=42 condition=1(1 1) s1[2]=41 s2[2]=42 k=4 end of the loop: s1[3]=0 s2[3]=42 condition=1(0 1) s1[3]=0 s2[3]=42 k=6 end of the loop: s1[3]=0 s2[4]=0 condition=1(0 1) [snip]
This does not add up. We do printf s2[j] and it is zero. Why does the test in the condition yield non-zero then? Actually reading the test reveals the culprit: it included i as opposed to j. Once the culprit is discovered, a pass over the function to verify there is no similar mistake elsewhere reveals that if (s2[i] != '\0') check is also wrong.
For this case, one has to stress that the faulty loop condition was *copied*, not retyped. Similarly, for printing parts of the condition, copy was used. Copying is the key to working your way to such mistakes.