mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
824 字
2 分钟
cpp(八)——指针全解
2026-10-30

CPP—指针全解#

又回到这里了啊,指针。

一、指针基础#

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

可以把内存想象成一排带编号的储物柜:

  • a 是一个柜子,里面放着整数 10。
  • &a 是这个柜子的编号,也就是地址。
  • p 是另一个变量,里面保存着 a 的地址。
  • *p 表示按照 p 保存的地址,找到那个柜子并读取里面的值。

*的含义有两种

int *p; // 声明:p 是一个 int 指针
*p = 20; // 解引用:修改 p 指向的内存

指针本身也是个变量,能够改变指向

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

空指针与野指针#

void *c=nullptr;//野指针。
int *p;
{
int a = 10;
p = &a;
}
// a 的生命周期已经结束,p 不能再安全使用,这时候就是个野指针。

二、指针与数组#

  1. 数组名在表达式中通常会退化为首元素地址
int arr[4] = {10, 20, 30, 40};
int *p = arr;

在大多数表达式中,arr 会转换为 &arr[0]。所以下面两种写法等价

int *p = arr;
int *q = &arr[0];
  1. 指针为何可以通过加法访问数组
//p = &arr[0],p + 1 = &arr[1]
int arr[4] = {10, 20, 30, 40};
int *p = arr;
printf("%d\n", *p); // 10
printf("%d\n", *(p + 1)); // 20
printf("%d\n", *(p + 2)); // 30
printf("%d\n", p[3]); // 40

核心的原因就是:

p+n=p的地址+n∗sizeof(∗p)p+n=p的地址+n*sizeof(*p)

如果 p 是 int*,而 sizeof(int) == 4,那么 p + 1 就向后移动 4 字节。

所以对于数组来说*(p+2)等价于p[2]

数组与指针并不相同#

int arr[4] = {10, 20, 30, 40};
int *p = arr;
printf("%zu\n", sizeof(arr));
printf("%zu\n", sizeof(p));

假设是 ARM64:

  • sizeof(arr) 通常是 4 * 4 = 16 字节。
  • sizeof(p) 通常是 8 字节。

数组是连续存放元素的一整块存储空间;指针只是一个保存地址的变量。

还有个区别

arr = p; // 错误,数组不能整体赋值
p = arr; // 可以,指针可以保存数组首元素地址

数组作为函数的参数#

void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d\n", arr[i]);
}
}
等价于void printArray(int *arr, int size);

这里的 int arr[] 作为函数参数时,会被调整为 int *arr。

数组本身不携带长度信息,所以需要附带长度。

三、多级指针#

二级指针#

int a = 10;
int *p = &a;
int **pp = &p;
printf("%d\n", a); // 10
printf("%d\n", *p); // 10
printf("%d\n", **pp); // 10

拆解一下pp:

pp=p的地址`,`*pp=指针p本身`,`**pp=p指的那块地址存的值

二级指针常见用处#

最常见的用途之一:让函数修改调用者的指针变量。

void allocateMemory(int **p) {
*p = new int(100);
}
int main() {
int *ptr = nullptr;
allocateMemory(&ptr);
printf("%d\n", *ptr); // 100
delete ptr;
}

为什么这里需要 int **?

因为我们希望函数修改的是 main() 中的 ptr,而不是仅仅修改一个指针副本。

调用过程:

  1. &ptr:把 ptr 自己的地址传进去。
  2. int **p:函数用二级指针接收这个地址。
  3. *p = new int(100):修改调用者的 ptr,让它指向新分配的内存。

三级指针#

多了一层间接访问而已。

int a = 10;
int *p = &a;
int **pp = &p;
int ***ppp = &pp;
printf("%d\n", ***ppp); // 10

四、函数指针#

保存函数入口地址的指针。

int add(int a, int b) {
return a + b;
}
int (*func)(int, int) = add;
int result = func(10, 20);
printf("%d\n", result); // 30

表示:

  • func 是一个指针。
  • 它指向一个函数。
  • 这个函数接收两个 int 参数。
  • 返回值是 int。

和正常函数对比一下,发现括号很重要。

int *func(int, int); // 函数,返回 int*
int (*func)(int, int); // 函数指针,指向返回 int 的函数

回调函数:函数指针作为参数#

int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int calculate(int a, int b, int (*operation)(int, int)) {
return operation(a, b);
}
int main() {
printf("%d\n", calculate(10, 5, add)); // 15
printf("%d\n", calculate(10, 5, sub)); // 5
}

calculate 不关心具体执行加法还是减法,它只负责调用传进来的函数。

这就是回调函数。

函数指针数组#

这样就形成了一个函数表。

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int (*operations[3])(int, int) = {
add,
sub,
mul
};
printf("%d\n", operations[0](10, 5)); // 15
printf("%d\n", operations[1](10, 5)); // 5
printf("%d\n", operations[2](10, 5)); // 50
IDA 伪代码可能的含义
*(_DWORD *)a1读取地址 a1 处的 4 字节
*(_QWORD *)a1读取地址 a1 处的 8 字节
*(_DWORD *)(a1 + 8)读取偏移 8 处的 4 字节
*(_QWORD *)(a1 + 0x10)读取偏移 0x10 处的 8 字节
**(_QWORD **)a1多级间接访问,需结合类型判断
(*(函数指针)...)可能是间接函数调用

