2009-09-07 81 views
0

我從來沒有真正做過多C,但我開始玩弄它。我正在寫下面的小片段,試圖理解C語言中關鍵結構/函數的用法和行爲。下面我寫了一個試圖理解char* stringchar string[]之間的區別以及字符串長度如何工作的區別。此外,我想看看是否可以使用sprintf連接兩個字符串並將其設置爲第三個字符串。我是C新手,有人可以解釋爲什麼這個字符串的大小可以改變嗎?

我發現的是,我用來存儲其他兩個連接的第三個字符串必須使用char string[]語法來設置,否則二進制文件將會與SIGSEGV (Address boundary error)一起死亡。使用數組語法設置它需要一個大小,所以我最初通過將其設置爲其他兩個字符串的組合大小來開始。這似乎讓我足夠好地進行連接。

出於好奇,我嘗試將「連接」字符串擴展爲比我分配的大小更長。令我驚訝的是,它仍然有效,並且字符串大小增加了,並且可能是printf'很好。

我的問題是:爲什麼會發生這種情況,它是無效的還是有風險/缺點?此外,爲什麼char str3[length3]有效,但當sprintf行嘗試執行時,char str3[7]會導致「SIGABRT(中止)」?

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

void main() { 
    char* str1 = "Sup"; 
    char* str2 = "Dood"; 

    int length1 = strlen(str1); 
    int length2 = strlen(str2); 
    int length3 = length1 + length2; 

    char str3[length3]; 
    //char str3[7]; 

    printf("%s (length %d)\n", str1, length1);   // Sup (length 3) 
    printf("%s (length %d)\n", str2, length2);   // Dood (length 4) 
    printf("total length: %d\n", length3);    // total length: 7 
    printf("str3 length: %d\n", (int)strlen(str3));  // str3 length: 6 
    sprintf(str3, "%s<-------------------->%s", str1, str2); 
    printf("%s\n", str3);        // Sup<-------------------->Dood 

    printf("str3 length after sprintf: %d\n",   // str3 length after sprintf: 29 
      (int)strlen(str3)); 
} 

回答

6
void main() { 
    char* str1 = "Sup"; // a pointer to the statically allocated sequence of characters {'S', 'u', 'p', '\0' } 
    char* str2 = "Dood"; // a pointer to the statically allocated sequence of characters {'D', 'o', 'o', 'd', '\0' } 

    int length1 = strlen(str1); // the length of str1 without the terminating \0 == 3 
    int length2 = strlen(str2); // the length of str2 without the terminating \0 == 4 
    int length3 = length1 + length2; 

    char str3[length3]; // declare an array of7 characters, uninitialized 

到目前爲止好。現在:

printf("str3 length: %d\n", (int)strlen(str3));  // What is the length of str3? str3 is uninitialized! 

C是一種原始語言。它沒有字符串。它所具有的是數組和指針。字符串是一種約定,而不是數據類型。按照慣例,人們同意「一個字符數組是字符串,並且字符串以第一個空字符結尾」。所有的C字符串函數都遵循這個約定,但它是一個約定。簡單地假設你遵循它,否則字符串函數將會中斷。

所以str3不是一個7個字符的字符串。它是一個由7個字符組成的數組。如果您將它傳遞給一個需要字符串的函數,那麼該函數將查找以查找字符串的結尾。 str3從未初始化,所以它包含隨機垃圾。在你的情況下,顯然,在第6個字符後有,所以strlen返回6,但這並不能保證。如果它不在那裏,那麼它會讀取數組的末尾。

sprintf(str3, "%s<-------------------->%s", str1, str2); 

然後又出錯了。您試圖將字符串"Sup<-------------------->Dood\0"複製到7個字符的數組中。這不適合。當然C函數不知道這一點,它只是複製到數組的末尾。未定義的行爲,並可能崩潰。

printf("%s\n", str3); // Sup<-------------------->Dood 

在這裏,您嘗試打印存儲在str3的字符串。printf是一個字符串函數。它不關心(或知道)你的數組的大小。它被賦予一個字符串,並且像所有其他字符串函數一樣,通過查找'\0'來確定字符串的長度。

9

此行是錯誤的:

char str3[length3]; 

你不採取終止零進去。它應該是:

char str3[length3+1]; 

您還試圖獲取str3的長度,但尚未設置。

此外,該行:

sprintf(str3, "%s<-------------------->%s", str1, str2); 

會溢出你STR3分配的緩衝區。確保分配足夠的空間來容納完整的字符串,包括終止零。

1

您的str3太短 - 您需要爲空終止符添加額外的字節,並且「< -------------------->」字符串的長度。

出於好奇,不過,我想 擴大「串聯」字符串 比大小我分配 更長。令我驚訝的是,它 仍然工作,字符串大小 增加,並可能printf'd罰款。

行爲是未定義的,所以它可能會或可能不會segfault。

1

strlen返回沒有尾隨NULL字節的字符串長度(\0,0x00),但是當您創建一個變量來存放組合字符串時,需要添加該1個字符。

char str3[length3 + 1]; 

...你應該全部設置。

3

而不是嘗試通過試錯來學習C,我建議你去當地的書店買一本「C編程入門」的書。你最終會以這種方式更好地瞭解這門語言。

沒有什麼比一半瞭解C的程序員更危險!

+0

我有一對夫婦在我身邊:) - 我喜歡試驗,但 - 如果我很好奇如何工作,我會盡力找出答案。另外,雖然我感謝你推薦學習C的方法,但這並沒有真正回答這個問題...... – 2009-09-07 11:51:30

