// main.c
void swap();
int data[] = {1,2,3,4};
int * p = &data[0];
int main() {
swap();
return 0;
}
// swap0.c
extern int data[];
void swap() {
int temp = data[0];
data[0] = data[1];
data[1] = temp;
}
// swap1.c
extern int * data
...
// swap2.c
extern int * p
...
swap1.c出错,汇编如下
//extern int data[]
movl data, %eax
movl data+4, %edx
movl %eax, data+4
movl %edx, data
# extern int * data
movl data, %eax
movl (%eax), %edx
movl 4(%eax), %ecx
movl %edx, 4(%eax)
movl %ecx, (%eax)
# extern int * p
movl p, %eax
movl (%eax), %edx
movl 4(%eax), %ecx
movl %edx, 4(%eax)
movl %ecx, (%eax)
# main.c中的data和p
1 .file "main.c"
2 .globl data
3 .data
4 .align 4
5 .type data, @object
6 .size data, 16
7 data:
8 .long 10
9 .long 20
10 .long 30
11 .long 40
12 .globl p
13 .align 4
14 .type p, @object
15 .size p, 4
16 p:
17 .long data
18 .section .rodata
很明显,main.c的汇编代码中p指向的是data, 虽然我们觉得data和p值应该相等,但编译器却不这么认为。
在extern int * p中movl p, %eax先取出p的值(data地址)给eax, 然后再操作,也就是说data和p值是不同。 虽然我们在应用层中看到p==data是对的,那是因为编译器做了同样改动movl p, %eax,也就是所谓的“隐形转换”, 然后比较eax和data地址自然相等。
在编译器眼里,main.c中申明的data作为一个对象(object), p作为指针, 二者根本不是一回事,如果要对二者进行相关操作只能先帮你隐形转换,而这却被我们视为相同。
所以如果extern int * data, 编译器会在swap1.c中将data视为指针,实际声明在main.c中的是对象,但编译器以为你懂且有意为之,它必须按指针的操作方式处理data。
假定是64位的编码模式,即地址长度为64。swap1.c执行data=0,编译器认为对指针变量(64位)取0那就直接movl $0, data呗,则data[0]和data[1]就被覆盖为0。实际上如果extern要声明data为指针,任何指针都行,不局限于int, 反正data早已不是对象。
总之说白了就是extern引用的对象被你更换类型成了指针,你误以为没事,不了解编译器对二者是不同对待。