指针转换方式详解-重定位表解析部分

文章目录

cpp 复制代码
#include <iostream>
#include <iomanip>
#include <windows.h>

// 模拟IMAGE_BASE_RELOCATION结构体
struct MY_BASE_RELOCATION {
    DWORD VirtualAddress;   // 4字节
    DWORD SizeOfBlock;      // 4字节
    WORD  TypeOffset[10];   // 20字节
};
typedef MY_BASE_RELOCATION* PMY_BASE_RELOCATION;

void printAddress(const char* method, void* addr) {
    std::cout << method << ": 0x" << std::hex << std::setw(8) << std::setfill('0')
              << reinterpret_cast<DWORD_PTR>(addr) << std::endl;
}

int main() {
    std::cout << "演示三种指针转换方式\n" << std::endl;
    
    // 创建一个测试结构体实例
    MY_BASE_RELOCATION relocBlock = {0};
    relocBlock.VirtualAddress = 0x00400000;
    relocBlock.SizeOfBlock = sizeof(DWORD) + sizeof(DWORD) + 4 * sizeof(WORD);  // 8字节头部 + 4个WORD
    
    // 填充一些测试数据
    for (int i = 0; i < 10; i++) {
        relocBlock.TypeOffset[i] = static_cast<WORD>(0x1000 + i * 0x100);
    }
    
    PMY_BASE_RELOCATION prelocTable = &relocBlock;
    
    std::cout << "原始地址:" << std::endl;
    printAddress("prelocTable指针", prelocTable);
    std::cout << "sizeof(MY_BASE_RELOCATION) = " << sizeof(MY_BASE_RELOCATION) << " 字节\n" << std::endl;
    
    // ================================
    // 1. 错误的方式:直接使用结构体指针加法
    // ================================
    std::cout << "1. 错误的方式:直接使用结构体指针 + 8" << std::endl;
    {
        // ❌ 错误!指针加8会实际加上 8 * sizeof(MY_BASE_RELOCATION) 字节
        PMY_BASE_RELOCATION wrongPtr = prelocTable + 8;
        printAddress("  prelocTable + 8", wrongPtr);
        
        // 计算实际偏移了多少字节
        DWORD_PTR wrongOffset = reinterpret_cast<DWORD_PTR>(wrongPtr) - 
                              reinterpret_cast<DWORD_PTR>(prelocTable);
        std::cout << "  实际偏移了: " << std::dec << wrongOffset << " 字节" << std::endl;
        std::cout << "  (应该是 8 字节,但实际是 " << 8 * sizeof(MY_BASE_RELOCATION) << " 字节)\n" << std::endl;
    }
    
    // ================================
    // 2. 正确方式1:转换为DWORD(整数)再加8
    // ================================
    std::cout << "2. 正确方式1:转换为DWORD再加8" << std::endl;
    {
        // ✅ 正确:转换为DWORD后,加8就是真正的加8字节
        DWORD address = reinterpret_cast<DWORD>(prelocTable);
        address += 8;
        PMY_BASE_RELOCATION correctPtr1 = reinterpret_cast<PMY_BASE_RELOCATION>(address);
        printAddress("  (DWORD)prelocTable + 8", correctPtr1);
        
        // 验证我们指向的是TypeOffset数组
        WORD* typeOffsetPtr = reinterpret_cast<WORD*>(correctPtr1);
        std::cout << "  指向的数据: 0x" << std::hex << *typeOffsetPtr 
                  << " (应该是第一个TypeOffset: 0x" << relocBlock.TypeOffset[0] << ")" << std::endl;
        
        DWORD_PTR offset1 = reinterpret_cast<DWORD_PTR>(correctPtr1) - 
                          reinterpret_cast<DWORD_PTR>(prelocTable);
        std::cout << "  实际偏移了: " << std::dec << offset1 << " 字节\n" << std::endl;
    }
    
    // ================================
    // 3. 正确方式2:转换为BYTE*(字节指针)再加8
    // ================================
    std::cout << "3. 正确方式2:转换为BYTE*再加8" << std::endl;
    {
        // ✅ 正确:BYTE*的加法以1字节为单位
        BYTE* bytePtr = reinterpret_cast<BYTE*>(prelocTable);
        bytePtr += 8;
        PMY_BASE_RELOCATION correctPtr2 = reinterpret_cast<PMY_BASE_RELOCATION>(bytePtr);
        printAddress("  (BYTE*)prelocTable + 8", correctPtr2);
        
        WORD* typeOffsetPtr = reinterpret_cast<WORD*>(correctPtr2);
        std::cout << "  指向的数据: 0x" << std::hex << *typeOffsetPtr 
                  << " (应该是第一个TypeOffset: 0x" << relocBlock.TypeOffset[0] << ")" << std::endl;
        
        DWORD_PTR offset2 = reinterpret_cast<DWORD_PTR>(correctPtr2) - 
                          reinterpret_cast<DWORD_PTR>(prelocTable);
        std::cout << "  实际偏移了: " << std::dec << offset2 << " 字节\n" << std::endl;
    }
    
    // ================================
    // 4. 正确方式3:转换为DWORD_PTR(平台安全)
    // ================================
    std::cout << "4. 正确方式3:转换为DWORD_PTR再加8" << std::endl;
    {
        // ✅ 正确:DWORD_PTR会根据平台自动调整大小(32位是4字节,64位是8字节)
        DWORD_PTR address = reinterpret_cast<DWORD_PTR>(prelocTable);
        address += 8;
        PMY_BASE_RELOCATION correctPtr3 = reinterpret_cast<PMY_BASE_RELOCATION>(address);
        printAddress("  (DWORD_PTR)prelocTable + 8", correctPtr3);
        
        WORD* typeOffsetPtr = reinterpret_cast<WORD*>(correctPtr3);
        std::cout << "  指向的数据: 0x" << std::hex << *typeOffsetPtr 
                  << " (应该是第一个TypeOffset: 0x" << relocBlock.TypeOffset[0] << ")" << std::endl;
        
        DWORD_PTR offset3 = reinterpret_cast<DWORD_PTR>(correctPtr3) - 
                          reinterpret_cast<DWORD_PTR>(prelocTable);
        std::cout << "  实际偏移了: " << std::dec << offset3 << " 字节\n" << std::endl;
    }
    
    // ================================
    // 5. 演示如何计算重定位项数量
    // ================================
    std::cout << "5. 演示计算重定位项数量" << std::endl;
    {
        // 原始代码中的计算方式
        DWORD sizeOfBlock = relocBlock.SizeOfBlock;
        DWORD numberOfEntries = (sizeOfBlock - 8) / 2;
        
        std::cout << "  SizeOfBlock = " << std::dec << sizeOfBlock << " 字节" << std::endl;
        std::cout << "  重定位项数量 = (" << sizeOfBlock << " - 8) / 2 = " << numberOfEntries << std::endl;
        
        // 实际验证
        std::cout << "\n  验证:遍历重定位项" << std::endl;
        BYTE* blockStart = reinterpret_cast<BYTE*>(prelocTable);
        WORD* typeOffsets = reinterpret_cast<WORD*>(blockStart + 8);  // 跳过头部8字节
        
        for (DWORD i = 0; i < numberOfEntries; i++) {
            std::cout << "  项[" << i << "] = 0x" << std::hex << typeOffsets[i] << std::endl;
        }
    }
    
    return 0;
}为我详细解释为什么三种转换方式是正确的,而第一种转换方式是错误的,我是初学者