1

C字符串是「\ 0」結束,要求爲一個額外的字節,所以至少你應該做

char str3[length3 + 1] 

將做的工作。

0

在sprintf()ypu正在寫入超出分配給str3的空間。這可能會導致任何類型的未定義行爲(如果你很幸運,它會崩潰)。在strlen()中,它只是從您指定的內存位置搜索NULL字符,並且它正在第29個位置中找到一個。它也可以是129,即它會表現得非常不正常。

3

你必須明白的是C實際上並沒有字符串,它有字符數組。此外,字符數組沒有關聯的長度信息 - 相反,字符串長度是通過迭代字符直到遇到空字節來確定的。這意味着,每個char數組的長度至少應爲strlen + 1個字符。

C不執行數組邊界檢查。這意味着你盲目調用的函數會相信你已經爲你的字符串分配了足夠的空間。如果情況並非如此,那麼最終可能會超出爲字符串分配的內存範圍。對於分配了char數組的堆棧,您將覆蓋局部變量的值。對於堆分配的字符數組,您可以寫入超出應用程序的內存區域。在任何一種情況下,最好的情況是你會立即出錯,最糟糕的情況是出現正在工作,但事實上並非如此。

至於分配,也可以不寫這樣的事情:

char *str; 
sprintf(str, ...); 

,並期望它的工作 - str是未初始化的指針,因此「沒有定義」的值,這在實踐中意味着「垃圾」。指針是內存地址,因此嘗試寫入未初始化的指針是嘗試寫入隨機內存位置。不是一個好主意。相反,你想要做的是一樣的東西:

char *str = malloc(sizeof(char) * (string length + 1)); 

其價值分配存儲的N + 1個字符,並存儲指針指向存儲在str中。當然,爲了安全起見,你應該檢查malloc是否返回null。當你完成後,你需要打電話給free(str)。

你的代碼與數組語法一起工作的原因是因爲作爲局部變量的數組是自動分配的,所以實際上有一個空閒的內存片。這通常不是未初始化指針的情況。

至於字符串的大小如何改變的問題,一旦你瞭解了關於空字節的位,就會變得很明顯:你需要做的所有改變字符串的大小都是空字節。例如:

char str[] = "Foo bar"; 
str[1] = (char)0; // I'd use the character literal, but this editor won't let me 

此時,由strlen報告的字符串長度將恰好爲1。或者:

char str[] = "Foo bar"; 
str[7] = '!'; 

之後strlen可能會崩潰,因爲它會繼續嘗試從數組邊界之外讀取更多字節。它可能遇到空字節然後停止(當然,返回錯誤的字符串長度),否則它可能會崩潰。

我已經寫完了一個C程序,所以希望這個答案在許多方面都是不準確和不完整的,這在評論中將毫無疑問地指出。 ;-)

0

幾個重點:

  • 只是因爲它的工作原理並不意味着它是安全的。經過緩衝區的末尾始終是不安全的,即使它在您的計算機上正常工作,它可能會在不同的操作系統,不同的編譯器或第二次運行下失敗。
  • 我建議你將char數組看作容器,並將字符串作爲對象存儲在容器中。在這種情況下,容器必須比它所容納的對象長1個字符,因爲需要「空字符」來指示對象的結尾。容器是固定大小的,並且對象可以改變大小(通過移動空字符)。
  • 第一個數組中的空字符表示字符串的結尾。數組的其餘部分未使用。
  • 您可以在char數組中存儲不同的東西(例如數字序列)。這取決於你如何使用它。但是,字符串函數(例如printf()strcat())假定在那裏存在以空字符結尾的字符串。
相關問題