strncpy并不总是空终止

我使用下面的代码:

char filename[ 255 ]; strncpy( filename, getenv( "HOME" ), 235 ); strncat( filename, "/.config/stationlist.xml", 255 ); 

收到此消息:

 (warning) Dangerous usage of strncat - 3rd parameter is the maximum number of characters to append. (error) Dangerous usage of 'filename' (strncpy doesn't always null-terminate it). 

我通常避免使用str*cpy()str*cat() 。 您必须应对边界条件,神秘的API定义以及意外的性能影响。

您可以使用snprintf()代替。 您只需要与目标缓冲区的大小竞争。 而且,它更安全,它不会溢出,并将永远NUL终止你。

 char filename[255]; const char *home = getenv("HOME"); if (home == 0) home = "."; int r = snprintf(filename, sizeof(filename), "%s%s", home, "/.config/stationlist.xml"); if (r >= sizeof(filename)) { /* need a bigger filename buffer... */ } else if (r < 0) { /* handle error... */ } 

您可以使用strncat调用溢出filename

使用:

 strncat(filename, "/.config/stationlist.xml", sizeof filename - strlen(filename) - 1); 

还要确保在strncpy调用后null终止缓冲区:

 strncpy( filename, getenv( "HOME" ), 235 ); filename[235] = '\0'; 

如果源的长度大于或等于要复制的最大字符数,则strncpy不会终止其目标缓冲区。

man strncpy有这个说:

 Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null terminated. 

如果它在耗尽最大长度之前遇到源中的0字节,它将被复制。 但是如果在源中的第一个0之前达到最大长度,则不会终止目标。 最好在strncpy()返回后确保它是你自己…

strncpy()和(甚至更多) strncat()都有非显而易见的行为,你最好不使用它们。

函数strncpy()

如果为了参数,你的目标字符串是255个字节长, strncpy()将始终写入所有255个字节。 如果源字符串短于255个字节,则将填充余数。 如果源字符串长度超过255个字节,它将在255个字节后停止复制,使目标不带空终止符。

strncat函数()

大多数’大小’函数的大小参数( strncpy()memcpy()memmove()等)是目标字符串(内存)中的字节数。 使用strncat() ,大小是已经在目标中的字符串结尾之后剩余的空间量。 因此,当您知道目标缓冲区的大小( S )和目标字符串当前的长度( L )时,您只能安全地使用strncat() )。 然后strncat()的安全参数就是SL (我们会担心其他时间是否有一个异地)。 但是考虑到你知道L ,没有必要让strncat()跳过L字符; 您可以将target+L作为起点,并简单地复制数据。 你可以使用memmove()memcpy() ,或者你可以使用strcpy() ,甚至strncpy() 。 如果你不知道源字符串的长度,你必须确信截断它是有意义的。

分析有问题的代码

 char filename[255]; strncpy(filename, getenv("HOME"), 235); strncat(filename, "/.config/stationlist.xml", 255); 

除非大小太小(或者你在未设置$HOME的上下文中运行程序),否则第一行是无法使用的,但这超出了这个问题的范围。 对strncpy()的调用不使用sizeof(filename)作为大小,而是使用任意小的数字。 这不是世界末日,但通常不能保证变量的最后20个字节是零字节(或者甚至是任何一个都是零字节)。 在某些情况下( filename是一个全局变量,以前未使用过),可能会保证零。

strncat()调用尝试在filename的字符串末尾附加24个字符,这些字符可能已经是232-234字节长,或者可能任意长于235字节。 无论哪种方式,这都是有保证的缓冲区溢出。 strncat()的使用也直接落入陷阱大小。 你已经说过,可以在filename已经超出filename的末尾添加255个字符,这显然是错误的(除非来自getenv("HOME")的字符串恰好是空的)。

更安全的代码:

 char filename[255]; static const char config_file[] = "/.config/stationlist.xml"; const char *home = getenv("HOME"); size_t len = strlen(home); if (len > sizeof(filename) - sizeof(config_file)) ...error file name will be too long... else { memmove(filename, home, len); memmove(filename+len, config_file, sizeof(config_file)); } 

会有人坚持认为’ memcpy()是安全的,因为字符串不能重叠’,并且在一个层次上它们是正确的,但重叠应该是非问题,而对于memmove() ,这是一个非问题。 所以,我一直使用memmove() ……但我还没有完成时序测量,看它有多大问题,如果它完全是一个问题。 也许其他人已经完成了测量。

摘要

  1. 不要使用strncat()
  2. 谨慎使用strncpy() (注意它在非常大的缓冲区上的行为!)。
  3. 计划使用memmove()memcpy() ; 如果你能安全地复制,你就知道了使这个变得合理的大小。

1)你的strncpy不一定是null-terminate filename。 实际上,如果getenv(“HOME”)长于235个字符而getenv(“HOME”)[234]不是0,则不会。 2)你的strncat()可能会尝试将文件扩展名超过255个字符,因为正如它所说的那样

 3rd parameter is the maximum number of characters to append. 

(不是dst允许的总长度)