假设以下两段代码:
char *c = "hello world";
c[1] = 'y';
上面的那个不起作用。
char c[] = "hello world";
c[1] = 'y';
这个确实如此。
关于第一个,我理解字符串“hello world”可能存储在只读存储器部分中,因此无法更改。然而,第二个在堆栈上创建一个字符数组,因此可以修改。
我的问题是这样 - 为什么编译器没有检测到第一种错误?为什么不是C标准的那部分?这有什么特别的原因吗?
答案 0 :(得分:4)
C编译器不需要检测第一个错误,因为C字符串文字不是const
。
参考the N1256 draft of the C99 standard:
6.4.5第5段:在转换阶段7中,附加值为零的字节或代码 每个由字符串文字产生的多字节字符序列 或文字。然后使用多字节字符序列 初始化一个静态存储持续时间和长度的数组 足以包含序列。对于字符串文字, 数组元素的类型为 char ,并使用 多字节字符序列的各个字节; [...]
第6段:
未指明这些数组是否与它们不同 元素具有适当的值。如果程序试图 修改这样的数组,行为是未定义的。
(C11不会改变这一点。)
因此字符串文字"hello, world"
的类型为char[13]
(不是 const char[13]
),在大多数情况下会转换为char*
。< / p>
尝试修改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"
传递给采用(非const
)char*
的函数则需要诊断。该程序有效,但如果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
类型不兼容。使用{{ 1}},-Wwrite-strings
被视为&"hello"
类型。)大概这就是为什么const char (*)[6]
和-Wall
都不包含-Wextra
。
另一方面,触发-Wwrite-strings
警告的代码无论如何都应该被修复。编写C代码并不是一个坏主意,因此无论是否使用-Wwrite-strings
进行编译都无需编译。
(请注意,C ++字符串文字是 -Wwrite-strings
,因为当Bjarne Stroustrup设计C ++时,他并不关心旧C代码的严格兼容性。)
答案 1 :(得分:3)
编译器可以检测到第一个“错误”。
在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 ++。
答案 2 :(得分:2)
-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页)内容如下:
如果这些数组的元素具有不同的特征,那么它们是否是不同的 适当的价值观如果程序试图修改这样的数组,则行为是 理解过程科幻奈德。
答案 3 :(得分:-1)
char* c = strdup("...");
会让c[1]
明智。 (删除了C 上的rant)虽然智能编译器可以/确实警告过这种情况,但C传统上是机器附近,没有(bounds / format / ...)检查和其他这种“不必要的”开销。 / p>
lint
是检测此类错误的工具:const char*
已分配给char*
。它还会标记char c = c[30];
(不再依赖于类型,但也会解决错误。)因为将c声明为const char*
会很好。 C是一种较旧的语言,具有宽大的传统,可在许多平台上运行。