举个例子

struct User {
int id;
int age;
char *name;
};
int getAge(User *user) {
return user->age;
}
user->age等价于
*(int *)((char *)user + 4)
也就是
return *(_DWORD *)(a1 + 4);
你应该能反推出:
1. a1 很可能是结构体指针。
2. a1 + 4 是结构体偏移 4。
3. 读取 4 字节,可能是一个 int 字段。
4. 如果结合其他字段访问,就能进一步还原结构体。

五、实战分析#

#include<iostream>
#include<string>
#include <cstring>
#include <cstdint>
//普通指针
int read_pointer(int* tmp){
if(tmp==nullptr)return 0;
return *tmp;
}
void write_pointer(int* tmp){
*tmp=6767;
}
void swap_p(int* a,int* b){
std::swap(a,b);
}
//数组与指针
int arr_sum(int* arr,int size){
int sum=0;
for(int i=0;i<size;i++){
sum+=arr[i];
}
return sum;
}
int find_target(int* arr,int index,int length){
if (arr == nullptr) return 0;
if (index < 0 || index >= length) return 0;
int* address=arr+index;
return *address;
}
//结构体与结构体指针
struct User{
int id;
int age;
int score;
char name[16];
};
//修改用户信息
void change_user(User* user,int age,int score,std::string name){
user->age=age;
user->score=score;
std::strncpy(user->name,name.c_str(),sizeof(user->name) - 1);
user->name[sizeof(user->name) - 1] = '\0';
}
//结构体数组遍历与条件查找
const User* find_user(const User* users,int count,int id){
if(count<=0||id<0)return nullptr;
for(int i=0;i<count;i++){
if(users[i].id==id){
return &users[i];
}
}
return nullptr;
}
//多级指针
int alloc_pointer(int** pp,int value){
if (pp == nullptr) return false;
int *p = new int(value);
if(p==nullptr)return false;
*pp=p;
return true;
}
//函数指针
typedef int (*Option)(int a,int b);
int add(int a,int b){
return a+b;
}
int sub(int a,int b){
return a-b;
}
static Option option[2] = {
add,
sub
};
// 根据索引选择函数
int calculateByIndex(
int a,
int b,
int index
) {
if (index < 0 || index >= 2) return -1;
Option operation = option[index];
return operation(a, b);
}
// ============================================================
// 6. 综合结构体:Request + Union + 函数指针
// ============================================================
union Payload {
int32_t number;
float value;
char name[16];
};
struct Request {
uint32_t type;
uint32_t length;
Payload payload;
};
//三种操作函数表
// 处理整数请求
static int handleInteger(Request* req) {
if (req == nullptr) return -1;
return req->payload.number + 100;
}
// 处理浮点数请求
static int handleFloat(Request* req) {
if (req == nullptr) return -1;
if (req->payload.value > 10.0f) {
return 200;
}
return 201;
}
// 处理字符串请求
static int handleString(Request* req) {
if (req == nullptr) return -1;
int length = 0;
for (int i = 0; i < 16; i++) {
if (req->payload.name[i] == '\0') break;
length++;
}
return length;
}
typedef int (*RequestHandler)(Request*);
// 请求处理函数表
static RequestHandler handlers[4] = {
nullptr,
handleInteger,
handleFloat,
handleString
};
// 统一请求处理入口
int processRequest(Request* req) {
if (req == nullptr) return -1;
if (req->type >= 4) return -1;
RequestHandler handler = handlers[req->type];
if (handler == nullptr) return -1;
return handler(req);
}
// 获取 Request 字段
uint32_t getRequestType(const Request* req) {
if (req == nullptr) return 0;
return req->type;
}
uint32_t getRequestLength(const Request* req) {
if (req == nullptr) return 0;
return req->length;
}
int32_t getRequestNumber(const Request* req) {
if (req == nullptr) return 0;
return req->payload.number;
}
int main() {
std::cout << "========== 1. 普通指针 ==========\n";
{
int x = 42;
std::cout << "read_pointer(&x) = " << read_pointer(&x) << " (期望 42)\n";
std::cout << "read_pointer(nullptr) = " << read_pointer(nullptr) << " (期望 0,你的实现返回空)\n";
int y = 0;
write_pointer(&y);
std::cout << "write_pointer 后 y = " << y << " (期望 6767)\n";
}
std::cout << "\n========== 2. 数组与指针 ==========\n";
{
int arr[] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]);
std::cout << "arr_sum(arr,5) = " << arr_sum(arr, len) << " (期望 15)\n";
std::cout << "find_target(arr,2,5) = " << find_target(arr, 2, len) << " (期望 3)\n";
std::cout << "find_target(arr,9,5) = " << find_target(arr, 9, len) << " (期望 0,越界返回空)\n";
std::cout << "find_target(nullptr,0,5) = " << find_target(nullptr, 0, len) << " (期望 0)\n";
}
std::cout << "\n========== 3. 结构体与结构体指针 ==========\n";
{
User u{};
u.id = 1;
change_user(&u, 20, 90, "Alice");
std::cout << "change_user 后: id=" << u.id
<< " age=" << u.age
<< " score=" << u.score
<< " name=" << u.name << " (期望 20,90,Alice)\n";
// 测试超长名字截断
change_user(&u, 21, 91, "ThisNameIsWayTooLongFor16Bytes");
std::cout << "超长名字截断后 name = " << u.name
<< " (长度 " << std::strlen(u.name) << ",期望 15)\n";
User users[3] = {
{1, 18, 80, "Tom"},
{2, 19, 85, "Jerry"},
{3, 20, 90, "Spike"},
};
const User* found = find_user(users, 3, 2);
if (found)
std::cout << "find_user(id=2) = " << found->name << " (期望 Jerry)\n";
else
std::cout << "find_user(id=2) = 未找到\n";
const User* notFound = find_user(users, 3, 99);
std::cout << "find_user(id=99) = " << (notFound ? "找到" : "nullptr") << " (期望 nullptr)\n";
}
std::cout << "\n========== 4. 多级指针 ==========\n";
{
int* p = nullptr;
bool ok = alloc_pointer(&p, 123);
std::cout << "alloc_pointer 返回 = " << ok << " (期望 1)\n";
if (p) {
std::cout << "*p = " << *p << " (期望 123)\n";
delete p; // ⚠️ 你的函数里 new 了,必须由调用方 delete
}
bool bad = alloc_pointer(nullptr, 1);
std::cout << "alloc_pointer(nullptr)= " << bad << " (期望 0)\n";
}
std::cout << "\n========== 5. 函数指针 ==========\n";
{
std::cout << "calculateByIndex(6,2,0) = " << calculateByIndex(6, 2, 0) << " (期望 8, add)\n";
std::cout << "calculateByIndex(6,2,1) = " << calculateByIndex(6, 2, 1) << " (期望 4, sub)\n";
// 注意:option 数组只有 2 个元素,但 calculateByIndex 检查的是 index >= 3,
// 传 index=2 会越界读 option[2],是 bug(见下方问题清单)
std::cout << "calculateByIndex(6,2,3) = " << calculateByIndex(6, 2, 3) << " (期望 -1, 越界)\n";
}
std::cout << "\n========== 6. Request + Union + 函数指针 ==========\n";
{
// type=1:整数
Request r1{};
r1.type = 1;
r1.payload.number = 5;
std::cout << "processRequest(整数5) = " << processRequest(&r1) << " (期望 105)\n";
// type=2:浮点,>10
Request r2{};
r2.type = 2;
r2.payload.value = 3.14f;
std::cout << "processRequest(浮点3.14)= " << processRequest(&r2) << " (期望 201)\n";
Request r2b{};
r2b.type = 2;
r2b.payload.value = 20.0f;
std::cout << "processRequest(浮点20) = " << processRequest(&r2b) << " (期望 200)\n";
// type=3:字符串
Request r3{};
r3.type = 3;
std::strncpy(r3.payload.name, "hello", sizeof(r3.payload.name) - 1);
std::cout << "processRequest(字符串hello) = " << processRequest(&r3) << " (期望 5)\n";
// type=0:handlers[0]=nullptr
Request r0{};
r0.type = 0;
std::cout << "processRequest(type=0) = " << processRequest(&r0) << " (期望 -1, nullptr handler)\n";
// type=4:越界
Request r4{};
r4.type = 4;
std::cout << "processRequest(type=4) = " << processRequest(&r4) << " (期望 -1)\n";
// nullptr
std::cout << "processRequest(nullptr) = " << processRequest(nullptr) << " (期望 -1)\n";
// getter
std::cout << "getRequestType(&r1) = " << getRequestType(&r1) << " (期望 1)\n";
std::cout << "getRequestLength(&r1) = " << getRequestLength(&r1) << " (期望 0,未设置)\n";
std::cout << "getRequestNumber(&r1) = " << getRequestNumber(&r1) << " (期望 5)\n";
std::cout << "getRequestType(nullptr) = " << getRequestType(nullptr) << " (期望 0)\n";
}
std::cout << "\n========== 测试结束 ==========\n";
return 0;
}

六、ida里的结构体数组#

v32 = 0;
v103 = 0;
v100 = xmmword_2504;
v101 = xmmword_2514;
v102 = xmmword_2524;
*(_OWORD *)v98 = xmmword_24E4;
v99 = xmmword_24F4;
上面都是在定义初始化结构体数组
User users[3] = {
{1, 18, 80, "Tom"},
{2, 19, 85, "Jerry"},
{3, 20, 90, "Spike"},
};
源代码如上
while ( *(_DWORD *)((char *)v98 + v32) != 2 )//v98是结构体数组起点,每次移动28字节对应结构体的大小。
{
v32 += 28;
if ( v32 == 84 )
{
v33 = nullptr;
goto LABEL_6;
}
}
v33 = (char *)v98 + v32;
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

cpp(八)——指针全解
https://fatdog.20060113.xyz/posts/cpp-again8/
作者
神秘大胖狗
发布于
2026-10-30
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00