为什么编译器在尝试修改char *字符串文字时没有检测到并产生错误?
假设以下两段代码:
char *c = "hello world"; c[1] = 'y';
上面的那个不起作用。
char c[] = "hello world"; c[1] = 'y';
这一个。
关于第一个,我理解字符串“hello world”可能存储在只读存储器部分中,因此无法更改。 然而,第二个在堆栈上创建一个字符数组,因此可以进行修改。
我的问题是 – 为什么编译器不会检测到第一类错误? 为什么不是C标准的一部分? 这有什么特别的原因吗?
C编译器不需要检测第一个错误,因为C字符串文字不是const
。
参考C99标准的N1256草案 :
6.4.5第5段:
在转换阶段7中,将值0的字节或代码附加到由字符串文字或文字产生的每个多字节字符序列。 然后使用多字节字符序列初始化静态存储持续时间和长度的数组,该数组足以包含序列。 对于字符串文字,数组元素的类型为char ,并使用多字节字符序列的各个字节进行初始化; […]
第6段:
如果这些数组的元素具有适当的值,则这些数组是否不同是未指定的。 如果程序试图修改此类数组,则行为未定义。
(C11不会改变这一点。)
所以字符串文字"hello, world"
的类型为char[13]
( 不是 const char[13]
),在大多数情况下转换为char*
。
尝试修改const
对象具有未定义的行为,并且大多数尝试这样做的代码必须由编译器诊断(例如,您可以使用强制转换来解决这个问题)。 尝试修改字符串文字也有未定义的行为,但不是因为它是const
(它不是); 这是因为该标准明确指出行为未定义。
例如,该程序严格遵守:
#include 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 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是一种较旧的语言,具有宽大的传统,可在许多平台上运行。