你贴出的这段代码,是在用 C++ 模拟 Windows PE 文件中的基址重定位表 结构,核心目的是演示:如何正确地将指针从结构体头部偏移到紧接在头部后面的数据区。

我们先理清最基本的概念,再逐一分析四种写法。


一、前提:内存布局和结构体大小

你定义的结构体是:

cpp 复制代码
struct MY_BASE_RELOCATION {
    DWORD VirtualAddress;   // 4 字节
    DWORD SizeOfBlock;      // 4 字节
    WORD  TypeOffset[10];   // 20 字节(10 * 2 字节)
};

它的总大小是 4 + 4 + 20 = 28 字节。

在内存中,这三个字段是连续存放的:

偏移(字节) 字段 大小
0 VirtualAddress 4 字节
4 SizeOfBlock 4 字节
8 TypeOffset[0] 2 字节
10 TypeOffset[1] 2 字节
... ... ...

PE 真实的重定位表里,TypeOffset 的数量是不固定的,长度由 SizeOfBlock 推算。

这里用一个固定数组只是为了模拟。

你希望从结构体起始地址开始,偏移 8 个字节 ,跳过 VirtualAddressSizeOfBlock,直接到达 TypeOffset 数组的第一个元素。


二、为什么"第一种方式"是错的?

cpp 复制代码
PMY_BASE_RELOCATION wrongPtr = prelocTable + 8;

关键规则:指针加减整数,按它所指向的类型的大小为步长

prelocTable 的类型是 PMY_BASE_RELOCATION,也就是 MY_BASE_RELOCATION*

它"认为自己"指向的是一个完整的 MY_BASE_RELOCATION 对象(28 字节)。

因此:

cpp 复制代码
prelocTable + 8

并不意味着"地址值 + 8 字节",而是**"地址值 + 8 × 28 字节"**。

计算一下:

复制代码
实际偏移 = 8 × sizeof(MY_BASE_RELOCATION) = 8 × 28 = 224 字节

这直接跳到了很远的地方,完全越过了整个结构体,更谈不上指向 TypeOffset 了。

所以这是完全错误的做法。

你程序里打印的实际偏移也能验证这一点:

复制代码
实际偏移了: 224 字节
(应该是 8 字节,但实际是 224 字节)

三、三种正确方式的共同思路

