我使用下面的代码:
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] = ' ';
as strncpy
如果源的长度大于或等于要复制的最大字符数,则不会null终止其目标缓冲区。
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 ()
大多数'size '函数(strncpy()
, memcpy()
, memmove()
等)的size参数是目标字符串(内存)中的字节数。对于strncat()
,大小是目标对象中已经存在的字符串结束后剩余的空间。因此,只有在知道目标缓冲区有多大(S
)和目标字符串当前有多长(L
)时,才能安全地使用strncat()
。strncat()
的安全参数是S-L
(我们将在其他时间担心是否存在off-by- 1)。但是考虑到你知道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
是一个全局变量,以前未使用),可能会保证0。
strncat()
调用尝试在filename
中的字符串末尾追加24个字符,该字符串可能已经是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()
代替;如果你可以安全地复制,你知道需要的大小,使这是合理的
1)您的strncpy不一定以空终止文件名。事实上,如果getenv("HOME")大于235个字符,并且getenv("HOME")[234]不是0,则不会。2) strncat()可能会尝试将文件名扩展到255个字符以上,因为,正如它所说的,
3rd parameter is the maximum number of characters to append.
(不是dst的总长度)
strncpy(Copied_to,Copied_from,sizeof_input)
在字符数组后输出垃圾值(不用于字符串类型)。使用遍历字符数组的for循环来解决输出问题,而不是简单地使用cout<<var;
for(i=0;i<size;i++){cout<<var[i]}
我找不到在使用minGW编译器的windows系统上遍历的工作。Null终止并不能解决问题。