如果不使用va_args,这个printf代码如何工作?

时间:2014-09-16 23:29:39

标签: c variadic-functions

相关代码来自grub。通常在printf实现中,您会看到stdargva_startva_listva_endva_arg,但他们似乎在做一些演员表这里。我的猜测是,他们依靠一些奇怪的技巧来获取参数,而(int *)va_arg(parameter, int)类似。这是可移植代码吗?它是如何工作的?

/* 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;
            }
        }
    }
}

3 个答案:

答案 0 :(得分:2)

这是可移植代码。它假设va_list本质上只是一个参数数组,并对使用的调用约定做了许多危险的假设。

它只能用于调用每个参数在堆栈上传递的约定,即使这样也不能保证工作:例如,padding可用于对齐超宽参数。此外,不保证&format甚至可以获得堆栈上参数列表的起始地址(&format在函数启动时可能是本地临时变量)。

对于在寄存器中传递一些参数的调用约定(ARM EABI,x86 __fastcall,x86_64和其他几个ABI),这会完全破坏。

答案 1 :(得分:1)

不,这不是可移植代码,并且它不能在任何现代编译器上远程工作。它假定函数的参数到达类型char *[N]的数组,其开头与局部变量format重叠。这当然是错误的,但与某些编译器过去可能选择将format定位在某些ABI (主要是i386)上的方式相匹配。< / p>

答案 2 :(得分:0)

此代码对参数传递给函数的顺序做出了某些假设。

特别是,它假定参数的地址是连续的。

它不可移植,因为一旦违反此假设,它就会中断。