分类: C/C++
2011-01-27 14:53:31
昨天同事写了一个程序,代码如下:
文件A:
char * p1 = "hello";
char p2[] = "world";
文件B:
#include
extern char * p1;
extern char * p2;
int main()
{
puts(p1);
puts(p2);
return 0;
}
用GCC编译以后问结果如何? 回答:输出hello和world。结果hello输出了,world输出的时候发生段错误,最后将puts(p2);语句改为puts(&p2);之后正确输出,哎,颜面扫地啊,静下心来好好琢磨一番,才有了这篇文章的诞生。
分析1:
在文件A中定义的数组和指针被编译器汇编为如下代码:
.file "a.c"
.globl p1
.section .rodata
.LC0:
.string "hello"
.data
.align
.type p1, @object
.size p1, 4
p1:
.long .LC0
.globl p2
.type p2, @object
.size p2, 6
p2:
.string "world"
.ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
.section .note.GNU-stack,"",@progbits
注意以p1和p2定义的差别,p1实际是一个long大小的空间,里面存储的是.LC0标签的位置,而.LC0标签的位置代表了字符串的位置,所以在文件b中使用以下语句: puts(p1);p1代表的是一个指针,通过指针里面的值可以找到字符串的位置,所以正确输出。
在使用puts(p2)语句的时候,其实p2在内存中的位置里面直接放的是字符串,从char p2[] = "world";到extern char * p2;其实发生了一个转变,编辑将p2当成纯粹的一个指针来使用,即在文件B中p2和p1在编译器的眼中是一样的,但其实定义的时候并不一样,这样从p2的位置取字符串的地址然后通过这个地址取的字符串显然会崩溃,因为p2中本来放的就是字符串,将字符串当成地址再取字符串,鬼知道会指到哪里去,所以崩溃。
但是使用puts(&p2);语句的时候能正确,是因为编译器将p2标签的地址传递puts函数,这样从p2的地址取的p2标签的位置,然后再p2标签的地方获取字符串就正确了,文件B生成的汇编代码可以看出差距:
// puts(p1)语句生成的汇编代码
movl p1, %eax
movl %eax, (%esp)
call puts
// puts(p2)语句生成的汇编代码
movl $p2, %eax
movl %eax, (%esp)
call puts
分析2
所以,通过分析1可以看出来这样的extern使用会出问题,但在函数中使用则没有问题,如下所示:
void foo(char [] p) { puts(p); }
void foo2(char * p) { puts(p); }
对foo和foo2函数传递指针和数据名没问题,因为编译器自己在里面会处理,有兴趣的可以看看生成的汇编代码。
总结: 在一个文件中定义字符数组,如果在别的文件中extern成指针的时候,感觉其实隐含了一个类型转换,但这个转换虽然没有警告,但不代表是正确的,程序运行的时候会崩溃!