snprintf 和 strncpy 的细节区别

语言: CN / TW / HK

现在一般不能用 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;
}

最后一点,如果确信源字符串长度不小于缓存区长度,可以直接用 memset ,效率更高(但注意最后没有添加 0 ):

char buffer[8];
memcpy(buffer, "xxxxxxxxx", sizeof(buffer));

Q. E. D.