在不同的类似UNIX的操作系统中,如何克服snprintf的不一致行为?

时间:2020-03-06 14:25:23  来源:igfitidea点击:

对于每个手册页,snprintf返回从glibc 2.2版开始写入的字节数。但是在较低版本的libc2.2和HP-UX上,它返回一个正整数,这可能导致缓冲区溢出。

如何克服这一难题并编写可移植的代码?

编辑:为了更清晰

这段代码在lib 2.3中运行良好

if ( snprintf( cmd, cmdLen + 1, ". %s%s", myVar1, myVar2 )  != cmdLen )
    {
        fprintf( stderr, "\nError: Unable to  copy bmake command!!!");
        returnCode = ERR_COPY_FILENAME_FAILED;
    }

它在Linux上返回字符串(10)的长度。但是,相同的代码返回的正数大于HP-UX计算机上打印的字符数。希望这个解释很好。

解决方案

我发现了一种可移植的方法来预测和/或者限制sprintf和相关函数返回的字符数,但是这种方法效率低下,并且许多人认为它不雅致。

我们要做的是使用tmpfile()和fprintf()创建一个临时文件(可靠地返回写入的字节数),然后倒退并将全部或者部分文本读入缓冲区。

例子:

int my_snprintf(char *buf, size_t n, const char *fmt, ...)
{
    va_list va;
    int nchars;
    FILE *tf = tmpfile();

    va_start(va, n);
    nchars = vfprintf(tf, fmt, va);
    if (nchars >= (int) n)
        nchars = (int) n - 1;
    va_end(va);
    memset(buf, 0, 1 + (size_t) nchars);

    if (nchars > 0)
    {
        rewind(tf);
        fread(buf, 1, (size_t) nchars, tf);
    }

    fclose(tf);

    return nchars;   
}

我们可以创建一个snprintf包装器,当缓冲区中没有足够的空间时,对于每种情况返回-1.

有关更多文档,请参见手册页。它也有一个威胁所有情况的例子。

while (1) {
      /* Try to print in the allocated space. */
      va_start(ap, fmt);
      n = vsnprintf (p, size, fmt, ap);
      va_end(ap);
      /* If that worked, return the string. */
      if (n > -1 && n < size)
         return p;
      /* Else try again with more space. */
      if (n > -1)    /* glibc 2.1 */
         size = n+1; /* precisely what is needed */
      else           /* glibc 2.0 */
         size *= 2;  /* twice the old size */
      if ((np = realloc (p, size)) == NULL) {
         free(p);
         return NULL;
      } else {
         p = np;
      }
   }

我们是否考虑过printf的可移植实现?我前一阵子找了一个,然后决定住三人间。

http://daniel.haxx.se/projects/trio/

问题仍然不清楚。链接到的手册页这样说:

The functions snprintf() and vsnprintf() do not write more than size bytes (including
  the trailing '
int ret = snprintf(cmd, cmdLen + 1, ". %s%s", myVar1, myVar2 ) == -1)
if(ret == -1 || ret > cmdLen)
{
    //output was truncated
}
else
{
    //everything is groovy
}
'). If the output was truncated due to this limit then the return value is the number of characters (not including the trailing '##代码##') which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.

因此,如果我们想知道输出是否被截断,请执行以下操作:

##代码##

请改用高级得多的asprintf()。

这是一个GNU扩展,但是如果它本身不可用,则值得复制到目标平台。

  • printf可移植性存在很多问题,实际上,我们可能希望遵循以下三种方法之一:
  • 需要一个符合c99的* printf,因为对于任何人来说9年就足够了,并且只说该平台已损坏即可。
  • 对于要支持所有特定平台的特定平台,有一个带有#ifdef的my_snprintf(),并在其下调用所有vsnprintf()(了解最低的公分母是我们拥有的)。
  • 只需在代码中随身携带vsnprintf()的副本,就简单的用例而言,它实际上非常简单;对于其他用例,我们可能想看一下vstr,我们将免费获得客户格式化程序。

...就像其他人建议的那样,仅针对-1情况,我们可以合并#1和#2,但是这样做有风险,因为c99 * printf在某些情况下可以/确实返回-1.

就个人而言,我建议仅使用类似于ustr的字符串库,该库可以为我们提供简单的解决方法,并为我们免费提供托管字符串。如果我们真的很在意,可以将其与vstr结合使用。