复制字符串

指针赋值不复制字符串

你可以使用 = 运算符复制整数,但不能使用 = 运算符复制 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() 功能。虽然你可能认为它应该是第一位的,但这是一个相当具有欺骗性的功能,它有两个主要的问题:

  1. 如果通过 strncpy() 复制达到缓冲区限制,则不会写入终止空字符。
  2. 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() 变得非常低效。