Printf debugging

From C

Jump to: navigation, search

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.

Personal tools