huyc

导航

C语言的指针小发现

众所周知,C语言是有指针的,指针本身又是一个数据。C语言的所有数据都是存储在容器中的,指针作为一种数据,自然应该有一个容器。
然而C语言的指针在两个地方是特别的,或者说有三种行为,其中两种比较怪。
第一种是纯数组,这种数据带占据连续的内存,没有间隙,可以随意用不同的指针转换之:
1 char arr[64];                    //64个char
2 int *arr1 = (int *)arr; //64/sizeof(int)个int
3 double *arr2 = (double *)arr; //64/sizeof(double)个double
更奇怪的是还能这样:
int (*arr3)[4] = (int (*)[4])arr;    //这个俨然就是一个列数为4的二维数组了
int *arr4 = (int *)arr3; //换回本来面目
这种做法是没问题的,数据转换只改变了解释方式,要有正确的行为就要求底层的内存是连续的一大块。
第二种是指针数组,数组的数据按指针的方式解释:
const char *argv[4] = {"a1", "a2", "a3", 0};   //定义一个数组,4个字符串存在别处
这种情况并不要求内存要完全连续,相比于整块的内存,这种数组的存储把数据分散开来,利于动态分配,但是不利于内存管理。访问起来也没整块内存方便,至少失去了自由解释数据的好处。
之所以要把它当做一个指针的特例提及,是因为的用法跟多维数组很相似:
int v2[2][3] = {{1, 2, 3}, {4, 5, 6}};
printf("%d\n", v2[0][2]); //这是二维数组的访问方式,实际上二维数组被视为v2内部保存了两个(int[3])类型的变量
int v11[3] = {1, 2, 3};
int v12[3] = {4, 5, 6};
int *pv1[2] = {v11, v12}; //这种情况是定义了保存两个(int *)的指针,指针指向俩数组,丢失了数组的长度信息,可以发现这个数组的行为与二维数组一样,但数据结构却可以完全不同
printf("%d\n", pv1[0][2]); //pv1[0]返回一个(int *),再接着访问第三个元素,由于(int *)没有数组的长度信息,这使得不整齐的数组成为可能
printf("%d\n", pv1[1][2]); //这样看起来也是没问题的,pv1自己保存了指针,亦即保存了下一个数组的位置
int (*pv2)[3] = &v11; //这样保存了数组的长度信息,但丢失了数组的位置信息,不能处理不连续的存储对象
printf("%d\n", pv2[0][2]); //pv2自己是指针,返回一个(int [3])
printf("%d\n", pv2[1][2]); //bug,pv2[1]等价于*(pv2+1),但下一个位置却不见得有期望的数据
第三种情况就是数组名,数组名是一个符号值,编译器认识它,但是内存却没它的份儿,由此也出现一些比较特殊的行为。
int d[4] = {1, 2, 3, 4};
这个初始化完成了一个数组,这个数组名字叫d,但是它的行为等价于指针,对它求值的结果是数组的第一个地址,因此,可以直接将它当做指针使用。
问题在于,msg它不是一个变量,它是常量,因此,它不应该有地址,对于一般的常量取地址是非法的,但偏偏它却可以,而且具有奇怪的行为:
int *pe = &1;  //error
for(int *pi = d; pi < *(&d+1); ++pi){ //前面一个d看起来就是(int *)类型,但是后面一个取地址显示d是一个(int [4])的数组变量,取地址才可能取出(int (*)[4])这种怪胎
printf("%d\n", *pi);
}
这里行为显示,d被编译器视为变量,具有int [4]的类型,可以隐式转换为int *,其取地址取出来的指针具有类型int (*)[4],但d本身却没有存储地址。&p的求值结果与p的数值一致,这种行为完全是编译器给一个常量强加的。
综合以上的说法,取两种奇怪的,一种正常的,指针就有三种行为。
还是老外说得好,计算机的数据其实就是"1/0"+上下文。

posted on 2011-09-25 22:42  huyc  阅读(427)  评论(0)    收藏  举报