2016-09-27 61 views
1

为什么下面的代码不工作?我可以通过引用来传递va_start()吗?

#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

偏题:请谨慎使用前面的下划线。它们通常意味着图书馆实施层面的一些东西。在这种情况下,我认为您已经违反了在全局名称空间中保留前面的下划线以供实现使用的规则。更多这里:http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-ac-identifier/228797#228797 – user4581301

+1

不是一个无效C代码的C问题' _myprintf(const char *&fmt,...)'建议选择一种语言。如果C使用'void _myprintf(const char * fmt,...)' – chux

+0

以下划线开头的名字在文件级保留。不要使用它们。 – Olaf

回答

4

va_start确实使用您给它的变量的地址。使用myprintf2时,只传递一个参数到myprintf,所以当您尝试访问第二个参数(传递的s的值)时,它不存在,并且您看到保存的寄存器,返回地址或其他正在处理的内容在堆栈上。

要做你想做的事情,你需要将va_list变量传递给你的两个printf类函数调用的一个公共函数。

编辑:从C++语言标准中,“如果参数parmN是引用类型的类型,或者与传递没有参数的参数传递的类型不兼容的类型,则行为是未定义“。 (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

+0

不,我通过一个指针的引用,而不是按值的指针。所以&fmt在myprintf2和_myprintf中是一样的。我不得不在_myprintf中使用......的唯一原因是编译器试图太聪明,并且看到我在没有'...'的函数中使用了va_start,并且不会让它编译。 –

+0

因此... _myprintf中使用的fmt的地址是由myprintf2获取的堆栈上原始格式字符串的const char *的地址。请参阅....另外,如果您查看输出,则%s和%c参数会正确打印。只有%u和%f没有。 –

+0

@MarshallJobe您无法将引用传递给va_start;看我的编辑。 – 1201ProgramAlarm

1

当调用函数时,所谓的堆栈框架在堆栈上创建,它包含返回地址,参数以及可能生成的代码所需的其他元数据。当前函数的参数是而不是传递给新函数。

因此在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不提供给_myprintf

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

+0

我完全了解堆栈帧。你错过了一件非常重要的事情。我正在传递一个对fmt字符串的引用,而不是一个指针。 va_start使用fmt字符串的地址来确定格式字符串后面的参数的位置。 主要调用myprintf2并将它们压入堆栈'float,char,unsigned,char *,char *(格式)。这些都有地址。所有在堆栈上。 –

+0

fmt的地址告诉va_start第一个参数'char *'在哪里,然后是第二个(未签名)等等。 通过将_myprintf的引用传递给fmt字符串,_myprintf中的&fmt与myprintf2中的&fmt相同。 –

+0

@MarshallJobe如果没有黑客入侵并且基本上重新实现了'va_'宏,那么它将无法工作。 –

1

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

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

所以,你的代码会导致不确定的行为,因为在_myprintf...前的最后一个参数的引用类型。

相关问题