2. 指针类型与强制转换

任务目标

编写一段C代码,演示不同类型指针之间的强制转换规则,并通过内存访问结果验证:指针类型转换只改变解释内存的方式,不改变内存地址本身;错误的对齐或长度假设可能导致非预期读取。

核心实现

#include <stdio.h>
#include <stdint.h>

int main(void) {
    // 原始数据:4字节,十六进制 0x44332211
    uint32_t value = 0x44332211;
    uint32_t *p_u32 = &value;

    // 1. 相同宽度类型转换(uint32_t* → int32_t*)
    int32_t *p_i32 = (int32_t *)p_u32;
    printf("地址: %p\n", (void *)p_u32);
    printf("uint32_t 视角: 0x%08X\n", *p_u32);
    printf("int32_t  视角: %d (0x%08X)\n", *p_i32, (uint32_t)*p_i32);

    // 2. 窄化转换(uint32_t* → uint8_t*),观察字节序
    uint8_t *p_bytes = (uint8_t *)p_u32;
    printf("\n字节访问 (小端序):\n");
    for (int i = 0; i < 4; i++) {
        printf("  byte[%d] = 0x%02X\n", i, p_bytes[i]);
    }

    // 3. 跨宽度转换(uint32_t* → uint64_t*),演示越界读取风险
    uint64_t *p_u64 = (uint64_t *)p_u32;
    printf("\nuint64_t 视角: 0x%016llX (可能越界读取)\n",
           (unsigned long long)*p_u64);

    // 4. 指针与整数互转(仅演示,不推荐)
    uintptr_t addr = (uintptr_t)p_u32;
    uint32_t *p_back = (uint32_t *)addr;
    printf("\n整数往返转换后值: 0x%08X\n", *p_back);

    return 0;
}

编译与运行

gcc -o ptr_cast ptr_cast.c -Wall -Wextra
./ptr_cast

验证方法与判定标准

  • 地址一致性:所有指针打印的地址值相同,证明类型转换不改变地址。
  • 同宽转换:int32_t视角打印的数值与uint32_t视角的十六进制位模式一致(如0x44332211对应十进制1144201745),说明仅解释方式改变。
  • 字节序观察:在小端机器上,byte[0]=0x11、byte[3]=0x44;在大端机器上顺序相反。此结果直接反映硬件字节序。
  • 越界风险:uint64_t读取会访问value之后的4字节内存,输出值取决于相邻内存内容,不可预测——这正是跨宽度转换的危险所在。
  • 整数往返:uintptr_t保证指针→整数→指针的往返转换后值不变,输出仍为0x44332211。

关键说明

  • 转换规则本质:C语言中指针类型转换仅改变编译期类型信息,运行时按新类型解释原地址处的字节序列。
  • 对齐要求:若目标类型对齐要求更严格(如uint32_t*转uint64_t*),可能引发未定义行为;本示例中value的对齐通常满足要求,但越界读取仍属未定义行为。
  • 实际工程中,uint8_t*常用于内存拷贝、协议解析;uintptr_t仅用于特殊场景(如嵌入式寄存器地址映射),日常编程应避免指针与整数互转。