如何正确使用strncpy?

how to use strncpy correctly?

我知道 strncpystrcpy 的更安全版本,如 here 所述。

但是,当我想从 src 复制到 dst 并且 dst 不是一个干净的缓冲区时,我得到了不需要的结果,这可以通过 strcpy:

char *p = "123";
char a[10] = "aaaaaa";

strncpy(a,p,strlen(p));
printf("%s\n", a);   // 123aaa

strcpy(a,p);
printf("%s\n", a);   // 123 <- desired output, as the trailing a's are garbage

在我的实际情况下,我知道strlen(src) < sizeof(dst)(至少,如果不是这样,程序会更快崩溃),所以我可以放心strcpy

但是,如果strncpy是我应该使用的,那么我必须在dst[strlen(src)] = '[=20=]'之后添加到避免垃圾(或者更好的是,预先初始化缓冲区?)?

strncpy 的第三个参数表示目标缓冲区 的 大小。当它填满时,它不会按设计添加空终止字符。

如果您有足够的 space 作为终结符,并且您坚持 strncpy,只需传递 strlen(p) + 1,这样它就不会假设它耗尽了目标缓冲区。

就像现在已经注意到的许多人一样。 strncpy 的这种用法违背了目的,实际上并不比简单地调用 strcpy 好多少。 strncpy 的唯一实际用途是如果您想就地覆盖字符串的一部分(这是您偶然发现的用例)。尽管这也是有问题的用途...

strncpy() 实际上不是字符串函数;相反,它处理哪些非零字符的零填充序列,它们一起具有已知的固定长度。例如,它是填充数据结构的正确工具,这些数据结构随后被发送到其他程序、外部或持久保存,以避免数据泄漏。

试图在其非常专业的领域之外使用它会导致可怕的扭曲和低效的代码。

有更合适的方法来解决它,例如 strlcpy() and/or 手动使用 strlen()memcpy()

how to use strncpy correctly?

当代码需要将字符串复制到目标位置并允许结果不是 空字符 终止或完全复制时,使用 sizeof destination 作为大小参数:

char a[10];
// strncpy(a,p,strlen(p));
strncpy(a, p, sizeof a);

// printf("%s\n", a);
printf("%.*s\n", (int) sizeof a, a);

当代码想要通过 strncpy() 复制字符串并检测内存不足问题或需要空字符 '[=16=]' 填充时,也可以使用 sizeof destination.

char a[10];
strncpy(a, p, sizeof a);
if (a[sizeof a - 1] != '[=11=]') {
  // insufficient memory
  // Maybe set last last character to the null character 
  a[sizeof a - 1] == '[=11=]';
  // or other more robust handling
  return ERROR_INSUFFICIENT_MEMORY;
}

否则不要使用strncpy()


当不需要空字符 '[=16=]' 填充时,有比 strncpy() 更有效的检测内存不足的方法。 strncpy() 零填充未复制缓冲区的其余部分(如果有)。下面的填零消耗了很多时间,只是为了提供一个不充分的检查。

char a[1000];
strncpy(a, "abc", sizeof a);
if (a[sizeof a - 1] != '[=12=]') {
  ....

更好的替代方案采用 strlen(), strlcpy(), memcpy().

另见 strncpy or strlcpy in my case

对于标准的 C lib 单行代码,代码可以使用 snprintf() 和一行错误检测。一个好的编译器应该能够分析 snprintf(a, sizeof a, "%s", p) 并发出高效的代码。

// Copy with no overflow.
// 'a' is always null character terminated.
int len = snprintf(a, sizeof a, "%s", p);
if (len < 0 || (unsigned) len >= sizeof a) Report_truncated_copy();