两者strncpy()
和(甚至更多)strncat()
都有不明显的行为,你最好不要使用任何一个。
strncpy()
如果您的目标字符串是 255 字节长,strncpy()
则将始终写入所有 255 字节。如果源字符串短于 255 个字节,它将用零填充剩余部分。如果源字符串超过 255 个字节,它将在 255 个字节后停止复制,使目标没有空终止符。
strncat()
大多数“大小”函数(、、、等)的大小参数strncpy()
是memcpy()
目标memmove()
字符串(内存)中的字节数。使用strncat()
,大小是在目标中已经存在的字符串末尾之后剩余的空间量。strncat()
因此,只有知道目标缓冲区有多大 ( S
) 以及目标字符串当前有多长( ) 时,才能安全使用L
。的安全参数strncat()
是S-L
(我们会担心其他时间是否有一个off-by-one)。但是鉴于您知道,跳过字符L
是没有意义的;您可以作为开始的地方通过,然后简单地复制数据。你可以使用or ,或者你可以使用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()
调用尝试将 24 个字符附加到filename
可能已经 232-234 字节长的字符串末尾,或者可能任意长于 235 字节。无论哪种方式,这都是保证缓冲区溢出。的使用strncat()
也直接落入了关于它的大小的陷阱。您说过可以在已经存在的内容的末尾添加最多 255 个字符filename
,这是明显错误的(除非字符串 fromgetenv("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()
代替;如果您可以安全地进行复制,那么您就知道使其合理所需的尺寸。