关于vsnprintf(访谈)

val*_*ldo 3 c printf crt

在一次采访中,我被要求(除其他事项外)实施以下功能:

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_tptrdiff_t.比如说,字符串是"合理的"大小.

我真的不知道这个bug会是什么.所有指针算术都可以恕我直言.我甚至不假设两个随后的调用vsnprintf产生相同的结果.所有可变处理的东西也是正确的恕我直言.va_copy不需要(这是被调用者的责任va_list).同时在x86 va_copyva_end是没有意义的.

如果有人能发现(潜在的)错误,我将不胜感激.

编辑:

在查看答案和评论后 - 我想添加一些注释:

  • 当然,我已经使用各种输入构建和运行代码,包括在调试器中逐步执行,观察变量状态.如果不首先尝试自己的事情,我永远不会寻求帮助.我没有看到任何问题,没有堆栈/堆损坏等.此外,我在调试版本中运行它,启用了调试堆(这对堆损坏是不容忍的).
  • 我假设函数是使用有效参数调用的,即psz是一个有效的指针(不要混淆*psz),szFmt是一个有效的格式说明符,并且所有的可变参数都被评估并对应于格式字符串.
  • 根据标准,free使用NULL指针调用是可以的.
  • 指针和大小= 0时调用vsnprintf正常NULL.它应该返回结果字符串长度.MS版本虽然不完全符合标准,但在这种特定情况下也是如此.
  • vsnprintf不会超过指定的缓冲区大小,包括0终止符.手段 - 它并不总是放置它.
  • 请把编码风格放在一边(如果你不喜欢它 - 对我很好).

cni*_*tar 9

不需要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上发生.