Pwn 基础之C/C++(三):一篇文章理解 C 指针
本篇目标:理解地址、指针、解引用、指针运算和二级指针。
预计阅读时间:20~25 分钟。
1. 内存地址
内存可以抽象成连续排列的字节,每个字节都有一个编号:
地址 内容
0x1000 0x41
0x1001 0x42
0x1002 0x43
这个编号就是地址。程序中的对象位于某个地址,并占用一定数量的连续字节。
2. 取地址 &
int number = 100;
printf("%p\n", (void *)&number);
&number 得到变量 number 的地址。%p 用来打印指针,通常转换为 void *。
3. 声明指针
int number = 100;
int *pointer = &number;
关系可以表示为:
pointer ─────→ number
100
pointer 自己也是变量,只不过它保存的是地址。
4. 解引用 *
printf("%d\n", *pointer);
*pointer = 200;
printf("%d\n", number);
*pointer 表示访问 pointer 所指地址处的 int 对象。
注意两种 *:
int *pointer; // 声明:pointer 是指针
*pointer = 10; // 表达式:访问指向的对象
5. 通过指针修改函数外变量
void change(int *value)
{
*value = 100;
}
int main(void)
{
int number = 10;
change(&number);
printf("%d\n", number);
return 0;
}
传递过程:
&number → number 的地址 → value → *value → number
C 仍然是按值传递,只是这次复制的“值”恰好是一个地址。
6. 指针类型为什么重要
char *byte_pointer;
int *int_pointer;
指针类型告诉编译器:
- 解引用时按什么类型解释数据;
- 通常读取多少字节;
- 指针加一时跨过多大的对象。
char * 解引用通常访问一个字节,int * 解引用通常访问一个 int。
7. 空指针
int *pointer = NULL;
NULL 表示当前不指向有效对象。解引用前应保证指针有效:
if (pointer != NULL) {
printf("%d\n", *pointer);
}
空指针不能被解引用。
8. void *
int number = 100;
void *raw = &number;
void * 可以保存对象地址,但不能直接解引用,因为编译器不知道目标类型。
int value = *(int *)raw;
强制转换只改变编译器的解释方式,不会改变底层字节,也不能保证解释一定合理。
9. 指针运算
int values[3] = {10, 20, 30};
int *pointer = values;
printf("%d\n", *pointer); // 10
printf("%d\n", *(pointer + 1)); // 20
如果 int 占 4 字节,pointer + 1 通常向后移动 4 字节,而不是 1 字节。
pointer + n
表示移动 n 个目标类型对象的距离。
指针运算只有在同一个数组对象及其尾后位置范围内才具有标准定义。
10. 二级指针
int value = 100;
int *pointer = &value;
int **pointer_to_pointer = &pointer;
pointer_to_pointer → pointer → value
访问结果:
printf("%d\n", value);
printf("%d\n", *pointer);
printf("%d\n", **pointer_to_pointer);
二级指针常用于:
- 修改调用者持有的指针;
- 表示字符串数组;
- 实现输出参数;
- 构建动态数据结构。
11. 用二级指针修改指针
#include <stdlib.h>
int create_number(int **output)
{
int *number = malloc(sizeof(*number));
if (number == NULL) {
return 0;
}
*number = 100;
*output = number;
return 1;
}
调用:
int *pointer = NULL;
if (create_number(&pointer)) {
printf("%d\n", *pointer);
free(pointer);
}
12. const 与指针
指向常量的指针:
const int *pointer;
不能通过该指针修改目标,但可以改变它的指向。
常量指针:
int *const pointer = &value;
可以修改目标,但不能改变指针自身保存的地址。
两者都限制:
const int *const pointer = &value;
13. 常见误区
指针不是目标对象
int value = 10;
int *pointer = &value;
pointer 保存地址,*pointer 才是目标值。
声明多个指针时要分别写 *
int *a, *b;
下面只有 a 是指针,b 是普通整数:
int *a, b;
地址相同不代表类型相同
同一个地址可以被转换成不同指针类型,但读取宽度和解释方法会随类型改变。
本篇练习
- 打印一个变量的值、地址以及指针自身的地址。
- 编写
swap(int *a, int *b)交换两个整数。 - 使用指针遍历整数数组。
- 编写函数,通过二级指针返回动态申请的整数。
- 分别声明“指向常量的指针”和“常量指针”,观察编译器限制。
本篇小结
- 指针保存地址;
&获取地址,*访问指向对象;- 指针类型决定访问和移动方式;
- C 可以通过传递地址间接修改外部对象;
- 二级指针保存另一个指针的地址;
- 指针是否有效必须由程序逻辑保证。
下一篇
下一篇把指针用于数组和字符串,并从逐字节观察整数引出大小端序。

说些什么吧!