Chinaunix首页 | 论坛 | 博客
  • 博客访问: 135485
  • 博文数量: 22
  • 博客积分: 698
  • 博客等级: 上士
  • 技术积分: 265
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-17 09:53
文章分类
文章存档

2012年(2)

2011年(15)

2010年(5)

我的朋友

分类: 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成指针的时候,感觉其实隐含了一个类型转换,但这个转换虽然没有警告,但不代表是正确的,程序运行的时候会崩溃!

阅读(1230) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~