前言
不久以前尝试总结过《尝试说透C语言指针》的底稿,但觉得还是写的太复杂了,遂就放弃发布出去。
最近遇到一个事情,觉得void *
类型是组织文字的一个比较好的抓手和切入点,以及增补有助理解的图表,就再次有兴趣将其共享出来:)
基本思想
在阅读严蔚敏经典的《数据结构》一书中,有句特别话,引起了我的重点关注,“对于递归,切记想的太深、太远”,我想这句话对于理解指针也是非常适用的。
最简指针形式
基础
任何变量均有地址
- 指针变量作为变量
无论其是多少级的指针变量,其存储value的语义都是存储内存中的地址信息
- 对指针解引用
无论其是多少级的指针变量,解引用此指针变量意味着从其存储的内存地址开始读写,且按照指针解引用的目标类型操作(也就是减少一级的类型),目标类型主要限定为读写多少个字节信息的内容。
图解指针
多级指针
Level | 说明 |
---|---|
Type var | 零级,Type var变量 |
Type *Ptr | 一级,解引用获得Type var变量 |
Type **Ptr2 | 二级,解引用获得Type *Ptr变量 |
Type ***Ptr3 | 三级,解引用获得Type **Ptr2变量 |
… | 貌似可以很多级,更难理解了;理解的金钥匙在于指针指向地址是否存在上一级指针类型变量? |
图解多级指针
凶险的指针解引用
场景 | 异常说明 |
---|---|
悬空野指针 | 读取的位置悬空就出现非法访问 |
按照某错误类型或约定类型读取过多的字节信息 | 读取越界的非法访问 |
金钥匙void指针
指针作为存储内存地址变量,通常为机器字长度的字节信息,以利于能够存储内存中每个字节的地址信息,所以,指针类型相互赋值,可以没有任何字节信息损失,其sizeof都是同样大小的。
例如,作为万型void *
,你可以将各类指针类型转化为void *
,也可以将void *
按照约定转化为对应类型。
同时,void *
经常作为API接口的参数,是因为其不单可以作为某类型的万型指针,还可以变通使用为存储字节信息的容器,例如,整型等。
场景 | 说明 |
---|---|
int i ; void* pv = &i; int* pi = (int*)pv | 指针类型互转 |
void* pv = (void*)generic_var; | 作为容器存储变量的值 |
字节信息容器
typedef struct tagT_KeyInfo
{
uint8_t bType;
uint16_t wLen;
union
{
uint8_t data[5];
uint16_t wData;
uint32_t dwData;
};
} __attribute__((packed)) T_KeyInfo;
int f(void *arg)
{
//arg中存储keyInfo信息,所以,按照约定,用求地址运算得到T_KeyInfo的指针,然后解引用
T_KeyInfo tKeyInfo = *(T_KeyInfo*)&arg;
printf("Passed arg info: bType: %u, wLen: %u, wData: %u\n", tKeyInfo.bType, tKeyInfo.wLen, tKeyInfo.wData);
return 0;
}
int main(int argc, char* argv[])
{
printf("Hello world !\n");
T_KeyInfo tKeyInfo = {};
printf("Cannot convert to a pointer type from struct type!\n");
//void* p3 = (void*)tKeyInfo;
tKeyInfo.bType = 100;
tKeyInfo.wLen = sizeof(uint16_t);
tKeyInfo.wData = 60000;
/*
* 指针作为字节容器,可以将机器字长度信息传入
* 用求地址运算得到某类型的地址,如将其类型转化为void**,则解引用后就可以按照同类型操作,
* 将字节信息装载到void*中
*/
void* arg = *(void**)&tKeyInfo;
f(arg);
printf("Exit from program !\n");
return 0;
}
图解指针作为字节容器
void*指针作为信息容器为什么称其为精华
// assert(sizeof(VarType) == sizeof(void*))
void* arg = *(void\*\*)&var;
//反向
VarType var = *(VarType*)&arg
体现C语言的语言特性,在很低硬件层次上可以根据内存地址读写内存,而且读写的语义随类型而变!
指针所指向的内存,如果真正存储了目标,则无惧各种指针操作了 :)