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 = 堆内存第一个字节的值