C 语言指针相关总结表

C 语言指针速查表,用于系统梳理 C 语言中变量地址、指针变量、解引用、数组指针关系、函数传参以及 FreeRTOS 队列传值/传指针的核心概念。

下面用这段代码作为基础示例:

int a = 10;
int *p = &a;
int **pp = &p;

1. 基础概念表

表达式 / 写法 名称 含义 示例结果
a 普通变量 变量 a 里的值 10
&a 取地址 变量 a 自身的地址 假设 0x1000
p 指针变量 存放一个地址,这里存的是 a 的地址 0x1000
&p 取地址 指针变量 p 自己的地址 假设 0x2000
*p 解引用 访问 p 指向的内存中的值 10
pp 二级指针 存放 p 的地址 0x2000
*pp 解引用一次 得到 p 的值,也就是 a 的地址 0x1000
**pp 解引用两次 得到 a 的值 10

2. *& 的用法表

符号 场景 含义 例子
& 表达式中 取变量地址 &a
* 声明时 表示“这是一个指针变量” int *p;
* 表达式中 解引用,访问指针指向的内容 *p
** 表达式中 二次解引用 **pp

注意:

int *p;

这里的 * 是声明语法,表示 p 是指针。

*p = 20;

这里的 * 是解引用,表示访问 p 指向的内存。


3. 常见写法对照表

写法 类型 含义
int a = 10; int 定义一个整型变量
int *p = &a; int * 定义一个指向 int 的指针,指向 a
int **pp = &p; int ** 定义一个指向 int * 指针的指针
char *s = "abc"; char * 指向字符串首字符
void *vp; void * 通用指针,可指向任意类型
NULL 特殊值 空指针,不指向有效对象

4. 指针和内存的关系表

内容 说明
普通变量 一般占一块内存,有自己的地址
指针变量 也是变量,所以它自己也占内存,也有自己的地址
指针的值 指针变量里存放的是另一个对象的地址
解引用 通过地址找到那块内存中的内容

例如:

int a = 10;
int *p = &a;

可以理解为:

对象 自己的地址 里面存的内容
a 0x1000 10
p 0x2000 0x1000

所以:

表达式 含义 结果
a 变量 a 的值 10
&a 变量 a 的地址 0x1000
p 指针变量 p 里面存的值 0x1000
&p 指针变量 p 自己的地址 0x2000
*p p 指向的内容 10

5. 数组和指针关系表

假设:

int arr[3] = {11, 22, 33};
int *p = arr;
表达式 含义 结果
arr 数组首元素地址 &arr[0]
&arr[0] 第一个元素地址 arr
p 指向首元素的指针 arr
*p 第一个元素值 11
p[1] 第二个元素值 22
*(p + 1) 第二个元素值 22
arr[2] 第三个元素值 33
*(arr + 2) 第三个元素值 33

注意:

arr[i] == *(arr + i)

数组下标本质上可以理解为“指针偏移后再解引用”。


6. 函数参数里的常见指针用法

写法 作用 说明
void f(int x) 传值 函数里改 x 不影响外部变量
void f(int *p) 传地址 可以通过 *p 修改外部变量
void f(int arr[]) 传数组 实际上退化为指针
void f(void *p) 通用接口 可接收任意类型地址

例子:

void addOne(int *p)
{
    (*p)++;
}

调用:

int a = 10;
addOne(&a);

执行后:

a == 11

原因是 addOne(&a)a 的地址传进去了,函数内部通过 *p 修改了 a 本身。


7. buf / &buf / *buf 对照表

假设:

uint8_t *buf = pvPortMalloc(100);

并假设:

buf 变量自己的地址:0x2000
buf 里面存的值:    0x3000
0x3000 是堆内存首地址

那么:

