复制字符串
指针赋值不复制字符串
你可以使用 =
运算符复制整数,但不能使用 =
运算符复制 C 中的字符串 .C 中的字符串表示为具有终止空字符的字符数组,因此使用 =
运算符只会保存地址(指针)一个字符串。
#include <stdio.h>
int main(void) {
int a = 10, b;
char c[] = "abc", *d;
b = a; /* Integer is copied */
a = 20; /* Modifying a leaves b unchanged - b is a 'deep copy' of a */
printf("%d %d\n", a, b); /* "20 10" will be printed */
d = c;
/* Only copies the address of the string -
there is still only one string stored in memory */
c[1] = 'x';
/* Modifies the original string - d[1] = 'x' will do exactly the same thing */
printf("%s %s\n", c, d); /* "axc axc" will be printed */
return 0;
}
上面的例子编译是因为我们使用的是 char *d
而不是 char d[3]
。使用后者会导致编译器错误。你无法在 C 中分配数组。
#include <stdio.h>
int main(void) {
char a[] = "abc";
char b[8];
b = a; /* compile error */
printf("%s\n", b);
return 0;
}
使用标准函数复制字符串
strcpy()
要真正复制字符串, strcpy()
功能在 string.h
中可用。复制前必须为目的地分配足够的空间。
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "abc";
char b[8];
strcpy(b, a); /* think "b special equals a" */
printf("%s\n", b); /* "abc" will be printed */
return 0;
}
Version => C99
snprintf()
为避免缓冲区溢出,可以使用 snprintf()
。它不是性能最佳的解决方案,因为它必须解析模板字符串,但它是唯一的缓冲区限制安全函数,用于复制标准库中易于使用的字符串,无需任何额外步骤即可使用。
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "012345678901234567890";
char b[8];
#if 0
strcpy(b, a); /* causes buffer overrun (undefined behavior), so do not execute this here! */
#endif
snprintf(b, sizeof(b), "%s", a); /* does not cause buffer overrun */
printf("%s\n", b); /* "0123456" will be printed */
return 0;
}
strncat()
第二个选项,具有更好的性能,是使用 strncat()
( strcat()
的缓冲区溢出检查版本) - 它需要第三个参数,告诉它要复制的最大字节数:
char dest[32];
dest[0] = '\0';
strncat(dest, source, sizeof(dest) - 1);
/* copies up to the first (sizeof(dest) - 1) elements of source into dest,
then puts a \0 on the end of dest */
注意这个配方使用 sizeof(dest) - 1
; 这是至关重要的,因为 strncat()
总是添加一个空字节(好),但不计算字符串的大小(混淆和缓冲区覆盖的原因)。
另请注意,替代方案 - 在非空字符串后连接 - 更加令人担忧。考虑:
char dst[24] = "Clownfish: ";
char src[] = "Marvin and Nemo";
size_t len = strlen(dst);
strncat(dst, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);
输出是:
23: [Clownfish: Marvin and N]
但请注意,指定为长度的大小不是目标数组的大小,而是剩余的空间量,不包括终结空字节。这可能会导致大量覆盖问题。这也有点浪费; 要正确指定长度参数,你知道目标中数据的长度,因此你可以在现有内容的末尾指定空字节的地址,从而节省 strncat()
重新扫描它:
strcpy(dst, "Clownfish: ");
assert(len < sizeof(dst) - 1);
strncat(dst + len, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);
这产生与以前相同的输出,但是 strncat()
在开始复制之前不必扫描 dst
的现有内容。
strncpy()
最后一个选项是 strncpy()
功能。虽然你可能认为它应该是第一位的,但这是一个相当具有欺骗性的功能,它有两个主要的问题:
- 如果通过
strncpy()
复制达到缓冲区限制,则不会写入终止空字符。 strncpy()
总是完全填充目的地,必要时使用空字节。
(这种古怪的实现是历史性的,最初用于处理 UNIX 文件名 )
使用它的唯一正确方法是手动确保空终止:
strncpy(b, a, sizeof(b)); /* the third parameter is destination buffer size */
b[sizeof(b)/sizeof(*b) - 1] = '\0'; /* terminate the string */
printf("%s\n", b); /* "0123456" will be printed */
即使这样,如果你有一个很大的缓冲区,由于额外的空填充,使用 strncpy()
变得非常低效。