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