我可以通过引用传递给va_start()吗?

时间:2016-09-27 18:38:47

标签: c++ pass-by-reference variadic-functions

为什么以下代码不起作用?

#include <stdarg.h>
#include <stdio.h>

// People are missing this in their reponses.... 'fmt' here is passed by
// reference, not by value. So &fmt in _myprintf is the same as &fmt in 
// myprintf2. So va_start should use the address of the fmt char * on the
// stack passed to the original call of myprintf2.
void _myprintf(const char *&fmt, ...)
{
    char buf[2000];
//---
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
//---
    printf("_myprintf:%sn", buf);
}

void myprintf2(const char *fmt, ...)
{
    _myprintf(fmt);
}

void myprintf(const char *fmt, ...)
{
    char buf[2000];
//---
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
//---
    printf(" myprintf:%sn", buf);
}

int main()
{
    const char *s = "string";
    unsigned u = 11;
    char c = 'c';
    float f = 2.22;
    myprintf("s='%s' u=%u c='%c' f=%fn", s, u, c, f);
    myprintf2("s='%s' u=%u c='%c' f=%fn", s, u, c, f);
}

我希望两行输出相同,但它们不同:

 myprintf:s='string' u=11 c='c' f=2.220000
_myprintf:s='string' u=2020488703 c='c' f=0.000000

我认为va_start()使用了fmt变量的地址,该地址应该是堆栈上字符串指针的地址。

3 个答案:

答案 0 :(得分:4)

va_start确实使用了您提供的变量的地址。使用myprintf2,您只需将一个参数传递给myprintf,因此当您尝试访问第二个参数(s的传递值)时,它就不存在,并且您看到保存的寄存器,返回地址或其他位于堆栈中的内容。

要执行您尝试执行的操作,您需要将va_list变量传递给两个类似printf函数调用的公共函数。

编辑:从C ++语言标准,&#34;如果参数parmN是引用类型,或者是与传递没有参数的参数时产生的类型不兼容的类型,行为未定义。&#34; (parmN是传递给va_start的参数。)

编辑2:示例未编译的实现:

void myprintf_core(const char *fmt, va_list ap);

void myprintf2(const char *fmt, ...) {
    //...
    va_list ap;
    va_start(ap, fmt);
    myprintf_core(fmt, ap);
    va_end(ap);     // could be included in myprintf_core
}

myprintf_core是您的_myprintf,但没有3 va_行,这些行已移至myprintf2

答案 1 :(得分:1)

在调用函数时,在堆栈上创建了一个所谓的堆栈帧,它包含返回地址,参数以及生成的代码所需的其他一些元数据。当前函数的参数是 not 传递给新函数。

因此,在myprintf2调用_myprintf时,只传递fmt参数,其他任何参数都不会被传递。因此,vsnprintf调用会导致未定义的行为,因为它会尝试访问不存在的参数。

以图形方式将堆栈上的帧看作是这样的:

| .                                      |
| .                                      |
| .                                      |
+----------------------------------------+
| arguments for the _myprintf function   |
| .                                      |
| .                                      |
| .                                      |
| return address                         |
| Stack frame for the _myprintf function |
+----------------------------------------+
| arguments for the myprintf2 function   |
| .                                      |
| .                                      |
| .                                      |
| return address                         |
| Stack frame for the myprintf2 function |
+----------------------------------------+
| arguments for the main function        |
| .                                      |
| .                                      |
| .                                      |
| return address                         |
| Stack frame for the main function      |
+----------------------------------------+
| .                                      |
| .                                      |
| .                                      |

应该很容易理解为什么myprintf2的{​​{1}}的参数无法用于_myprintf

堆栈帧的确切格式和布局当然取决于系统和编译器。

答案 2 :(得分:1)

参见C ++ 14 [support.runtime] / 3:

  

参数parmN是函数定义的变量参数列表中最右边的参数的标识符(...之前的参数)。如果参数parmN是引用类型,或者是与传递没有参数的参数时产生的类型不兼容的类型,则行为未定义。

因此,您的代码会导致未定义的行为,因为..._myprintf之前的最后一个参数具有引用类型。