我有两个文件:
extern char *s;
int main()
{
puts(s);
}
char s[] = "hello";
我同时编译它们,没有错误。但是程序在运行时崩溃了。为什么? C语言规范的哪一部分说这是非法的?
答案 0 :(得分:6)
您调用未定义的行为,程序发生崩溃。
引自N1256 6.2.7兼容类型和复合类型
1如果类型相同,则两种类型具有兼容类型。附加规则 确定两种类型是否兼容在6.7.2中描述了类型说明符, 在6.7.3中用于类型限定符,在6.7.5中用于声明符。 [...]
2所有引用同一对象或函数的声明都应具有兼容的类型; 否则,行为未定义。
在典型的环境中,当程序运行时,存储的内容将被读作指针,因为声明表明指针在a.c
中,但实际上是字符串的一部分(如果指针的大小)是4个字节),它几乎没有机会成为有效的指针。因此,从该地址读取很有可能导致分段错误。
答案 1 :(得分:3)
如果你想真正知道为什么 确实崩溃(而不是为什么它不应该工作):
数组是内存中的一系列事物。所以用
char s[] = "hello";
此变量的内存布局如下所示(让我们说它从0x00123400开始,带有4个字节的指针):
0x00123400: 'h' <- address of s
0x00123401: 'e'
0x00123402: 'l'
0x00123403: 'l'
0x00123404: 'o'
0x00123405: '\0'
要获取字符串的地址,它只使用固定数字0x00123400。
指针保存其他内容的地址。如果你有:
char *s = "hello";
然后编译器会放置数组&#34; hello&#34;某处,然后填写地址:
0x00123400: 0x00 <- address of s
0x00123401: 0x56
0x00123402: 0x78
0x00123403: 0x9A
0x0056789A: 'h' <- what s points to
0x0056789B: 'e'
0x0056789C: 'l'
0x0056789D: 'l'
0x0056789E: 'o'
0x0056789F: '\0'
要获取字符串的地址,它从固定数字0x00123400开始,并读取该位置的数字。
现在,如果你的变量实际上是char[]
并且你告诉编译器它是char*
,那么它会把它当作指针。这意味着它将从变量的地址开始,读取那里的数字,并使用该数字作为字符串的地址。
那是多少?好吧,我确实说过:
0x00123400: 'h' <- address of s
0x00123401: 'e'
0x00123402: 'l'
0x00123403: 'l'
但那是谎言 - 我们都知道记忆只存储数字,而不是字母。它只是速记,所以人们不必记住ASCII表。 真正存储的内容是:
0x00123400: 0x68 <- address of s
0x00123401: 0x65
0x00123402: 0x6C
0x00123403: 0x6C
所以你的程序会读取0x68656C6C,然后它会尝试从地址0x68656C6C开始打印字符串,这很可能是一个无效的地址。
(注意:我在这个答案中忽略了字节顺序)