《 一次让你学会并掌握指针》嵌入式-C语言高级-指针

好的,我们来一次把C语言指针彻底搞懂,尤其是从嵌入式开发最常用的角度去讲。

目标:看完这篇后,你能自信地说“我指针基本会了”,并且能看懂90%嵌入式C代码里的指针用法。

第一步:先把最本质的概念建立(很多人一辈子都没建立)

指针最核心的3句话(请背下来):

  1. 内存的本质是编号(从0开始的整数,通常用十六进制表示)
  2. 指针变量本质上是存地址的普通变量
  3. * 是“去它指向的地址里取东西/放东西”的操作符

用最直白的比喻:

内存就像一个超级长的信箱排(0号 ~ 几亿号)

int a = 100;     →  找一个空信箱(比如0x20000010),把100塞进去

int *p;          →  买一个记事本,专门用来记“哪个信箱有东西”

p = &a;          →  把a的信箱号(0x20000010)写到记事本上

*p = 200;        →  根据记事本上的地址去找信箱,把里面的东西改成200

printf("%d", *p);  →  根据记事本去信箱看现在里面是多少 → 输出200

第二步:最常用的5种指针类型(嵌入式最常考/最常写)

写法含义嵌入式最典型用法sizeof(这个类型)
int *p指向int的指针普通变量、数组元素、结构体成员通常4字节(32位)/8字节(64位)
uint8_t *p指向字节的指针(最常用!)操作寄存器、串口收发缓冲区、I2C/SPI数据4或8字节
const uint8_t *p只读字节指针指向常量区字符串、Flash里的查找表同上
uint8_t * const p指针本身不可改(常量指针)指向固定硬件寄存器地址同上
void *p万能指针(什么都能指)malloc、memcpy、硬件寄存器映射同上

第三步:嵌入式最经典的真实写法(强烈建议全部敲一遍)

// 1. 寄存器直接操作(最常见写法)
#define GPIOA_BASE    ((uint32_t)0x40020000)
#define GPIOA_ODR     (*(volatile uint32_t *)(GPIOA_BASE + 0x14))

// 等价写法(更推荐初学者这样理解)
volatile uint32_t * const GPIOA_ODR = (volatile uint32_t *)(0x40020000 + 0x14);

void LED_ON(void) {
    *GPIOA_ODR |= (1U << 5);   // 置位 PA5
}

void LED_OFF(void) {
    *GPIOA_ODR &= ~(1U << 5);  // 清零 PA5
}
// 2. 内存映射 + 结构体方式(现代STM32 HAL/LL最常用)
typedef struct {
    volatile uint32_t MODER;    // 0x00
    volatile uint32_t OTYPER;   // 0x04
    // ... 很多寄存器
    volatile uint32_t ODR;      // 0x14
} GPIO_TypeDef;

#define GPIOA   ((GPIO_TypeDef *)0x40020000)

void LED_Toggle(void) {
    GPIOA->ODR ^= (1U << 5);
}
// 3. 一级指针做函数参数(最常用传出多个值的方式)
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 用法
int x = 10, y = 20;
swap(&x, &y);   // 现在 x=20, y=10
// 4. 数组名就是指针(但不能 ++/-- 也不能赋值)
uint8_t buf[100];

uint8_t *p = buf;       // OK,等价于 &buf[0]
p++;                    // 指向 buf[1]
*(p + 3) = 0x55;        // 等价 buf[4] = 0x55

// 但下面这些都不合法:
// buf++;               // 错!数组名不能自增
// buf = p;             // 错!数组名不能被赋值
// 5. 指针数组 vs 数组指针(面试+驱动最爱考)

int arr[5];                // 普通数组

int *ptr_arr[5];           // 指针数组:5个int*  (每个元素都是指针)

int (*ptr_to_arr)[5];      // 数组指针:指向“5个int的数组” (很少用,但驱动里常见)

// 经典写法(函数指针数组)
void (*task_func[])(void) = {task1, task2, task3};
task_func[1]();   // 执行 task2()

第四步:嵌入式最容易踩的10个坑(避开这些能少debug很多天)

  1. 野指针:定义了指针但没初始化就用 *p
  2. 释放后继续使用:free() / 任务结束 / 栈空间回收后还用
  3. 忘记 volatile:寄存器/共享变量/中断标志没加 volatile
  4. 指针类型对不齐uint32_t* 去指 uint8_t 数组 → 硬件对齐硬故障
  5. 返回局部变量地址:return &local_var; → 栈被回收,变成野指针
  6. 数组传参退化int func(int a[]) 其实是 int func(int *a)
  7. const 位置搞错const int *p vs int * const p
  8. void* 强制转换前没对齐(尤其DMA、硬件寄存器)
  9. 多级指针没搞清char **argv 是指针的指针
  10. sizeof(指针) ≠ sizeof(它指向的东西)(64位系统尤其明显)

第五步:快速自测清单(做完这些说明你指针基本过关)

1. int a = 10;   int *p = &a;   ++p;   现在 *p 是多少?(答案:非法/未定义)

2. const int x = 100;   const int *p = &x;   *p = 200;   // 合法吗?

3. int arr[3] = {1,2,3};   int (*p)[3] = &arr;   现在 p+1 指向哪里?

4. void func(const uint8_t *buf, uint16_t len);   为什么 buf 用 const?

5. volatile uint32_t * const REG = (uint32_t *)0x40000000;   两个 const 分别限制什么?

6. char *str = "hello";   str[0] = 'H';   // 会发生什么?(嵌入式尤其危险)

7. 如何定义一个指向“函数的指针”,函数原型是 void delay_ms(uint32_t ms)?

8. 二维数组 int mat[4][5];   如何用指针遍历它?(两种常用写法)

答案可以自己先写出来,再对照网上或书验证。

如果你把上面内容全部理解 + 代码都敲一遍,恭喜你——嵌入式C里最难的指针部分你已经基本掌握了

有哪一块还觉得模糊?可以直接告诉我,我再给你针对性拆解(比如多级指针、函数指针、const volatile组合、DMA指针操作等)。

文章已创建 4026

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部