现在一般不能用 sprintf 和 strcpy ,推荐使用 snprintf 和 strncpy ,以防止缓冲区溢出:
char buffer[32];
snprintf(buffer, sizeof(buffer), "xxxxxxxxx");
strncpy(buffer, "xxxxxxx", sizeof(buffer));
但 snprintf 和 strncpy 有一些很细节的区别,一不注意就会出错:
- 如果预复制(或打印)的字符串长度小于缓冲区长度(上面例子里的 32 ),那么 snprintf 会在末尾添加一个 0 ,而 strncpy 会在剩余的空间都填上 0。
- 如果预复制(或打印)的字符串长度大于等于缓冲区长度(上面例子里的 32 ),那么 snprintf 会复制 31 个字符,然后填上一个 0 ,此时 buffer 是一个正常的 C 字符串,但比源字符串字符数要少。但 strncpy 会复制刚好 32 个字符,不会添加 0。此时 buffer 不是一个正常的 C 字符串,可能引起缓冲区溢出。最新的编译器会给出警告。
- snprintf 返回打印的字符数(不包含尾部的 0 ), strncpy 返回 buffer (貌似多此一举)。
从防范风险的角度看, snprintf 更安全,但 snprintf 要慢很多。实际工作中需要取舍。
从 GCC9 开始,编译器会给 strncpy 给出一个-Wstringop-truncation
的编译警告,提示可能没有 0 结束字符问题。
从实际工作角度,可以构造一个 sprintf 和 strncpy 的安全结合版:
inline char * safe_strncpy(char* dest, const char* src, size_t n)
{
assert(n > 0);
strncpy(dest, src, n - 1);
dest[n - 1] = 0;
return dest;
}
最后一点,如果确信源字符串长度不小于缓存区长度,可以直接用memcpy
,效率更高(但注意最后没有添加 0 ):
char buffer[8];
memcpy(buffer, "xxxxxxxxx", sizeof(buffer));
Q. E. D.