extern

// 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 * pmovl 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引用的对象被你更换类型成了指针,你误以为没事,不了解编译器对二者是不同对待。