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 会影响栈布局

  • 压栈更多数据
  • 改变变量偏移
相关推荐
Freak嵌入式4 小时前
PIO 入门:手把手实现板载 LED 闪烁,原理与代码全解析
嵌入式
Freak嵌入式11 小时前
一文搞懂树莓派 Pico 任务调度器:基于定时器的 MicroPython 多任务实现
嵌入式
Interview Aid11213 小时前
TikTok OA 四题分享|半小时内 AC,题目基本都是实现题
java·开发语言·算法·面试·职场和发展
CoderIsArt20 小时前
C#中UI 线程与 Dispatcher
开发语言·ui·c#
y = xⁿ21 小时前
DeepSeek Harness 学习日记:关于Agent接口,工具调用的底层实现
android·java·学习
AI情绪识别开源1 天前
检信 ALLEMOTION OS 加密打包可执行程序 — 全面测试报告版本: v1.3功能测试 / 性能测试 /
开发语言·数据结构·人工智能·功能测试
「QT(C++)开发工程师」1 天前
C++ auto 用法详解
开发语言·c++
OPEN-F1 天前
C++STL教程:容器适配器与实用工具
开发语言·c++
yaoxin5211231 天前
507. Java 反射 - 在 BeanFactory 中实现依赖注入
java·开发语言