2009-09-21 54 views
7

请考虑以下代码。无法修改C字符串

 
int main(void) { 
    char * test = "abcdefghijklmnopqrstuvwxyz"; 
    test[5] = 'x'; 
    printf("%s\n", test); 
    return EXIT_SUCCESS; 
} 

在我看来,这应该打印abcdexghij。然而,它只是终止而不打印任何东西。

 
int main(void) { 
    char * test = "abcdefghijklmnopqrstuvwxyz"; 
    printf("%s\n", test); 
    return EXIT_SUCCESS; 
} 

但是,这工作得很好,所以我误解操纵C字符串或什么的概念?万一它很重要,我运行的是Mac OS X 10.6,它是我编译的32位二进制文​​件。

+1

我讨厌这么说,但这确实应该在某个C语言常见问题解答中......在Stack Overflow中已经有数十次或数百次的问题了。 – ephemient 2009-09-21 19:44:44

+0

如果之前已询问过此问题,我很抱歉,但无法找到答案。我确实首先阅读了函数参考和所有内容,但是我确实没有看到我做错了什么。你能指点我这样一个C FAQ吗? – fresskoma 2009-09-21 20:58:06

+3

@ x3ro:4年内没有人回答您有关C FAQ的问题? [comp.lang.c FAQ](http://www.c-faq.com/)非常好。第8部分涵盖字符和字符串,第8.5部分问题涉及问题1.32,它解决了您的具体问题。 – 2013-09-28 20:24:57

回答

4

accepted answer是好的,但不完整。

char * test = "abcdefghijklmnopqrstuvwxyz"; 

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

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

注意,在C++中,字符串实际上是const,和上面的声明是非法的。在C或C++,最好声明test为指针,以常量char

const char *test = "abcdefghijklmnopqrstuvwxyz"; 

所以编译器会警告你,如果你尝试通过test修改数组。

(由于历史原因,C字符串文字不是const在1989 ANSI C标准之前const关键字不存在要求它被用于像你这样的声明中会使用更安全的代码但它会要求现有的代码进行修改,一些ANSI委员会试图避免的。你应该假装该字符串字面量const,尽管事实并非如此。如果你碰巧使用gcc,该-Wwrite-strings选项将导致编译器把字符串文字为const - 这使得GCC不符合)

如果你希望能够修改字符串。指的是,你可以将其定义是这样的:

char test[] = "abcdefghijklmnopqrstuvwxyz"; 

编译器着眼于初始确定test需要有多大是。在这种情况下,test将是char[27]类型。字符串文字仍然指的是一个匿名的大部分只读数组对象,但它的值是复制到test中。 (在用于初始化一个数组对象的初始化字符串文字是其中的阵列不“衰减”的指针的上下文中的一个;所述其它的是当它的一元&sizeof操作数。)由于不存在进一步的对匿名数组的引用,编译器可以优化它。

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

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

调用者将收到一个指向不再存在的指针。如果需要参考范围之外的阵列,其中test被定义,则可以将其定义为static,也可以使用malloc分配它:

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 FAQ非常出色。第8部分涵盖字符和字符串,第8.5部分问题涉及问题1.32,它解决了您的具体问题。第6节介绍了数组和指针之间经常令人困惑的关系。

27

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

不可修改:

char * foo = "abc"; 

可修改:

char foo[] = "abc"; 
+0

哎呀 - 感谢您的编辑。 – Joe 2009-09-21 18:36:14

+1

foo [0] ='x'我的盒子上仍然存在segfaults – pm100 2010-02-07 01:11:37

4

你应该得到与初始化剂的类型相匹配的变量的类型的习惯。在这种情况下:

const char* test = "abcdefghijklmnopqrstuvwxyz"; 

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

3

字符串文字可能不可修改;最好假设他们不是。有关更多详细信息,请参阅here

1

做:

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

strdup作出修改副本。

是的,你应该真的测试strdup没有返回NULL。

+0

...并且如果您使用strdup(),则最终免费(bar)! – 2010-07-26 22:44:56