在一次采访中,我被要求(除其他事项外)实施以下功能:
int StrPrintF(char **psz, const char *szFmt, ...);
Run Code Online (Sandbox Code Playgroud)
类似于sprintf,除了已经分配的存储,函数必须自己分配它,并返回*psz变量.此外,*psz可能指向已经分配的字符串(在堆上),这可能在格式化期间使用.当然,这个字符串必须通过适当的方式释放.
返回值应该是新创建的字符串的长度,或者是错误时的负数.
这是我的实施:
int StrPrintF(char **psz, const char *szFmt, ...)
{
va_list args;
int nLen;
va_start(args, szFmt);
if ((nLen = vsnprintf(NULL, 0, szFmt, args)) >= 0)
{
char *szRes = (char*) malloc(nLen + 1);
if (szRes)
if (vsnprintf(szRes, nLen + 1, szFmt, args) == nLen)
{
free(*psz);
*psz = szRes;
}
else
{
free(szRes);
nLen = -1;
}
else
nLen = -1;
}
va_end(args);
return nLen;
}
Run Code Online (Sandbox Code Playgroud)
问题作者声称这个实现中存在一个错误.不仅是在特定深奥系统上可能失败的标准违规,而且是"真正的"错误,在大多数系统上偶然可能会失败.
它也与使用int而不是内存功能适合的类型有关,例如size_t或ptrdiff_t.比如说,字符串是"合理的"大小.
我真的不知道这个bug会是什么.所有指针算术都可以恕我直言.我甚至不假设两个随后的调用vsnprintf产生相同的结果.所有可变处理的东西也是正确的恕我直言.va_copy不需要(这是被调用者的责任va_list).同时在x86 va_copy和va_end是没有意义的.
如果有人能发现(潜在的)错误,我将不胜感激.
编辑:
在查看答案和评论后 - 我想添加一些注释:
psz是一个有效的指针(不要混淆*psz),szFmt是一个有效的格式说明符,并且所有的可变参数都被评估并对应于格式字符串.free使用NULL指针调用是可以的.vsnprintf正常NULL.它应该返回结果字符串长度.MS版本虽然不完全符合标准,但在这种特定情况下也是如此.vsnprintf不会超过指定的缓冲区大小,包括0终止符.手段 - 它并不总是放置它.不需要va_copy(使用va_list的被调用者负责)
不太对劲.我没有vsnprintf在C11标准中找到任何此类要求.它在脚注中这样说:
由于函数vfprintf,vfscanf,vprintf,vscanf,vsnprintf,vsprintf和vsscanf调用va_arg宏,因此返回后arg的值是不确定的.
当你打电话时vsnprintf,va_list可以通过值或引用传递(对于我们所知道的,它是一个不透明的类型).所以第一个vsnprintf实际上可以修改va_list和破坏第二个东西.建议的方法是使用复制va_copy.
事实上,根据这篇文章,它不会在x86上发生,但它在x64上发生.