本帖最后由 DKENNY 于 2025-5-13 17:25 编辑
#申请原创# #技术资源# @21小跑堂
前言:指针为啥这么“麻烦”?
哈喽,各位好!,今天想跟大家聊聊C语言里一个让新手抓狂的玩意儿——指针!
你是不是也有这疑问:在嵌入式C里,指针不就是个内存地址吗?比如一串 0x1000 这样的数字?既然地址就是个编号,为啥还得指定 int *、char * 这样的数据类型?地址不就是一串0x开头的数字吗,干嘛搞那么复杂?今天就这个问题跟大家聊聊。。
1. 指针的本质真是地址吗?
绝对是的!在嵌入式C里,指针的本质就是内存地址!
啥叫内存地址?打个比方,假设内存是个超级大的货架,上面有无数小格子,每个格子有个编号,比如 0x1000、0x1004 这样的十六进制数字。每个格子存1字节(8位)的数据。
指针呢?就是一张小纸条,上面写着某个格子的编号,告诉你“去这个格子找东西”。
举个例子:
- a 是个整数,值是 42,假设存在内存的 0x1000 格子里。
- p 是指针,里面存的值就是 0x1000 这个地址。
- 用 *p,你就能去 0x1000 拿数据,看到 42。
可以形象的说,指针就是个“导航”,告诉你宝贝藏在内存哪个格子。比如 0x1000 就是个坐标,指针的任务就是带你去那儿。但光知道坐标不够,你还得知道格子里是啥宝贝,咋拿出来!
2. 地址就是0x数字,为啥还得指定数据类型?
你肯定想:地址不就是 0x1000 这样的数字吗?直接用不就行了?为啥还得写 int *、char *?
答案是:地址只告诉你“去哪个格子”,但不告诉你“格子里是啥”“拿多少”“咋拿”!数据类型就像个“说明书”,告诉编译器这些关键信息。
在嵌入式C里,类型尤其重要,因为你常直接戳硬件寄存器,弄错类型可能让设备直接异常。那么开发中,类型这玩意儿为啥不能少呢?
(1)类型决定拿多少字节
内存按字节存,每个地址对应1字节。但不同数据占的字节数不同:
- char:1字节
- int:4字节(32位系统)
- uint32_t:4字节
- double:8字节
指针用的时候,编译器得知道从地址开始,读或写多少字节。类型就是“尺寸表”。
(2)类型决定咋解读数据
即使字节数一样,不同类型让编译器用不同方式解码数据。比如,4字节可以是:
- uint32_t:无符号整数
- float:浮点数
没类型,编译器不知道咋解码,可能读出乱码。
(3)类型决定指针咋跳
指针可以做运算,比如 p + 1。但“+1”不是加1字节,而是看类型:
- uint32_t *:p + 1 加 4 字节。
- uint8_t * :p + 1 加 1 字节。
在嵌入式C里,指针运算常用来遍历数组或寄存器组,步长错不得。
(4)类型帮你少犯错
类型让编译器查错,比如:
int a = 42;
float *p = &a; // 编译器报警:类型不对!
没类型,编译器发现不了错,可能读到垃圾数据,程序崩盘。
类型可以说是一个标签,告诉你抽屉里是苹果还是香蕉。标签错了,你拿香蕉当苹果啃,程序就“拉肚子”了!
3. 代码实验:亲眼看类型咋回事
下面给段能跑的嵌入式C代码,用不同类型指针戳同一个地址,让你看类型咋影响“拿多少”“咋解码”“跳多远”。
// 模拟内存,装点数据
uint8_t buffer[] = {0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00};
int main()
{
USARTInit();
// buffer的起始地址(实际地址系统给)
void *base_addr = buffer; // void指针,只存地址
printf("起始地址: %p\r\n\r\n", base_addr);
// 用不同类型指针戳同一地址
// (1) 32位整数指针(4字节)
uint32_t *p_uint32 = (uint32_t *)base_addr;
printf("uint32_t 指针:\r\n");
printf(" 地址: %p, 值: 0x%08X (%u)\r\n", p_uint32, *p_uint32, *p_uint32);
printf(" 下个地址 (p_uint32 + 1): %p\r\n\r\n", p_uint32 + 1);
// (2) 8位整数指针(1字节)
uint8_t *p_uint8 = (uint8_t *)base_addr;
printf("uint8_t 指针:\r\n");
printf(" 地址: %p, 值: 0x%02X (%u)\r\n", p_uint8, *p_uint8, *p_uint8);
printf(" 下个地址 (p_uint8 + 1): %p\r\n\r\n", p_uint8 + 1);
// (3) 浮点数指针(4字节)
float *p_float = (float *)base_addr;
printf("float 指针:\r\n");
printf(" 地址: %p, 值: %f\r\n", p_float, *p_float);
printf(" 下个地址 (p_float + 1): %p\r\n\r\n", p_float + 1);
// (4) void指针(没类型)
void *p_void = base_addr;
printf("void 指针:\r\n");
printf(" 地址: %p\r\n", p_void);
// 不能直接读值,会报错
// printf("值: %d\r\n", *p_void); // 错!
printf(" 下个地址 (p_void + 1): %p\r\n", (uint8_t *)p_void + 1); // 编译报错,强制转为uint8_t
while(1);
}
代码咋回事?
- buffer:8字节数组,装数据 {0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00},模拟内存。
- 指针们:用 uint32_t *、uint8_t *、float * 和 void * 指向 buffer 头地址。
- 干啥:
- 读值(*p),看类型咋影响解码。
- 算地址(p + 1),看步长差别。
跑出来啥?
这是在APM32F4系列上运行的结果。
看啥差别?
1. 拿多少:
- uint32_t * 拿4字节(0x12345678)。
- uint8_t * 拿1字节(0x78)。
- float * 拿4字节,但解码成浮点数。
2. 咋解码:
- 4 字节(0x78, 0x56, 0x34, 0x12),uint32_t 是 305419896,float 是 0x0(可能是该数据的存储格式和浮点数的二进制编码不匹配)。
3. 跳多远:
- uint32_t * 和 float * 跳4字节(0x2000002C)。
- uint8_t * 和 void * 跳1字节(0x20000029)。
4. void指针:
- 没法直接读值,证明没类型啥也干不了。
这么来看,同一个抽屉(0x20000028),用不同“钥匙”(类型)开,拿的东西(值)和下步位置(地址)都不一样!
4. 嵌入式C里类型为啥更关键?
在嵌入式C里,指针类型不是闹着玩的!我们常直接操作硬件寄存器,比如:
#define REG_ADDR 0x40000000
uint32_t *reg = (uint32_t *)REG_ADDR;
*reg = 0xDEADBEEF; // 写32位数据
- 寄存器要是32位的,错用 uint8_t * 只写1字节(0xEF),硬件可能直接“宕机”。
- 还有内存对齐:uint32_t 得在4的倍数地址(像 0x1000),用 0x1001 可能崩。
5. 没类型咋整?void指针行不行?
C语言有个“无类型”指针,叫 void *,但它很鸡肋,看看这段这段代码,其中就存在一些问题。// (4) void指针(没类型)
void *p_void = base_addr;
printf("void 指针:\n");
printf(" 地址: %p\n", p_void);
// 不能直接读值,会报错
// printf("值: %d\n", *p_void); // 错!
printf(" 下个地址 (p_void + 1): %p\n", p_void + 1);
- 不能直接读值(*p_void 报错),因为编译器不知道拿多少字节。
- 运算步长不明确(p_void + 1 通常是1字节,但不标准,有的编译器直接编译报错,像Keil的ARMCC)。
- 得转成具体类型才能用:
void *p = buffer;
uint32_t *q = (uint32_t *)p;
*q = 42; // 转类型后才能用
void * 就像一张只有地址的藏宝图,只告诉你位置,但没说明里面是什么数据、怎么取。你需要先指定类型(通过类型转换),否则无法正确访问数据!
6. 总结:地址为啥还得配类型?
- 指针就是地址,内存的格子编号(0x1000),在嵌入式C里尤其常用来戳寄存器。
- 类型是说明书,告诉你:
1. 拿多少字节(尺寸)。
2. 咋解码数据(整数、浮点数)。
3. 指针运算跳多远(步长)。
4. 防出错(类型检查)。
5. 嵌入式里不能错(硬件认类型)。
地址就像一个坐标,指向内存位置;类型是使用手册,告诉你数据的种类、读取多少字节以及如何解析!没有类型手册,你就无法正确取出内存中的数据!
附件:文章例程:
PointerTypeTemplate.zip
(782.07 KB)
|
图文结合,形象比喻,非常容易理解指针的含义。以简单易懂的文字配上清晰的图片,揭开指针的神秘面纱。