为什么以下代码不起作用?
#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
变量的地址,该地址应该是堆栈上字符串指针的地址。
答案 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
之前的最后一个参数具有引用类型。