为什么此示例将字符串的最后一个字符(由snprintf()分配)显式设置为/ 0?

时间:2019-03-03 15:40:50

标签: c printf

来自https://stackoverflow.com/a/13067917/156458

  

snprintf ...将结果写入字符串缓冲区。 (...)将以空字符终止,除非buf_size为零。

那么为什么Linux编程接口中的以下示例将字符串的最后一个字符(由snprintf()分配)显式设置为\0

char *
inetAddressStr(const struct sockaddr *addr, socklen_t addrlen,
               char *addrStr, int addrStrLen)
{
    char host[NI_MAXHOST], service[NI_MAXSERV];
    if (getnameinfo(addr, addrlen, host, NI_MAXHOST,
                    service, NI_MAXSERV, NI_NUMERICSERV) == 0)
        snprintf(addrStr, addrStrLen, "(%s, %s)", host, service);
    else
        snprintf(addrStr, addrStrLen, "(?UNKNOWN?)");
    addrStr[addrStrLen - 1] = '\0';     /* Ensure result is null-terminated */
    return addrStr;
}

2 个答案:

答案 0 :(得分:3)

如果'\0'符合C标准,则将目标数组的最后一个字符显式设置为snprintf似乎没用。

但是请注意,Linux内核使用自己的C库版本,该版本可能符合或可能不符合C标准。大多数功能都比最初指定snprintf的C99标准要旧,这可以解释实现上的差异。内核snprintf实现的当前版本确实设置了空终止符。发布的代码似乎来自用户代码,而不是内核代码,因此这无关紧要。

还请注意,某些C库具有非标准行为。例如,Microsoft C运行时库在发布后将近15年不支持C99扩展,并且由于各种原因仍不完全兼容。例如,snprintf具有%n转换的非标准行为。

还请注意,某些名称与标准函数非常相似的Microsoft扩展程序的行为令人惊讶且令人困惑。例如,如果输出被截断,_snprintf()将不会在目标数组中设置空终止符。有关详细信息,请参见this documentation page

对于发布的代码,C库不太可能来自Microsoft,但是linux内核可以在具有各种C库的系统中使用:GNU libc用于大多数桌面发行版,但是Android使用不同的库图书馆。代码片段的作者之一:

  • 不完全了解C标准并编写了冗余代码。
  • 不信任本地C库的实现,也不想假设snprintf写空终止符。
  • 习惯使用防御性编程并编写冗余代码,以防万一。

答案 1 :(得分:1)

对于任何适当的snprintf实现,都不需要这样做。但是,在某些系统上,snprintf是一些“相似”功能的包装,它们在结束时做了诸如not writing a NUL byte之类的有趣事情,或者忽略了length参数。来自任何POSIX系统的snprintf或来自linuxfreebsd内核的snprintf都不是这种情况。

此外,该代码未检查snprintf的返回值(是否能够复制整个字符串?),对于示例代码而言,这是很麻烦的。


snprintf总是使用NUL字节终止复制到缓冲区的字符串,除非length / size参数为0 ,在这种情况下它不会复制任何内容。引用susv4 [1]:

int snprintf(char *restrict s, size_t n, const char *restrict format, ...);
     

snprintf()函数应等效于sprintf(),其中        n参数的添加,它指出缓冲区的大小        由s引用。如果n为零,则不写任何内容,s可以        为空指针。否则,n-1 st之后的输出字节应为        丢弃而不是写入数组,并且空字节为        写在实际写入数组的字节的末尾。

     

如果由于以下原因在重叠的对象之间进行复制        调用sprintf()或snprintf(),结果是不确定的。

[1] C99标准中的语言相同