为什么编译器在尝试修改char *字符串文字时没有检测到并产生错误?

时间:2012-01-24 03:57:06

标签: c standards c99 c11

假设以下两段代码:

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

上面的那个不起作用。

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

这个确实如此。

关于第一个,我理解字符串“hello world”可能存储在只读存储器部分中,因此无法更改。然而,第二个在堆栈上创建一个字符数组,因此可以修改。

我的问题是这样 - 为什么编译器没有检测到第一种错误?为什么不是C标准的那部分?这有什么特别的原因吗?

4 个答案:

答案 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"传递给采用(非constchar*的函数则需要诊断。该程序有效,但如果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是一种较旧的语言,具有宽大的传统,可在许多平台上运行。