簡體   English   中英

為什么編譯器在嘗試修改char *字符串文字時沒有檢測到並產生錯誤?

[英]Why doesn't the compiler detect and produce errors when attempting to modify char * string literals?

假設以下兩段代碼:

char *c = "hello world";
c[1] = 'y';

上面的那個不起作用。

char c[] = "hello world";
c[1] = 'y';

這一個。

關於第一個,我理解字符串“hello world”可能存儲在只讀存儲器部分中,因此無法更改。 然而,第二個在堆棧上創建一個字符數組,因此可以進行修改。

我的問題是 - 為什么編譯器不會檢測到第一類錯誤? 為什么不是C標准的那部分? 這有什么特別的原因嗎?

C編譯器不需要檢測第一個錯誤,因為C字符串文字不是const

參考N9956 C99標准草案

6.4.5第5段:

在轉換階段7中,將值為零的字節或代碼附加到由字符串文字或文字產生的每個多字節字符序列。 然后使用多字節字符序列初始化靜態存儲持續時間和長度的數組,該數組足以包含序列。 對於字符串文字,數組元素的類型為char ,並使用多字節字符序列的各個字節進行初始化; [...]

第6段:

如果這些數組的元素具有適當的值,則這些數組是否不同是未指定的。 如果程序試圖修改此類數組,則行為未定義。

(C11不會改變這一點。)

所以字符串文字"hello, world"的類型為char[13]不是 const char[13] ),在大多數情況下轉換為char*

嘗試修改const對象具有未定義的行為,並且大多數嘗試這樣做的代碼必須由編譯器診斷(例如,您可以使用強制轉換來解決這個問題)。 嘗試修改字符串文字也有未定義的行為,但不是因為它是const (它不是); 這是因為該標准明確指出行為未定義。

例如,該程序嚴格遵守:

#include <stdio.h>

void print_string(char *s) {
    printf("%s\n", s);
}

int main(void) {
    print_string("Hello, world");
    return 0;
}

如果字符串文字是const ,那么將"Hello, world"傳遞給帶有(非常量) char* const將需要診斷。 該程序有效,但如果print_string()嘗試修改s指向的字符串,它將顯示未定義的行為。

原因是歷史性的。 前ANSI C沒有const關鍵字,因此沒有辦法定義一個帶有char*的函數,並承諾不修改它指向的內容。 在ANSI C(1989)中創建字符串文字const會破壞現有代碼,並且在標准的后續版本中沒有很好的機會進行這樣的更改。

gcc的-Wwrite-strings確實導致它將字符串文字視為const ,但是使gcc成為不合格的編譯器,因為它無法為此發出診斷:

const char (*p)[6] = &"hello";

"hello"的類型為char[6] ,因此&"hello"的類型為char (*)[6] ,它與聲明的p類型不兼容。使用-Wwrite-strings&"hello"被處理作為const char (*)[6]類型const char (*)[6] 。)大概這就是-Wall-Wextra包括-Wwrite-strings

另一方面,使用-Wwrite-strings觸發警告的代碼應該可以修復。 編寫C代碼並不是一個壞主意,因此無需使用和不使用-Wwrite-strings編譯即可進行編譯。

(請注意,C ++字符串文字 const ,因為當Bjarne Stroustrup設計C ++時,他並不關心舊C代碼的嚴格兼容性。)

編譯器可以檢測到第一個“錯誤”。

在現代版本的gcc中,如果使用-Wwrite-strings,您將收到一條消息,指出您無法從const char*分配給char* 對於C ++代碼,默認情況下此警告處於啟用狀態。

這就是問題所在 - 第一個任務,而不是c[1] = 'y'位。 當然取一個char* ,取消引用它並分配到解除引用的地址是合法的。

引用man 1 gcc

When compiling C, give string constants the type "const char[length]" so that
copying the address of one into a non-"const" "char *" pointer will get a warning.
These warnings will help you find at compile time code that can try to write into a
string constant, but only if you have been very careful about using "const" in
declarations and prototypes. Otherwise, it will just be a nuisance. This is why we
did not make -Wall request these warnings.

所以,基本上,因為大多數程序員在C的早期都沒有編寫const-correct代碼,所以它不是gcc的默認行為。 但它適用於g ++。

-Wwrite-strings似乎做你想要的。 可以發誓,這是-Wall一部分。

% cat chars.c 
#include <stdio.h>

int main()
{
  char *c = "hello world";
  c[1] = 'y';
  return 0;
}
% gcc -Wall -o chars chars.c          
% gcc -Wwrite-strings -o chars chars.c
chars.c: In function ‘main’:
chars.c:5: warning: initialization discards qualifiers from pointer target type

從手冊頁:

在編譯C時,給字符串常量指定類型“const char [length]”,這樣將一個地址復制到非“const”“char *”指針中就會收到警告。 這些警告將幫助您在編譯時找到可以嘗試寫入字符串常量的代碼,但前提是您在聲明和原型中使用“const”時非常小心。 否則,這只會​​令人討厭。 這就是為什么我們沒有讓-Wall請求這些警告。

編譯C ++時,警告不要將字符串文字轉換為“char *”。 對於C ++程序,默認情況下會啟用此警告。

請注意,“默認情況下啟用C ++”可能是我(和其他人)認為-Wall涵蓋它的原因。 還要注意為什么它不是-Wall一部分。

至於有關標准, C99 ,6.4.5項目6(鏈接PDF 63頁)記載:

如果這些數組的元素具有適當的值,則不確定這些數組是否是不同的。 如果程序試圖修改這樣的數組,則行為未定義。

char* c = strdup("..."); 會使c[1]明智。 刪除了C上的咆哮 )雖然智能編譯器可以/確實警告過這個,但傳統上C是機器附近,沒有(邊界/格式/ ...)檢查和其他這樣的“不必要的”開銷。

lint是檢測此類錯誤的工具:將const char*分配給char* 它也會標記char c = c[30]; (不再依賴於類型,但也解決了錯誤。)因為將c聲明為const char*會很好。 C是一種較舊的語言,具有寬大的傳統,可在許多平台上運行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM