C 为什么此示例将字符串的最后一个字符(由`snprintf()`指定)显式设置为`0`?

C 为什么此示例将字符串的最后一个字符(由`snprintf()`指定)显式设置为`0`?,c,printf,C,Printf,从 snprintf。。。将结果写入字符串缓冲区。(…)将以空字符结尾,除非buf_大小为零 那么,为什么下面的Linux编程接口示例明确地将字符串的最后一个字符(由snprintf()指定)设置为\0 char * inetAddressStr(const struct sockaddr *addr, socklen_t addrlen, char *addrStr, int addrStrLen) { char host[NI_MAXHOST], ser

snprintf。。。将结果写入字符串缓冲区。(…)将以空字符结尾,除非buf_大小为零

那么,为什么下面的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;
}

这对于任何适当的
snprintf
实现都是不必要的。然而,在一些系统上,
snprintf
是一些“类似”函数的包装器,它做了一些有趣的事情,比如在末尾或忽略
length
参数。来自任何POSIX系统的
snprintf
或来自或内核的
snprintf
都不是这种情况

另外,该代码没有检查
snprintf
的返回值(它能够复制整个字符串吗?),对于示例代码来说,这是非常糟糕的


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

snprintf()
函数应等同于
sprintf()
,具有 添加说明缓冲区大小的
n
参数 由
s
引用。如果
n
为零,则不应写入任何内容,
s
可以 必须是空指针。否则,超出
n-1
st的输出字节应为 丢弃而不是写入数组,并返回空字节 在实际写入数组的字节末尾写入

如果复制发生在由于复制而重叠的对象之间 调用sprintf()或snprintf(),结果未定义


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

如果
snprintf
符合C标准,则将目标数组的最后一个字符设置为
'\0'
显然是无用的

但是请注意,Linux内核使用自己版本的C库,它可能符合也可能不符合C标准。大多数函数都比C99标准旧,C99标准首先指定了
snprintf
,这可能解释了实现差异。内核的
snprintf
实现的当前版本没有设置空终止符。发布的代码似乎来自用户代码,而不是内核代码,因此这与此无关

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

还请注意,一些Microsoft扩展的名称与标准函数非常相似,其行为方式令人惊讶和困惑。例如,如果输出被截断,则
\u snprintf()
不会在目标数组中设置空终止符。有关血淋淋的详细信息,请参阅

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

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

检查手册页中的错误部分。引用:“因此,在早期的libc4中使用snprintf()会导致严重的安全问题”。我记得以前手册页对snprintf的功能含糊不清,其中有“函数snprintf()和vsnprintf()的写入量不超过大小字节(包括终止的空字节('\0')”)”之类的描述。因为这实际上并没有说明字符串在被截断时是否以null结尾,所以我假设代码的作者想要额外确保。我相信我在过去也做过类似的事情。@HansPassant以及如何将最后一个字节设置为0来帮助libc4,它“包含一个相当于sprintf()的snprintf(),即忽略size参数的snprintf()”?(完成错误的引用)。来自linux内核的
snprintf
不终止其缓冲区;请参阅我答案中的链接。@PizSelect:是,正确。代码要么来自旧内核,要么是很久以前编写的,没有更新。这是示例用户代码(来自一本书),而不是内核代码——请注意getnameinfo(3)的使用,如果我没有弄错的话,它将c99和snprintf()的日期后移。@pizdelect:确实,您是正确的。我想是防御性编程吧。
int snprintf(char *restrict s, size_t n, const char *restrict format, ...);