声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值时一个常量,指向这段空间的起始位置。数组名是符号地址常量
(一个常量指针,这个值不能被改变),在编译时求值并存在编译器的符号表里面,其值就是个内存地址;所以可以认为程序没有给他分配空间,数组名只是代表了那个数组空间;与指针不一样,指针指向一块空间,同时指针本身也存储在某个空间;可以认为数组名存在在符号表里,符号表是编译器用的,我们管不到;p_arr和&p_arr值是一样的,本来对常量取地址是非法的,但是标准组织没有定对数组名取地址是非法还是合法,所以因编译器而异.
引用chinaunix上一个叫dump_crash会员的发言:
C专家编程里解释的很好:
经典的错误:在一个文件定义了数组,却在另外一个文件声明其为指针。代码如下:
/*File name: main.c*/
extern int* array; /*声明指针*/
int main(void)
{
array[0]=1; /*用指针访问数据*/
return 0;
}
/*File name: def.c*/
int array[5]={0}; /*定义的却是数组*/
1。编译器对数组名和指针变量的处理方式
编译器在编译时会产生一个符号表,记录了符号名和它的地址。对于指针变量,这显然很好理解。而数组名就不那么明显了,它仅仅是一个符号而已,何来地址?编译器是这样处理的,它记录了array[0]的地址;这和我们通常的理解也是一样的。
2。带下标形式的数组和指针寻址方式
(1)数组情形
char a[9]="abcdefgh";
...
c=a[i];
在编译期,会在符号表中创建这样一条记录:
name:a address:9980
要获取a[i]的值分两个步骤:
step 1:取得i的值并和9980相加
step 2:在内存地址(9980+i)处取其内容
(2)指针情形
char* p="abcdefgh";
...
c=p[i];
在编译期,会在符号表中创建这样一条记录:
name:p address:4624
要获取p[i]的值分三个步骤:
step 1:在内存地址4624处取其内容,比如说“5081”
step 2:取得i的值并和5081相加
step 3:在内存地址(5081+i)取其内容
现在有了这些知识,前面出现的错误就很好解决了。在def.c中定义array是一个int型数组,在符号表中就会有把array和array[0]的地址关联起来。而在main.c中却把把array看作是一个int型指针,于是乎按照上面介绍的指针寻址方式,把符号表中array的地址(即&array[0])中的内容当作指针来寻址,这显然是不对的。
上面程序运行的后果是core dumped,但是这种情况和下面的还不一样:
using namespace std;
void print(int arr[]){
cout<<arr[0]<<endl;
printf("0x%X\n",arr);
printf("ox%X\n",&arr);
}
main()
{
int a[4]={1,2,3,4};
printf("0x%X\n",a);
print(a);
}
在这个例子中,注意这里面虽然print的参数arr被声明为一个貌似指针一样的东西int arr[]
但是这个东西其实和你在栈上声明的int arr[5]已经有了本质的区别,区别在哪呢?请看:
- 在栈上声明 int arr[] 是办不到的,因为编译器不能在编译的时候确定这个array的空间大小,也不能分配空间
- 在栈上声明 int arr[5]的时候,arr本身是不占用任何空间的,他的大小是数组元素的大小总和。这个符号本身并不占任何空间,这个是和指针的本质区别
- 在print函数参数中声明的int arr[]已经丧失了上面说它在的栈上本质特点,他在本质上已经变成了一个指针,你可以用sizeof看到他在64位机器上占用的空间是8个字节。而且,这次它是占用空间的有地址的,这个可以从输出的结果上看出来。
所以,当main中调用print(a)的时候,把a的值传给了arr,这个时候编译器使用指针的方式来寻址是没有问题的,因为arr其实就是一个指针,他已经不再是一个数组名称!
以上讨论了,像int arr[]的这种声明
- 不能在栈上出现
- 在函数的参数列表里面声明的时候,作用等同于指针
- 最后说一下这个东西在struct/class这样的东西里面声明
比如有这样一个声明:
int32_t len;
int arr[];
};
先说结论:这种情况下,arr还是一个array,所以他不占用任何空间,他的存在就像是一个隐形的指针。因为他的存在不占用空间,然而你却可以像使用指针一样来使用他,你可以写obj.arr[0]=3(前提是你给obj分配了足够的空间),但是你却不能写obj.arr=&temp_integer,因为arr是一个arrar名称,是一个常量,不能被赋值。
但是,这就有了一个很尴尬的局面:
你似乎不能操作这个arr里面的内容,因为不论是在构造函数还是使用他的代码中,你都不能写arr=(int*)malloc(sizeof(int));
这个时候别急,你可以给test的对象分配更大的空间,比如:
test * obj = (test *) malloc(sizeof(test)+4)0;
这里后面+40的空间就可以被arr来使用,想要对它操作的话既可以把的值传给一个真正的指针来操作,也可以直接使用arr。
你可能会说,这种属于奇巧淫技,没处用,可惜你错了,redis中就是这么用的,
不信请看:http://redis.io/topics/internals-sds