Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Printf gone wild

There is quite a lot of code in my source but the main problem is in 2 consecutive lines.

 struct step
 {
     int left,tonum;
     long long int rez;
 };

inline bool operator==(const step& a, const step& b)
{
    printf("\n%d",b.tonum);
    printf("\n%d %d | %d %d | %d %d",a.left, b.left, a.rez, b.rez, a.tonum, b.tonum);
    return a.left==b.left && a.rez==b.rez && a.tonum==b.tonum;
}

This gets called quite a few million times but the problem is that although it should be the same most of the time, it never is, and the output is very strange indeed.

2
7989 7989 | 53 0 | 53 0
1
8989 7989 | 52 0 | 53 0
2
8989 8989 | 52 0 | 52 0
1
7899 8989 | 51 0 | 52 0

Not only should b.tonum be the same but also he should be == a.tonum for some other reasons not explained in this piece of code

Why isnt b.tonum printed the same both times?

like image 769
ditoslav Avatar asked Jan 10 '14 03:01

ditoslav


1 Answers

You cannot use %d to print a long long. You'd have to use %lld. (So use "\n%d %d | %lld %lld | %d %d" for your format string.)

In particular, it's apparent that in the "52 0 | 52 0", the first 52 0 is a.rez, and the second 52 0 is b.rez (each of these, being a long long, is apparently (judging from the output) pushing two words to the stack). a.tonum and b.tonum are not printed at all.


To understand why this is happening, let me explain what Jonathan and I are trying to say. When you invoke a variadic function like printf (which is declared as something like printf(const char *format, ...), the compiler has no way to validate the correct argument types for the ... at compile-time. So there's a procedure for deciding what to push on the stack in that case, which can roughly be summarised as: if it's int or promotable to int, it gets pushed as an int; if it's double or promotable to double, it gets pushed as a double; otherwise, it gets pushed as is.

When implementing a variadic function like printf, you need some way to access the ... items. The way to do so is to use a va_list, which is declared in <stdarg.h>. Here's some pseudocode which shows how it'd be used:

int printf(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    while (/* we find the next format marker */) {
        if (/* %d, %i, %c */) {
            int val = va_arg(ap, int);
            /* print out val as decimal or (for %c) char */
        } else if (/* %u, %x, %X, %o */) {
            unsigned int val = va_arg(ap, unsigned int);
            /* print out val as decimal, hex, or octal */
        } else if (/* %ld, %li */) {
            long val = va_arg(ap, long);
            /* print out val as decimal */
        } else if (/* %lu, %lx, %lX, %lo */) {
            unsigned long val = va_arg(ap, unsigned long);
            /* print out val as decimal, hex, or octal */
        } else if (/* %lld, %lli */) {
            long long val = va_arg(ap, long long);
            /* print out val as decimal */
        } else if (/* %llu, %llx, %llX, %llo */) {
            unsigned long long val = va_arg(ap, unsigned long long);
            /* print out val as decimal, hex, or octal */
        } else if (/* %s */) {
            const char *val = va_arg(ap, const char *);
            /* print out val as null-terminated string */
        } /* other types */
    }
    va_end(ap);
    return /* ... */;
}

Notice that each time you want to pick off a ... argument, you'd do so using va_arg, and you'd have to specify the type to pick off. It is up to you to pick off the correct type. If the type is incorrect, you have a type-punning situation, which in most cases has undefined behaviour (meaning that the program can do anything it likes, including crashing or worse).

In your particular computer, it seems like when you passed a long long, it pushed a 64-bit quantity to the stack, but because you used a %d format specifier, it used the va_arg(ap, int) version, which only grabbed a 32-bit quantity. That means that the other half of the 64-bit word was still unread, which the subsequent %d then proceeded to read. This is why, by the time the format string was finished, it never got around to processing the values of a.tonum and b.tonum that you passed.

Whereas, had you correctly used %lld, it would have used va_arg(ap, long long), and would have correctly read in the whole 64-bit quantity.

like image 135
Chris Jester-Young Avatar answered Nov 11 '22 15:11

Chris Jester-Young