虽然相比于其他高级编程语言,C语言的语法非常简单,但是它指针部分确实比较抽象,事实上,C语言的指针语法是不少初学者的噩梦,网络上有很多关于指针的有趣问题,前几天有读者向我问了下面这样的问题。
C语言为什么要分多级指针?C语言为什么要分多级指针?
这位读者认为,C语言中的指针其实就是用于存储特定变量地址的,下面是一段C语言代码:
inta=5;
int*b=a;
指针b存储了变量a的地址,没有什么问题。现在,假设需要存储指针b的地址,那么C语言代码可以按照下面这样写:
inta=5;
int*b=a;
int**c=b;
此时内存布局如下图所示,请看
C语言程序内存布局显然,二级指针c指向了指针b的地址。一切似乎都很自然,但是这位读者有疑问:既然C语言中的指针其实就是用于存储特定变量地址的,那为什么还要分多级指针呢?
详细来说,变量c里存放的起始是b的地址,而b的地址和a的地址在数值上并无区别,因此将c定义为int*类型,按理说足以存储b的地址,因此下面这样的C语言代码应该可以正常实现需求的:
inta=5;
int*b=a;
int*c=b;
但是,为什么上面这几行C语言代码会产生警告呢?也即为什么编译器认为int*c=b;是不妥的?
为什么上面这几行C语言代码会产生警告讨论
首先应该指出,C语言中的指针并不仅仅用于存储变量的地址,它还可以描述程序如何解释某段内存的值。这一点在我之前的文章里已经较为深入的讨论过,感到迷惑的读者可以再回头看看。
例如,对于char类型的指针:char*pc=0x;,*pc表示地址0x处的sizeof(char)=1个字节。若是定义为int类型(假设占用4字节空间)指针:int*pi=0x;,则*pi表示地址0x处的sizeof(int)=4个字节。
此外,pc+1是指下一个char对象处,也即0x处,而pi+1则是值下一个int对象处,也即0x处。
再回到这位读者的问题,C语言标准并没有保证int*和int**指针具有相同的的大小,因此对于int**ppi;,*ppi表示的其实是sizeof(int*)个字节,而ppi+1则是指下一个int*对象处,也即0x+sizeof(int*)处。
C语言标准并没有保证int*和int**显然,一级指针pi和多级指针ppi是不同的。
此外,C语言中的多级指针在函数定义中也有不同的用途,例如下面这段C语言代码:
voidfoo(T*p){
*p=new_value();
}
voidbar(void){
Tval;foo(val);
}
T表示任意数据类型。如果将T改为指针的形式,相关C语言代码如下:
voidfoo(P**p){
*p=new_value();
}
voidbar(void){
P*val;foo(val);
}
这两段C语言代码本质上其实完全相同这两段C语言代码本质上其实完全相同,只是数据类型在表现上有所差异:形参p总是比变量val多一级间接寻址。
可见,C语言中的多级指针和一级指针还是有所差异的。
不过,要是仅考虑这位读者所说的C语言中的指针其实就是用于存储特定变量地址的,那么使用一级指针完全足够了,毕竟C语言程序中的地址其实就是一个整数而已。事实上,只要保证没有精度损失,使用任意数据类型存储地址,都是没有问题的。
再考虑开头的问题:
inta=5;
int*b=a;
如果仅仅需要存储指针b的地址,使用void*指针也是可以的:
void*p=b;
甚至,使用long整数类型存储都是可以的:
longp=(long)b;
这里假设long类型的宽度大于或者等于指针类型的宽度。
要保证没有精度损失
最后
这里再强调一次,存储指针地址时,使用一级int*指针,void*指针,甚至long类型都是可以的,但是若想正确引用原本地址指向的内容,则仍需将其转换为应有的格式。
例如,我们使用p存储指针b的地址,实际上最终需要索引的有意义的数值是变量a存储的数值5。若是希望使用p索引数值5,那么强制类型转换是不可避免的:
int**ppi=(int**)p;
原因其实我们都讨论过了,C语言程序解释地址指向的内存时,是需要我们提供给其解释方式的,而指定其原本数值类型的目的就在于此。
点个