表达式 类型 含义 示例值
buf uint8_t * 堆内存首地址 0x3000
&buf uint8_t ** 指针变量 buf 自己的地址 0x2000
*buf uint8_t 堆内存第 1 个字节的值 例如 0x12
buf[0] uint8_t *buf 例如 0x12
buf[1] uint8_t 堆内存第 2 个字节 例如 0x34
&buf[0] uint8_t * 第 1 个元素地址 buf
&buf[1] uint8_t * 第 2 个元素地址 buf + 1

一句话:

表达式 含义
buf 地址
&buf “地址变量”自己的地址
*buf 地址指向的内容

8. FreeRTOS 队列里的对应关系

队列元素类型 创建方式 发送写法 实际复制的内容
整数值 xQueueCreate(10, sizeof(uint32_t)) xQueueSend(q, &value, ...) value 的值副本
结构体 xQueueCreate(10, sizeof(MyStruct)) xQueueSend(q, &msg, ...) 整个结构体副本
指针 xQueueCreate(10, sizeof(uint8_t *)) xQueueSend(q, &buf, ...) buf 里存的地址值
buffer 内容 xQueueCreate(10, 100) xQueueSend(q, buf, ...) buf 指向的 100 字节内容

核心规则:

FreeRTOS 队列不判断你传的是值还是指针。
它只知道:
1. 从哪个地址开始复制
2. 复制多少字节

复制多少字节由 xQueueCreate() 的第二个参数决定。


9. xQueueSend(q, &buf, ...)xQueueSend(q, buf, ...) 的区别

假设:

uint8_t *buf = pvPortMalloc(100);

发送指针值

QueueHandle_t q = xQueueCreate(10, sizeof(uint8_t *));
xQueueSend(q, &buf, portMAX_DELAY);

含义:

从 buf 变量自己的地址开始,复制 sizeof(uint8_t *) 个字节。
复制进去的是 buf 里面存的地址值。

也就是队列里保存的是:

0x3000

发送 buffer 内容

QueueHandle_t q = xQueueCreate(10, 100);
xQueueSend(q, buf, portMAX_DELAY);

含义:

从 buf 指向的堆内存开始,复制 100 字节。

也就是队列里保存的是 buffer 里的实际数据。


10. 常见错误表

错误写法 问题
int *p; *p = 10; 野指针,p 没有初始化
int *p = NULL; *p = 10; 空指针解引用
free(p); *p = 1; 释放后继续使用,悬空指针
返回局部变量地址 函数返回后局部变量地址失效
队列发送指针后立刻释放 接收方拿到的是无效地址
xQueueSend(q, value, ...) 把值当地址传,通常会崩溃
xQueueSend(q, buf, ...) 但队列元素大小是 sizeof(uint8_t *) 复制的是 buffer 前几个字节,不是指针值
xQueueSend(q, &buf, ...) 但队列元素大小是 100 会从 buf 变量自身位置复制 100 字节,越界风险很大

11. 指针使用时的安全规则

规则 说明
指针使用前必须初始化 不要解引用野指针
NULL 不能解引用 解引用前可判断 p != NULL
malloc / pvPortMalloc 后要检查返回值 分配失败会返回 NULL
谁申请,谁释放;或者明确所有权转移 避免内存泄漏和重复释放
释放后不要继续使用 释放后可以把指针置为 NULL
不要返回局部变量地址 栈变量离开作用域后失效
队列传指针时要保证数据生命周期 接收方使用前,指针指向的数据必须有效

12. 最常用记忆表

写法 作用
a 变量的值
&a 变量 a 的地址
p 指针变量里保存的地址
&p 指针变量 p 自己的地址
*p p 指向的内容
arr 数组首元素地址,多数表达式场景下成立
arr[i] i 个元素
*(arr + i) i 个元素

13. 最短总结

变量有值,也有地址。
指针变量也是变量,所以它自己也有地址。
指针里存的是地址。
* 指针 = 访问这个地址里的内容。
& 变量 = 拿到这个变量自己的地址。

对于:

uint8_t *buf = pvPortMalloc(100);

可以记成:

buf   = 堆内存地址
&buf  = buf 这个指针变量自己的地址
*buf  = 堆内存第一个字节的值