2013-07-18 26 views
11

典型的例子:在易发生异常的代码中使用va_list是否安全?

void foo(const char *fmt, ...) 
{ 
    va_list args; 
    va_start(args, fmt); 

    // might throw, might not. who knows. 
    bar(fmt, args); 

    // uh-oh... 
    va_end(args); 
} 

这是一个糟糕的主意,即是它罕见的使用在C++ va_list?如果我将bar换成try-catch,是否有帮助?什么是一些替代品?

+0

恕我直言变长参数列表是一个坏主意。看看'iostream'模型的合理解决方案。 –

+0

只需将它包裹在try/catch中,它应该是“安全的”。 – jmucchiello

+0

@EdHeal可变长度参数列表很好,只要您使用可变参数模板实现它们即可。 –

回答

4

C++标准遵循va_start等规范的C标准。 C标准有这样的说法:

7.15.1p1 ... va_start和va_copy宏的每个调用都应该在同一个函数中通过相应的va_end宏调用进行匹配。

因此,如果您在致电va_start之后但在va_end之前以任何方式退出该功能,那么您的程序会显示未定义的行为。

是的,包装bartry/catch将有所帮助。

+0

这当然是100%真实的,我不主张编写假设具体实现的代码,但我只是想抛出那些我已经看到的'va_end'的三个实现都是'ap =(va_list)0;',即空列表。所以不会调用'va_end'会在技术上导致UB,但实际上可能不会。同样,不主张在这里写错误的代码,而且样本量不大。该标准必须具体说明如何使用'va_list'及其相关函数,但实际上,这可能并不重要。 –

3

C++标准将此推迟到C标准。

C99(草案)7.15.1/1告诉我们:

所述的va_start的每次调用和va_copy宏应由 的va_end用来宏中相同功能的相应调用相匹配。

因此,如果bar抛出,你不能执行va_end和你的程序未定义的行为。如果你添加一个try/catch来确保va_end总是按需要调用,那么你应该没问题。但是请记住,您不能将非POD作为可变参数传递,所以如果您需要处理它们,则无论如何您都需要一个备用机制。

更多的C++类似的替代方案可能是插入运算符(operator<<),如在语言提供的各种iostream中所见。

+9

...或C++ 11可变参数模板。然后我们不再需要可怕的iostream链接的操作符黑客。 –

0

如上所述,它是c标准未定义的行为。 然而,根据你的平台,marco可以编译成不同的东西,例如我看到它只是args = 0;而且va_list是一个char *;在这种情况下,最终的宏观似乎没有做任何关键的事情。除了我不确定谁会释放参数,我不知道他们是在哪里分配的。

我不以任何方式推荐使用此功能,但有时为了支持遗留代码,有时需要疯狂的东西。如果你可以使用try catch,那么一定要这样做。