我们知道,声明和定义是不一样的。声明只是告诉编译器有这样一个对象存在,而定义不仅告诉编译器有这个对象,还为这个对象分配内存。同时我们也知道,指针只是指向数据,而数组则是保存数据。在很多情况下,数组和指针可以混用,比如
int arr[512];
int *p = arr;
p[0] = 1;
以上代码是可以正确运行的。但如果把指针和数组完全混为一谈,有时候还是会令人苦恼的。
一个诡异的BUG
请看如下代码:
/*文件1*/
int arr[512] = {1, 2};
/*文件2*/
extern int *arr;
...
arr[0] = 1;
乍一看,这是说得通的。但如果你的程序挂掉的话,你或许会庆幸内存没有被污染。
指针和数组的访问方式
数组的访问方式
char string[10] = "abcdefg";
...
c = string[2];
这段代码是怎样被执行的呢?假设string的地址为1234,
c = string[2];
- 第一步:取出偏移量2
- 第二步:把2和1234相加得到1236
- 第三步:把地址1236的内容’c’提取出来。
指针的访问方式
char *p = "abcdefg";
...
c = p[2];
假设常量”abcdefg”在内存中的地址为1234,指针p的地址为5678,地址为5678的内存单元中的值就会为1234。
c = p[2];
- 第一步:先取p的地址5678的内容,得到1234
- 第二步:取出偏移量2
- 第三步:把2和1234相加得到1234
- 第四步:后把地址1234的内容’c’提取出来。
我们可以看到,访问指针的步骤只比数组多了一步,那么该如何解释上面那个BUG呢?
BUG产生的原因
/*文件1*/
int arr[512] = {1, 2};
/*文件2*/
extern int *arr;
...
arr[0] = 1;
让我们聚焦到这个语句:
extern int *arr;
...
arr[0] = 1;
arr被声明是一个指针,于是我们将arr处的值取出来当做一个地址(就像上面的第1步)。但请注意到,在文件1的定义中,int arr[512]是实实在在分配了内存单元的。假设arr当前地址为1234,那么地址1234中的值是数字1,地址1235中的值是数字2。那么问题来了,在文件2中把1234处的值(一个数字)取出来当做一个地址是什么情况?这显然是不符合逻辑的
如何修正BUG?
答案是使声明和定义相匹配,数组就是数组,指针就是指针。
/*文件1*/
int arr[512] = {1, 2};
/*文件2*/
extern int arr[];
...
arr[0] = 1;
指针是C语言中最难正确理解和使用的部分之一,可能只有声明的语法比它更烦了。然而,它们是C语言中最重要的部分之一。专业的C程序员必须熟练掌握malloc()函数,并且学会用指针操纵匿名内存。