user4048336
user4048336

Reputation: 21

How does this printf code work without using va_args?

The code in question is from grub. Normally in a printf implementation, you'd see stdarg and va_start, va_list, va_end and va_arg, but they seem to be doing some casting here. My guess is that they're relying on some strange trick to grab the parameters and (int *) is similar to va_arg(parameter, int). Is this portable code? How does it work?

/* Format a string and print it on the screen, just like the libc
   function printf. */
void
printf(const char *format, ...) {
    char **arg = (char **) &format;
    int c;
    char buf[20];

    arg++;

    while ((c = *format++) != 0) {
        if (c != '%')
            putchar(c);
        else {
            char *p;

            c = *format++;
            switch (c) {
                case 'd':
                case 'u':
                case 'x':
                    itoa(buf, c, *((int *) arg++));
                    p = buf;
                    goto string;
                    break;

                case 's':
                    p = *arg++;
                    if (!p)
                        strcpy(p, "(null)");

string:
                    while (*p)
                        putchar(*p++);
                    break;

                default:
                    putchar(*((int *) arg++));
                    break;
            }
        }
    }
}

Upvotes: 2

Views: 507

Answers (3)

merlin2011
merlin2011

Reputation: 75545

This code makes certain assumptions about the order in which arguments are passed to the function.

In particular, it assumes that the addresses of the arguments are contiguous.

It is not portable because it will break as soon as this assumption is violated.

Upvotes: 0

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215191

No, this is not portable code, and it does not remotely work on any modern compiler. It assumes the arguments to the function arrive in an array of type char *[N] whose beginning overlaps with the local variable format. This is of course false, but matched the way some compilers might have chosen to position format on some ABIs (mainly, just i386) in the past.

Upvotes: 1

nneonneo
nneonneo

Reputation: 179392

This is not portable code. It assumes that va_list is essentially just an array of arguments, and makes a number of dangerous assumptions about the calling convention in use.

It can only work for calling conventions where every single argument is passed on the stack, and even then it isn't guaranteed to work: for example, padding may be used to align extra-wide arguments. Further, it isn't guaranteed that &format even obtains the start address of the argument list on the stack (&format may well be a local temporary variable by the time the function starts).

For calling conventions that pass some arguments in registers (ARM EABI, x86 __fastcall, x86_64, and several other ABIs), this breaks completely.

Upvotes: 2

Related Questions