发新帖本帖赏金 50.00元(功能说明)我要提问
返回列表
打印
[开发工具]

嵌入式C指针大揭秘:地址咋还得配个“说明书”?

[复制链接]
932|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
DKENNY|  楼主 | 2025-5-13 17:38 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 DKENNY 于 2025-5-13 17:25 编辑

#申请原创#  #技术资源#   @21小跑堂

前言:指针为啥这么“麻烦”?

      哈喽,各位好!,今天想跟大家聊聊C语言里一个让新手抓狂的玩意儿——指针!  
      你是不是也有这疑问:在嵌入式C里,指针不就是个内存地址吗?比如一串 0x1000 这样的数字?既然地址就是个编号,为啥还得指定 int *char * 这样的数据类型?地址不就是一串0x开头的数字吗,干嘛搞那么复杂?今天就这个问题跟大家聊聊。。

1. 指针的本质真是地址吗?
      绝对是的!在嵌入式C里,指针的本质就是内存地址
      啥叫内存地址?打个比方,假设内存是个超级大的货架,上面有无数小格子,每个格子有个编号,比如 0x10000x1004 这样的十六进制数字。每个格子存1字节(8位)的数据。
      指针呢?就是一张小纸条,上面写着某个格子的编号,告诉你“去这个格子找东西”。  
举个例子:
int a = 42;
int *p = &a;
     - 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)



使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2025-05-14
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论
21小跑堂 2025-5-14 16:09 回复TA
图文结合,形象比喻,非常容易理解指针的含义。以简单易懂的文字配上清晰的图片,揭开指针的神秘面纱。 
沙发
呐咯密密| | 2025-5-14 14:56 | 只看该作者
通俗易懂,解释的非常好

使用特权

评论回复
板凳
dffzh| | 2025-5-14 15:10 | 只看该作者
C语言的灵魂是什么? 那就是指针;
能玩转指针的朋友绝对是C语言高手;
楼主分享的文章都比较详细,值得看看

使用特权

评论回复
地板
天罡星lmy| | 2025-5-14 16:00 | 只看该作者
写的真不错,点赞

使用特权

评论回复
5
cooldog123pp| | 2025-5-15 11:51 | 只看该作者
写的很不错啊。问下楼主你的状态图用什么软件做的,挺好看的,学习学习以后发帖用得上。

使用特权

评论回复
6
DKENNY|  楼主 | 2025-5-15 15:34 | 只看该作者
cooldog123pp 发表于 2025-5-15 11:51
写的很不错啊。问下楼主你的状态图用什么软件做的,挺好看的,学习学习以后发帖用得上。 ...

我这里的图片都是用draw.io这款软件手工绘制的哦~

使用特权

评论回复
7
永恒的一瞥| | 2025-5-20 19:26 | 只看该作者
C指针嘛!
写代码的时候,可以让程序跟随思维的主要方式之一。
话说,指针的用法还是太高级了,这一篇帖子还是远远不够的

使用特权

评论回复
发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

52

主题

94

帖子

10

粉丝