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()
……但我还没有完成时序测量,看它有多大问题,如果它完全是一个问题。 也许其他人已经完成了测量。
摘要
- 不要使用
strncat()
。 - 谨慎使用
strncpy()
(注意它在非常大的缓冲区上的行为!)。 - 计划使用
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允许的总长度)