在C语言里经常将数组名当成指针或者将指针当成数组来使用,那么两者到底一样吗?
以下面代码为例
在a.c中定义一个数组
//在a.c文件中
char array[] =”1234567890”;
然后再main.c中声明这个数组
//在main.c文件中
#include <stdio.h>
extern char *array; //将array声明成指针
void main(int argc, char **argv) {
char a = array[2];
printf("%c\n", a);
}
可以看到在a.c中定义array为一个char型的数组,而在main.c中将array声明成一个char型的指针,这样的代码运行起来有问题吗?
通过测试,会发现这个程序运行的时候会死机。不能运行的原因,就是因为指针和数组是有区别的。
一、指针与数组的区别
这两者的区别体现在编译器对这两个不同类型变量的处理方法。
以下面定义的两个变量为例
char array[] = "1234567890";
char *p_array = "1234567890";
1. 在内存中的区别
这两个变量在编译后生成的符号表里的显示:
从上图中可以看出,编译器编译之后生成的符号表中,数组名array对应的地址就是数组所在的地址,而且其所占的大小也是数组的大小。如下图所示:
而编译器对于指针的处理,可以看出指针变量的大小是4个字节,也就是说指针变量所在的地址并不是字符串所在的位置,字符串的地址是指针变量的值。如下图所示,指针变量的地址是0x2000000C,而字符串的地址是0x08001A54,字符串的地址是p_array这个指针变量的值。
2. 代码运行的区别
通过查看编译后的汇编语言也能看出区别
将array声明成数组类型
当在main.c中将array声明为数组类型时
//在main.c中
extern char array[]; //声明为数组类型
int main()
{
char c;
c = array[2];
while(1);
}
生成的汇编语句如下:
其先将0x20000000(即array所在地址)保存到r0寄存器中,然后再将0x20000000这个地址加2,即0x20000002这个地址的值保存到r4寄存器中,从而获得array[2]的值。
将array声明为指针类型时
当在main.c中将array声明为指针类型时
//在main.c中
extern char *array; //声明为指针类型
int main()
{
char c;
c = array[2];
while(1);
}
生成的汇编代码如下:
其先将0x20000000保存到r0寄存器,然后再将0x20000000这个地址存储的值保存到r0寄存器,之后再将r0的值加2,再读取数据保存到r4寄存器中。
通过对比这两组汇编语句,发现指针生成的汇编语句比数组生成的汇编语句多了一句。对于数组变量,其取数据用的就是数组名所在的地址,而对于指针变量,其取数据用的是指针变量所在地址里的值。
二、开始处代码的问题
通过上面的对比,可以得出最开始处代码的主要问题是array的定义和声明不一致。
变量array在a.c中被定义成数组,因此在编译的时候是按照数组编译的,也就是说array的地址就是数组的地址,array所在地址存放的就是array数组的数据。但是在main.c中声明的时候却将array声明成了指针,所以编译器在编译main.c的时候则是将array当指针编译的,也就是说编译器认为array存放的是一个地址,这样的话,在main.c文件中调用char c=array[2]时,编译器也是将array当成指针处理的,所以在array内存的位置存放的是数据0x31323334(也就是字符’1’’2’’3’’4’),编译器会把这个数据当成指针所指向的地址,然后从这个地址取数据,这是一个非法地址,那么肯定是无法得到正确数据的,所以程序无法正确运行。
三、解决方法
解决方法也很简单,那就是“使声明和定义相匹配”。变量的定义是一个数组,那么声明的时候也要是数组,定义是指针,那么声明也要是指针。比如上面的代码中,在main.c中将声明改成
extern char array[];
四、总结
在应用的时候,我们总是将数组和指针混用,但是对于编译器来说,对这两种变量类型的处理方法是不同的。但是只要使变量的声明和定义相匹配就行,这样编译器就能够正确的处理数据了。