土法炼钢兴趣小组的算法知识备份

当 AI 遇到未定义行为

目录

未定义行为 (UB) 是 C/C++ 最危险的概念。不是”程序会崩溃”——是”编译器可以做任何事,包括让程序看起来正常工作,直到某天突然不正常”。

AI 对 UB 的理解程度如何?我用三个 sanitizer(UBSan、ASan、MSan)扫描了 200 段 AI 生成的 C/C++ 代码。

实验设计

200 段代码分 4 类:指针操作(50 段)、整数运算(50 段)、类型转换(50 段)、并发代码(50 段)。

每段代码先编译通过,然后分别用以下工具检查: - UBSan (Undefined Behavior Sanitizer):检测整数溢出、空指针解引用、类型不匹配等 - ASan (Address Sanitizer):检测越界、use-after-free、double free 等 - MSan (Memory Sanitizer):检测使用未初始化内存

结果

类别 UBSan 报错 ASan 报错 MSan 报错 至少一个 UB
指针操作 22% 28% 18% 42%
整数运算 35% 2% 0% 35%
类型转换 30% 8% 12% 38%
并发代码 15% 20% 25% 40%
总计 25% 14% 14% 38%

38% 的代码包含至少一个未定义行为。

高频 UB 模式

1. 有符号整数溢出(整数运算类 #1 常见 UB)

// AI 生成
int sum(int a, int b) {
    return a + b;  // 当 a + b > INT_MAX 时,这是 UB
}

C 标准规定有符号整数溢出是未定义行为。编译器可以假设它不会发生,并基于这个假设做优化。比如 if (a + b < a) 这种溢出检查可能被编译器直接删掉——因为”有符号整数不会溢出”。

AI 在 70% 的情况下不处理整数溢出。因为在 Python(AI 训练数据的大头)里整数不会溢出。

2. 空指针解引用(指针操作类 #1)

// AI 生成
struct Node *find(struct Node *head, int val) {
    while (head->val != val) {  // 如果 head 是 NULL,UB
        head = head->next;
    }
    return head;
}

AI 默认输入是有效的。它不加 NULL 检查——不是因为忘了,而是因为训练数据里的教学代码大部分也不加。

3. strict aliasing 违反(类型转换类 #1)

// AI 生成:把 float 的位模式当 int 读
float f = 3.14;
int i = *(int *)&f;  // UB: 违反 strict aliasing rule
// 正确做法: memcpy(&i, &f, sizeof(i));

这是 AI 最系统性的 UB 来源。AI 经常生成 *(type *)&var 形式的类型双关,这在 C 标准里是 UB(除了 char *)。正确做法是用 memcpy,但大量训练数据里的旧代码用的就是指针转换。

4. 使用未初始化变量(并发类 #1)

// AI 生成
int result;
pthread_create(&thread, NULL, compute, &result);
pthread_join(thread, NULL);
printf("%d\n", result);  // 如果 compute 里没写 result,MSan 报错

AI 假设线程函数会写入 result。但如果线程函数提前返回(错误路径),result 就是未初始化的。

为什么 AI 不理解 UB

  1. 训练数据里 UB 是沉默的。UB 代码能编译、能运行(大部分时候)。AI 从训练数据里学到的是”这段代码在实践中能用”,而不是”这段代码在标准里是未定义的”。

  2. UB 的后果是非局部的。有符号溢出的 UB 可能导致完全不相关的代码被优化掉。这种非局部效应超出了 AI 的上下文窗口——它看到的是局部代码,看不到编译器对整个函数的优化决策。

  3. 不同优化级别不同表现-O0 下 UB 代码通常”正常工作”,-O2 下才暴露问题。AI 的训练数据里很多代码是在 -O0 下测试的。

防御措施

如果你使用 AI 生成 C/C++ 代码:

# 编译时开启所有 sanitizer
gcc -O2 -fsanitize=undefined,address -fno-sanitize-recover=all \
    -Wall -Wextra -Werror -o myapp myapp.c

# Rust 用 Miri (无 UB by design,但 unsafe 块需要检查)
cargo miri test

规则:AI 生成的每一段 C/C++ 代码都必须在 -fsanitize=undefined,address 下编译和测试。 没有例外。


延伸阅读:


By .