既然直接使用"结构体指针 + 整数"会导致按类型大小放大,那么正确思路就是:

先把指针转换成一个"按字节运算"的类型,加上 8 字节,再转回需要的指针类型。

下面三种方式,本质都在做这件事,只是选择的中间类型不同。


✅ 方式 2:转换为 DWORD(整数)

cpp 复制代码
DWORD address = reinterpret_cast<DWORD>(prelocTable);
address += 8;
PMY_BASE_RELOCATION correctPtr1 = reinterpret_cast<PMY_BASE_RELOCATION>(address);

DWORD 就是一个 32 位无符号整数,不再是任何指针。

对它做 +8,就是纯粹的整数加法,永远只增加 8 个字节。

把整数 address + 8 再转回 PMY_BASE_RELOCATION,就得到了正确地址。

⚠️ 注意

这种方式在 64 位程序下是不安全的,因为 DWORD 只有 4 字节,而指针在 64 位下是 8 字节。

如果地址高 32 位非零,转为 DWORD 会截断高位,导致地址错误。


✅ 方式 3:转换为 BYTE*(字节指针)

cpp 复制代码
BYTE* bytePtr = reinterpret_cast<BYTE*>(prelocTable);
bytePtr += 8;
PMY_BASE_RELOCATION correctPtr2 = reinterpret_cast<PMY_BASE_RELOCATION>(bytePtr);

BYTE 在 Windows 下就是 unsigned char,大小恰好是 1 字节。

所以 BYTE* 指针的步长就是 1 字节。

对它 +8,就是真正移动 8 字节。

这种做法不仅正确,而且完全跨平台 ,因为 BYTE 的大小在所有平台上都是 1。


✅ 方式 4:转换为 DWORD_PTR(指针大小的整数)

cpp 复制代码
DWORD_PTR address = reinterpret_cast<DWORD_PTR>(prelocTable);
address += 8;
PMY_BASE_RELOCATION correctPtr3 = reinterpret_cast<PMY_BASE_RELOCATION>(address);

DWORD_PTR 是 Windows 定义的一种整数类型,它的大小和指针大小相同

  • 32 位程序:4 字节
  • 64 位程序:8 字节

因此无论编译成 32 位还是 64 位,都能完整保存指针数值,不会截断。

然后对它 +8 就是纯粹的字节偏移。

这是一种**"平台安全"**的整数做法。


四、三种正确方式的对比总结

方式 中间类型 步长 32/64 位安全 推荐度
1. 结构体指针 +8 结构体指针 28 ❌ 完全错误 绝不能用
2. DWORD +8 整数 1 ❌ 仅 32 位安全 仅限 32 位项目
3. BYTE* +8 字节指针 1 ✅ 完全安全 ⭐ 最直观、最通用
4. DWORD_PTR +8 整数 1 ✅ 完全安全 适合偏底层、模拟 PE 的代码

在你的场景(模拟 PE 加载器),很多经典代码会采用 BYTE* 方式DWORD_PTR 方式

对初学者来说,BYTE* 方式最容易理解:"我先把它当成字节看待,走 8 字节,再变回需要的类型。"


五、对代码整体的补充理解

代码末尾计算重定位项数量:

cpp 复制代码
DWORD numberOfEntries = (sizeOfBlock - 8) / 2;

这里 8 就是指 VirtualAddress + SizeOfBlock 这两个字段的总大小,也就是头部长度。
2 是每个重定位项的大小(WORD,2 字节)。

为什么要减 8?就是为了跳过头部,只用有效数据部分计算条目数------这与我们前面用指针偏移 8 字节的目的完全相同。

希望这个解释能帮你彻底理解指针算术的本质。如果还有不清楚的地方,可以继续问。

相关推荐
CHANG_THE_WORLD3 天前
PE 文件寻址基石:彻底理解 FOA 与 RVA 的转换
pe·rva·foa·可选头·节区
BestOrNothing_20153 天前
C++零基础到工程实战(5.2.4):指针与引用在函数传参、返回值与效率优化中的应用
c++·指针·引用·const·函数参数
Dlrb12114 天前
C语言-指针三
c语言·算法·指针·const·命令行参数
审判长烧鸡6 天前
【PHPer转Go】函数/方法返回类型的取舍,指针还是值
go·指针·类型·
Peter·Pan爱编程8 天前
引用:比指针更安全的别名
c++·指针·引用·c++基础
谙弆悕博士9 天前
【附C语言源码】C语言 栈结构 实现及其扩展操作
c语言·开发语言·数据结构·算法·链表·指针·
Dlrb12119 天前
C语言-指针数组与数组指针
c语言·数据结构·算法·指针·数组指针·指针数组·二级指针
咩咦10 天前
C++学习笔记08:指针和引用的区别
c++·学习笔记·指针·引用·指针和引用
qq38624619611 天前
推荐几本C语言书籍
c语言·指针·函数·学习资料·编程书籍