2012-06-18 44 views
1

我想从cstdio中为标准sprintf函数编写几个包装。但是,运行我的程序时,出现了一些奇怪的行为和访问冲突崩溃。我已经简化了问题并再现它下面的代码:C++变量参数和vsprintf

#include <string> 
#include <cstdio> 
#include <cstdarg> 

std::string vfmt(const std::string& format, va_list args) 
{ 
    int size = format.size() * 2; 
    char* buffer = new char[size]; 
    while (vsprintf(buffer, format.c_str(), args) < 0) 
    { 
     delete[] buffer; 
     size *= 2; 
     buffer = new char[size]; 
    } 

    return std::string(buffer); 
} 

std::string fmt(const std::string& format, ...) 
{ 
    va_list args; 
    va_start(args, format); 
    std::string res = vfmt(format, args); 
    va_end(args); 
    return res; 
} 

int main() 
{ 
    std::string s = fmt("Hello %s!", "world"); 
    printf(s.c_str()); 
    return 0; 
} 

此代码调用vfmtvsprintf时产生一个内存访问冲突。但是,当我将fmt的功能签名从fmt(const std::string& format, ...)更改为fmt(const char* format, ...)时,我不再崩溃,并且所有内容都按预期工作。为什么会发生这种情况?

为什么将format参数的类型从const std::string&更改为const char*解决了这个问题?还是它似乎只是解决了?

+1

@dirkgently不,那while循环不是用来解析'args'的。它调用'vsprintf'并检查结果字符串是否适合缓冲区。如果不是,则调整缓冲区大小并再次尝试,直到它结束。 – Zeenobit

+1

是否有任何理由使用不安全的'vsprintf'而不是'vsnprintf'或其任何C++替代品? –

+0

啊,是的,我意识到发布后大约5分钟。我的错。 :/ – dirkgently

回答

3

您不能将引用类型用作va_start的参数。这就是为什么改成char*的原因,所以会离开string但没有&。使用一个引用会扰乱为获得可变数量的参数所完成的魔法。

参见Are there gotchas using varargs with reference parameters

+0

就是这样。也有道理。感谢您及时的回复。 – Zeenobit

+0

好的。不幸的是,切换到指针需要更改公共接口,而不仅仅是辅助函数。 :( –

1

你不能那样做。我的意思是,你说的“有效”的版本。

vsprintf不允许您检测缓冲区传入的时间太小,因为它不知道它有多大。它会愉快地写下你的缓冲区中的任何对象。这可能会导致访问冲突,稍后可能会使您的程序崩溃,它可能会通过附加的活泼图片生成一封电子邮件给您的母亲。这是未定义的行为您的重新分配和重试循环无用。

你可能有更好的运气与vsnprintf,这知道缓冲区有多大,如果你的库来提供。