正确的复制C字符串方法

20

有没有一种简单的方法来复制C字符串?

我有一个const char *stringA,我想让char *stringB获取这个值(请注意,stringB不是const)。我尝试了stringB=(char*) stringA,但这使得stringB仍然指向同一个内存位置,所以当stringA稍后更改时,stringB也会更改。

我还尝试过strcpy(stringB,stringA),但如果stringB未初始化为足够大的数组,则会出现段错误。虽然我对C字符串不是非常熟悉,但我是否遗漏了一些明显的东西?

如果我只是将stringB初始化为char *stringB[23],因为我知道我的字符串永远不会超过22个字符(包括空终止符),那么这是正确的方式吗?如果将stringB与其他C字符串进行比较,那么额外的空间会影响任何内容吗?

(在这里仅使用字符串并不是一个解决方案,因为我需要最小的开销和易于访问单个字符。)


7
我怀疑你高估了字符串的开销,并且你肯定高估了访问单个字符的难度。如果sstd::string,那么c = s[10]s[10] = c都可以正常工作,就像schar*一样易于访问单个字符。 - ruakh
请格式化您的问题。您可以使用反引号表示内联代码,并使用四个空格缩进以获得代码块。 - 0xC0000022L
1
除非你有更好的理由,否则请使用std::string - bames53
5
std::string是解决方案。您所持有的错误假设是它会更加昂贵。 - Martin York
很抱歉关于格式问题,我无法理解反引号的意思(撇号似乎没有任何作用)。 - Cannoliopsida
显示剩余3条评论
4个回答

27
你可以使用strdup()函数来返回一个C字符串的副本,例如:
#include <string.h>

const char *stringA = "foo";
char *stringB = NULL;

stringB = strdup(stringA);
/* ... */
free(stringB);
stringB = NULL; 

你也可以使用strcpy(),但是你需要先分配空间,这并不难做,但如果不正确地执行,可能会导致溢出错误:

#include <string.h>

const char *stringA = "foo";
char *stringB = NULL;

/* you must add one to cover the byte needed for the terminating null character */
stringB = (char *) malloc( strlen(stringA) + 1 ); 
strcpy( stringB, stringA );
/* ... */
free(stringB);
stringB = NULL;

如果你不能使用strdup(),我建议使用strncpy()代替strcpy()strncpy()函数最多只能复制n个字节,这有助于避免溢出错误。然而,如果strlen(stringA) + 1 > n,你需要自己终止stringB。但是,一般来说,你会知道你需要的大小。
#include <string.h>

const char *stringA = "foo";
char *stringB = NULL;

/* you must add one to cover the byte needed for the terminating null character */
stringB = (char *) malloc( strlen(stringA) + 1 ); 
strncpy( stringB, stringA, strlen(stringA) + 1 );
/* ... */
free(stringB);
stringB = NULL;

我认为strdup()更简洁,因此我尽量在仅使用字符串时使用它。对于基于POSIX或非POSIX的方法,在性能方面是否存在严重的缺陷,我不是C或C++专家,所以不清楚。

请注意,我将malloc()的结果强制转换为char *。这是因为您的问题标记为c++问题。在C++中,必须对malloc()的结果进行强制转换。然而,在C中,你不需要这样做。

编辑

有一个复杂情况:在C或C++中没有strdup()函数。因此,请使用预定义大小的数组或使用malloc分配的指针来使用strcpy()strncp()。在可能使用strcpy()的任何地方,习惯上使用strncp()会有助于减少错误的潜在可能性。


5
strdup 在 POSIX 中定义,而非 C 或 C++。 - R. Martinho Fernandes
1
strdup() 函数本质上只是 strlen()mallocmemset 的包装器。如果你想通过这个练习来学习直接编辑内存的恐怖而强大的世界,我建议你使用 strlen()memset - Robert Martin
1
不要使用strncpy代替strcpystrncpy是为了将字符串插入到其他字符串中而创建的。因此,它不会在字符串末尾添加空字符。这等于用同等严重的问题来交换另一个问题。 - Ed S.
1
@R.MartinhoFernandes 嗯,看起来不再是这样了:https://en.cppreference.com/w/c/experimental/dynamic/strdup - Ayxan Haqverdili
虽然这只是一个例子,但在释放指针后将其设置为NULL可以改进答案。不这样做会引来麻烦。然而,strncpy()存在问题,因为它可能无法正确地加入字符串的终止符,这非常糟糕。最好使用strdup(),或者确保分配足够的空间,或者明确地确保以NUL结尾。但是,没有足够的空间会创建另一个问题,即可能不是完整的字符串,这也是一个潜在的问题。 - Pryftan
显示剩余3条评论

4

如果你想用纯C语言风格实现,那么:

char* new_string = strdup(old_string);
free(new_string);

如果您想以(某种程度上的)C++风格进行操作:

char* new_string = new char[strlen(old_string) + 1];
strcpy(new_string, old_string);
delete[] new_string;

4

如果我将stringB初始化为char *stringB [23],因为我知道字符串不会超过22个字符(包括空终止符),这样做是正确的吗?

几乎可以。在C语言中,如果您确定字符串永远不会太长:

char stringB[MAX+1];
assert(strlen(stringA) <= MAX));
strcpy(stringB, stringA);

或者,如果字符串可能太长:

char stringB[MAX+1];
strncpy(stringB, stringA, MAX+1);
if (stringB[MAX] != '\0') {
    // ERROR: stringA was too long.
    stringB[MAX] = '\0'; // if you want to use the truncated string
}

在C++中,应该使用std::string,除非你已经证明开销是严重影响的。许多实现都有“短字符串优化”,将避免为短字符串动态分配内存; 在这种情况下,与使用C风格数组相比,几乎没有或没有额外开销。访问单个字符与C风格数组一样方便;在两种情况下, s[i]都会以 lvalue 的形式给出位置i处的字符。复制变成了stringB = stringA;,没有未定义行为的危险。
如果您真的发现std::string无法使用,可以考虑std::array<char,MAX+1>:一个包含固定大小数组的可拷贝类。

如果要将stringB与其他C字符串进行相等性检查,那么额外的空间会对任何东西产生影响吗?

如果使用strcmp,则它将在最短字符串的末尾停止,并且不会受到额外空间的影响。

1

你可能正在寻找strncpy,它允许你从一个字符串中复制前n个字符。只需确保在复制到的字符串的第n个位置添加空终止符。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接