2009-09-21 52 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