824 字
2 分钟
cpp(八)——指针全解
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 不能再安全使用,这时候就是个野指针。二、指针与数组
- 数组名在表达式中通常会退化为首元素地址
int arr[4] = {10, 20, 30, 40};
int *p = arr;在大多数表达式中,arr 会转换为 &arr[0]。所以下面两种写法等价
int *p = arr;int *q = &arr[0];- 指针为何可以通过加法访问数组
//p = &arr[0],p + 1 = &arr[1]int arr[4] = {10, 20, 30, 40};
int *p = arr;
printf("%d\n", *p); // 10printf("%d\n", *(p + 1)); // 20printf("%d\n", *(p + 2)); // 30printf("%d\n", p[3]); // 40核心的原因就是:
如果 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); // 10printf("%d\n", *p); // 10printf("%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,而不是仅仅修改一个指针副本。
调用过程:
&ptr:把ptr自己的地址传进去。int **p:函数用二级指针接收这个地址。*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)); // 15printf("%d\n", operations[1](10, 5)); // 5printf("%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/ 部分信息可能已经过时









