关于cstring:无法修改C字符串

Cannot modify C string

考虑下面的代码。

1
2
3
4
5
6
7
int main(void) {
    char * test ="abcdefghijklmnopqrstuvwxyz";
    test[5] = 'x';
    printf("%s
"
, test);
    return EXIT_SUCCESS;
}

在我看来,这应该打印abcdexghij。但是,它只是在没有打印任何内容的情况下终止。

1
2
3
4
5
6
int main(void) {
    char * test ="abcdefghijklmnopqrstuvwxyz";
    printf("%s
"
, test);
    return EXIT_SUCCESS;
}

不过,这个方法很好,所以我是否误解了操纵C字符串的概念?如果这很重要,我运行的是MacOSX10.6,它是我正在编译的32位二进制文件。


用初始化值定义的字符指针进入只读段。要使它们可修改,您要么需要在堆上创建它们(例如使用new/malloc),要么将它们定义为数组。

不可修改的:

1
char * foo ="abc";

Modifiable:

1
char foo[] ="abc";


这个答案很好,但不完全。

1
char * test ="abcdefghijklmnopqrstuvwxyz";

字符串文字是指具有静态存储持续时间的char[N]类型的匿名数组对象(意味着它存在于程序的整个执行过程中),其中N是字符串的长度加上终止'\0'的长度。这个对象不是const,但是任何修改它的尝试都有未定义的行为。(如果选择,实现可以使字符串文本可写,但大多数现代编译器不可写。)

上面的声明创建了一个类型为char[27]的匿名对象,并使用该对象第一个元素的地址初始化test。因此,像test[5] = 'x'这样的赋值试图修改数组,并且具有未定义的行为;通常它会使程序崩溃。(初始化使用地址,因为文本是数组类型的表达式,在大多数上下文中,该表达式被隐式转换为指向数组第一个元素的指针。)

注意,在C++中,字符串文字实际上是EDCOX1(3),上面的声明是非法的。在C或C++中,最好声明EDOCX1×5作为指向const EDCOX1的指针:9表示:

1
const char *test ="abcdefghijklmnopqrstuvwxyz";

因此,如果您试图通过test修改数组,编译器将警告您。

(由于历史原因,C字符串文字不是const。在1989年的ANSIC标准之前,const关键字不存在。要求在声明中使用它,就像您所做的那样,这样做是为了更安全的代码,但是它要求修改现有的代码,这是ANSI委员会试图避免的。您应该假装字符串文本是const,即使它们不是。如果您碰巧使用gcc,-Wwrite-strings选项将导致编译器将字符串文本视为const,这使得gcc不符合要求。)

如果要修改test引用的字符串,可以这样定义:

1
char test[] ="abcdefghijklmnopqrstuvwxyz";

编译器查看初始值设定项以确定test需要多大。在这种情况下,test将是char[27]类型。字符串文字仍然引用一个匿名的只读数组对象,但它的值被复制到test中。(用于初始化数组对象的初始值设定项中的字符串文字是数组不会"衰减"到指针的上下文之一;其他是当它是一元&sizeof的操作数时)因为没有对匿名数组的进一步引用,编译器可以将其优化掉。

在这种情况下,test本身就是一个包含您指定的26个字符以及'\0'终止符的数组。该数组的生存期取决于声明test的位置,这可能重要,也可能不重要。例如,如果您这样做:

1
2
3
4
char *func(void) {
    char test[] ="abcdefghijklmnopqrstuvwxyz";
    return test; /* BAD IDEA */
}

调用方将收到指向不再存在的内容的指针。如果需要引用定义test的范围之外的数组,可以将其定义为static,也可以使用malloc进行分配:

1
2
3
4
5
char *test = malloc(27);
if (test == NULL) {
    /* error handling */
}
strcpy(test,"abcdefghijklmnopqrstuvwxyz";

所以数组将继续存在,直到您调用free()。非标准的strdup()函数就是这样做的(它由posix定义,而不是由iso c定义)。

请注意,test可能是指针或数组,这取决于您如何声明它。如果把test传递给一个字符串函数,或者传递给任何接受char*的函数,这并不重要,但是像sizeof test这样的函数的行为会非常不同,这取决于test是指针还是数组。

comp.lang.c常见问题解答非常好。第8节包括字符和字符串,问题8.5指向问题1.32,该问题解决了您的特定问题。第6节介绍了数组和指针之间经常混淆的关系。


字符串文本可能不可修改;最好假定它们不可修改。有关详细信息,请参阅此处。


您应该养成将变量类型与初始化器类型匹配的习惯。在这种情况下:

1
const char* test ="abcdefghijklmnopqrstuvwxyz";

这样您将得到一个编译器错误而不是运行时错误。将编译器警告级别提高到最大值也有助于避免此类陷阱。为什么这不是C中的一个错误可能是历史性的;早期的编译器允许它,而不允许它可能在语言标准化时破坏了太多的现有代码。但是现在操作系统不允许这样做,所以这是学术性的。


做:

1
2
 char * bar = strdup(foo);
 bar[5] = 'x';

strdup生成可修改的副本。

是的,您应该测试strdup没有返回空值。