C 语言类型强转引发的隐蔽内存破坏问题分析

1、问题背景

最近在开发过程中遇到一个非常诡异的问题:

  • 某些变量被篡改成异常值
  • 加一行 printf 后,问题消失或表现改变
  • Debug 版本容易复现
  • Release 版本基本正常

这类问题非常典型,但也非常容易误导人,因为它看起来像:

随机问题 / 编译器问题 / 栈问题 / 多线程问题

但最终定位下来,其实是一个经典错误:

👉 非法类型转换 + 栈内存越界写(Undefined Behavior)


2、最小复现代码

测试平台:Aarch64

c 复制代码
#include <stdio.h>
#include <stdint.h>

void overflow(uint64_t *p) {
    uint64_t a = 0XFF;
    *p = a;
}

void test() {
    uint8_t a;
    uint32_t x = 0x11111111;
    uint32_t y = 0x22222222;

    printf("Before:\n");
    printf("x = 0x%x, y = 0x%x\n", x, y);

    overflow((uint64_t *)&a);

    printf("\nAfter:\n");
    printf("x = 0x%x, y = 0x%x\n", x, y);
}

int main() {
    test();
    return 0;
}

3、运行现象

Debug 版本

bash 复制代码
[root@sylixos:/apps/app_test]# ./app_test
Before:
x = 0x11111111, y = 0x22222222

After:
x = 0x11000000, y = 0x0

👉 x 和 y 都被破坏了


Release 版本

bash 复制代码
[root@sylixos:/apps/app_test]# ./app_test
Before:
x = 0x11111111, y = 0x22222222

After:
x = 0x11111111, y = 0x22222222

👉 看起来完全正常

4、问题本质

核心问题在这一行:

c 复制代码
overflow((uint64_t *)&a);

把 1 字节变量当成 8 字节变量,然后越界写。

c 复制代码
*p = a;   // 写 8 字节

我们观察反汇编:

Debug 版本:

c 复制代码
void test() {
 744:        a9be7bfd         stp        x29, x30, [sp, #-32]!
 748:        910003fd         mov        x29, sp
 74c:        39005fff         strb       wzr, [sp, #23]        // 将 0 存储到 [sp + 23] 这个栈内存地址中
 
 750:        3200e3e0         mov        w0, #0x11111111       // 将立即数 0x11111111 加载到 w0 寄存器       
 754:        b9001fe0         str        w0, [sp, #28]		   // 将 w0 寄存器的值存储到栈上
 
 758:        3203e3e0         mov        w0, #0x22222222   	   // 将立即数 0x22222222 加载到 w0 寄存器   
 75c:        b9001be0         str        w0, [sp, #24]		   // 将 w0 寄存器的值存储到栈上
 ......
 ......
 76c:        b9401be2         ldr        w2, [sp, #24]         // 从栈中读取 y
 770:        b9401fe1         ldr        w1, [sp, #28]         // 从栈中读取 x
 774:        90000000         adrp        x0, 0 <.plt-0x480>
 778:        911fe000         add        x0, x0, #0x7f8
 77c:        97ffff49         bl        4a0 <printf@plt>
 780:        91005fe0         add        x0, sp, #0x17			// x0 保存的是变量 a 的地址,0x17 换算成十进制就是 23
 784:        97ffff4f         bl        4c0 <overflow@plt>
 788:        90000000         adrp        x0, 0 <.plt-0x480>
 78c:        91204000         add        x0, x0, #0x810
 790:        97ffff48         bl        4b0 <puts@plt>
 794:        b9401be2         ldr        w2, [sp, #24]			// 从栈中读取 y
 798:        b9401fe1         ldr        w1, [sp, #28]			// 从栈中读取 x
 79c:        90000000         adrp        x0, 0 <.plt-0x480>
 7a0:        911fe000         add        x0, x0, #0x7f8
 7a4:        97ffff3f         bl        4a0 <printf@plt>
}

👉 栈布局大致是:

bash 复制代码
sp+23 : a (1 byte)
sp+24 : y (4 bytes)
sp+28 : x (4 bytes)

写入发生什么?

c 复制代码
*p = 0xFF;

👉 实际写:

sp+23 ~ sp+30 (连续 8 字节)

覆盖范围:

a + y + x 的一部分

4.1 为什么 Debug 会显现问题,Release 却不会?

查看 Release 的反汇编:

c 复制代码
void test() {
 750:        a9bd7bfd         stp        x29, x30, [sp, #-48]!
 754:        90000000         adrp        x0, 0 <.plt-0x480>
 758:        911f8000         add        x0, x0, #0x7e0
 75c:        910003fd         mov        x29, sp
 760:        f9000bf3         str        x19, [sp, #16]
 764:        90000013         adrp        x19, 0 <.plt-0x480>
 768:        3900bfff         strb        wzr, [sp, #47]
 76c:        97ffff51         bl        4b0 <puts@plt>
 770:        3203e3e2         mov        w2, #0x22222222                    // #572662306
 774:        3200e3e1         mov        w1, #0x11111111                    // #286331153
 778:        911fa273         add        x19, x19, #0x7e8
 77c:        aa1303e0         mov        x0, x19
 780:        97ffff48         bl        4a0 <printf@plt>
 784:        9100bfe0         add        x0, sp, #0x2f
 788:        97ffff4e         bl        4c0 <overflow@plt>
 78c:        90000000         adrp        x0, 0 <.plt-0x480>
 790:        91200000         add        x0, x0, #0x800
 794:        97ffff47         bl        4b0 <puts@plt>
 798:        aa1303e0         mov        x0, x19
 79c:        3203e3e2         mov        w2, #0x22222222                    // #572662306
 7a0:        3200e3e1         mov        w1, #0x11111111                    // #286331153
 7a4:        97ffff3f         bl        4a0 <printf@plt>
}
 7a8:        f9400bf3         ldr        x19, [sp, #16]
 7ac:        a8c37bfd         ldp        x29, x30, [sp], #48
 7b0:        d65f03c0         ret
 7b4:        d503201f         nop
 7b8:        d503201f         nop
 7bc:        d503201f         nop

👉 x 和 y:

  • 根本没从内存读
  • 直接用立即数
c 复制代码
mov w2, #0x22222222
mov w1, #0x11111111

Release 版本改变了栈布局、重排变量、使用寄存器缓存数据

4.2 为什么加 printf 会改变程序执行结果?

原因: printf 会影响栈布局

  • 压栈更多数据
  • 改变变量偏移
相关推荐
遇乐的果园14 分钟前
前端学习笔记-vue加载渲染优化
前端·笔记·学习
心平气和量大福大40 分钟前
C#-WPF-Window主窗体
开发语言·c#·wpf
遇乐的果园2 小时前
前端学习笔记-vue状态管理优化
前端·笔记·学习
从零开始的代码生活_2 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法
云小逸2 小时前
【C++ 第七阶段:模板、泛型编程与工程综合详解】
开发语言·c++
可乐奶茶sky2 小时前
AI Agent 学习
人工智能·学习
摇滚侠3 小时前
Java 全栈开发实战教程 课程笔记 29-33
笔记
GIS阵地3 小时前
QgsSingleBandPseudoColorRenderer 完整详解(QGIS 3.40.13 C++)
开发语言·前端·c++·qt·qgis
茯苓gao3 小时前
嵌入式开发笔记:EtherCAT协议从硬件到软件完整配置指南——从零搭建一套EtherCAT通信系统
笔记·嵌入式硬件·学习
程序员Rock3 小时前
上位机开发-MODBUS面试常见问题
c语言·c++·面试·职场和发展·上位机