假设以下两段代码:
char *c = "hello world";
c[1] = 'y';
上面那个不行。
char c[] = "hello world";
c[1] = 'y';
这个可以。
关于第一个,我知道字符串“hello world”可能存储在只读内存部分中,因此无法更改。然而,第二个在堆栈上创建了一个字符数组,因此可以修改。
我的问题是——为什么编译器不检测第一类错误?为什么那不是 C 标准的一部分?这有什么特别的原因吗?
C 编译器不需要检测第一个错误,因为 C 字符串文字不是const
.
6.4.5 第 5 段:
在翻译阶段 7 中,将一个字节或值为零的代码附加到由一个或多个字符串文字产生的每个多字节字符序列。然后使用多字节字符序列来初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。对于字符串字面量,数组元素的类型为char,并使用多字节字符序列的各个字节进行初始化;[...]
第 6 段:
如果它们的元素具有适当的值,则未指定这些数组是否不同。如果程序尝试修改这样的数组,则行为未定义。
(C11 不会改变这一点。)
所以字符串文字"hello, world"
的类型是char[13]
( not ),在大多数情况下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"
给采用 (non- const
)的函数char*
将需要诊断。print_string()
该程序是有效的,但如果试图修改 指向的字符串,它将表现出未定义的行为s
。
原因是历史的。Pre-ANSI C 没有const
关键字,因此无法定义一个接受 achar*
并承诺不修改其指向的函数的函数。在 ANSI C (1989) 中制作字符串文字const
会破坏现有代码,并且在标准的后续版本中没有进行此类更改的好机会。
gcc-Wwrite-strings
确实会导致它将字符串文字视为const
,但会使 gcc 成为不符合标准的编译器,因为它无法对此发出诊断:
const char (*p)[6] = &"hello";
("hello"
is of type char[6]
,so &"hello"
is of type char (*)[6]
,这与声明的类型不兼容。with ,被视为类型p
。)大概这就是为什么既不也不包括的原因。-Wwrite-strings
&"hello"
const char (*)[6]
-Wall
-Wextra
-Wwrite-strings
另一方面,触发警告的代码-Wwrite-strings
可能无论如何都应该被修复。编写 C 代码不是一个坏主意,因此无论有无-Wwrite-strings
.
(请注意,C++ 字符串文字是 const
,因为当 Bjarne Stroustrup 设计 C++ 时,他并不关心旧 C 代码的严格兼容性。)
编译器可以检测到第一个“错误”。
在现代版本的 gcc 中,如果您使用 -Wwrite-strings,您将收到一条消息,指出您无法分配 from const char*
to char*
。C++ 代码默认开启此警告。
这就是问题所在 - 第一个任务,而不是c[1] = 'y'
位。当然,获取 a 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 正确的代码,所以这不是 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]”类型,以便将 1 的地址复制到非“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
是检测此类错误的工具:将 aconst char*
分配给 a char*
。它还会标记 a char c = c[30];
(不再依赖于类型,但也会标记错误。)因为将 c 声明为const char*
. C 是一种较老的语言,具有宽大的传统并在许多平台上运行。