2026年C/C++代码未定义行为无处不在,大语言模型或成修复关键!

2026年C/C++代码未定义行为无处不在,大语言模型或成修复关键! 个人博客声明这是个人博客页面上表达的观点仅代表个人不代表雇主观点且内容非AI生成所有想法均为个人作为人类的奇思妙想。C语言里全是未定义行为2026年5月19日从编程、安全分类角度来看若红衣主教黎塞留是程序员或许会说“给我世界上最专业的C程序员手写的六行代码我就能从中找出足以触发未定义行为的问题。”作为近30年来几乎每天都编写C和C代码收听C相关播客、观看C会议演讲、喜欢阅读和编写C代码的人认为没有人能写出完全正确的C或C代码。C虽曾带来很大帮助但如今是2026年与1985年C诞生或1972年C语言诞生时的环境大不相同。有人约十年前读过一篇知名人士文章提到使用C很可能违反《萨班斯 - 奥克斯利法案》Sarbanes–Oxley Act简称SOX虽不认同文章其他观点但在这一点上没有异议。随着时间推移越发觉得未定义行为Undefined Behavior简称UB比想象的多。大家都知道双重释放内存、释放后使用、访问对象越界以及访问未初始化的内存属于未定义行为然而行业仍反复犯这些错误且未定义行为还有更微妙、更不合逻辑的情况。这与优化无关有些人认为编译时不开启优化选项未定义行为就不会造成影响觉得编译器会故意利用代码漏洞不开启优化就不会如此。但这种想法错误未定义行为意味着编译器假定代码有效人类阅读代码的意图在编译器各阶段或模块间可能无法表达编译器甚至不必处理某些特殊情况因为这些情况“不可能发生”。编译器和底层硬件处理未定义行为代码就像玩传话游戏最终结果虽可能符合预期但无法保证。未定义行为无处不在以下并非列举所有未定义行为旨在说明其无处不在。观点认为所有非平凡的C/C代码都存在未定义行为。访问未正确对齐的对象如代码“int foo(const int* p) { return *p; }”若调用时传入指针未正确对齐属于未定义行为参考C23标准的6.3.2.3节。在Linux Alpha系统中某些情况会陷入内核内核软件模拟操作其他情况程序会因SIGBUS信号崩溃在SPARC架构上会引发SIGBUS信号在x86/amd64架构上可能没问题甚至是原子读取操作。未来架构情况未知编译器无义务生成处理未对齐指针的汇编指令。代码“void set_it(std::atomic* p) { p-store(123); } int get_it(std::atomic* p) { return p-load(); }”对象未正确对齐时操作是未定义行为易引发原子性问题。当认为是原子读取的对象跨页时也是未定义行为。实际上问题在这之前就出现了示例代码“bool parse_packet(const uint8_t* bytes) { const int* magic_intp (const int*)bytes; // 未定义行为 int magic_raw foo(magic_intp); // 在SPARC架构上可能会崩溃。 int magic ntohl(magic_raw); // 至少这一步没问题。 [...] }”问题出在类型转换上编译器可为int*低位赋予特定含义。对char类型输入使用isxdigit()函数代码“bool bar(char ch) { return isxdigit(ch); }”isxdigit()接受int类型若bar()函数传入值不在0 - 127范围内且架构中char是有符号类型转换后整数值为负数。isxdigit()的有效实现可能读取未知内存触发意想不到的事情在嵌入式系统中更可能发生。从float类型转换为int类型代码“int milliseconds(float seconds) { int tmp (int)(seconds * 1000.0); /* 错误 */ return tmp 1; /* 单独来看也是错误的有符号溢出属于未定义行为 */ }”根据C标准将实浮点类型有限值转换为整数类型若整数部分无法用该整数类型表示行为未定义浮点数是非有限值也属于未定义行为。虽有改进代码“int milliseconds(float seconds) { const float ftmp seconds * 1000.0f; if (!isfinite(ftmp)) { // 或者进行其他错误处理。 return 0; } if ((float)(INT_MIN 1000) ftmp) { // 或者进行其他错误处理。 return 0; } if ((float)(INT_MAX - 1000) ftmp) { // 或者进行其他错误处理。 return 0; } // 现在可以安全转换了。 const int tmp (int)ftmp; if (INT_MAX tmp) { // 或者进行其他错误处理。 return 0; } // 现在可以安全地进行加法运算了。 return tmp 1; }”但很多代码只是简单转换。地址为零的对象符合C标准情况下几乎无法将对象放在地址为零的位置在操作系统内核和嵌入式编程中可能出现。根据C标准整数常量零和nullptr是“空指针常量”标准未规定NULL实际指向机器地址为零解引用空指针属于未定义行为不能假设memset(ptr, 0, sizeof(ptr));会创建NULL指针历史上有机器使用非零的NULL指针。代码“void (*func_ptr)() NULL; func_ptr();”属于未定义行为C标准说“这里没有函数”编译器可能无法理解意图“全零”在不同架构含义不同。可变参数和类型例如printf中使用%ld而不是%lld代码“execl(/bin/sh, sh, -c, date, NULL); /* 错误 */ execl(/bin/sh, sh, -c, date, 0); /* 错误 */”属于未定义行为正确写法是“execl(/bin/sh, sh, -c, date, (char*)NULL);”。代码“uint64_t blah 123; printf(%ld\n, blah); /* 错误 */”也属于未定义行为正确写法是“uint64_t blah 123; printf(%PRIu64\n, blah);”。打印uid_t类型值可转换为uintmax_t类型用PRIuMAX打印但uid_t是否为无符号类型不确定。除零操作属于未定义行为除零操作属于未定义行为分母通常来自不可信输入。C23标准中“undefined”出现283次还不包括省略未明确的未定义情况。额外的非未定义行为示例没人能在快速浏览代码时正确应用整数提升规则。示例代码“unsigned char a 0xff; unsigned char b 1; unsigned char zero 0; bool overflowed (a b) zero; // overflowed 的值为 0而不是 1。”和“unsigned char a 0x80; uint64_t b a 24; // 额外的未定义行为 // b 的值现在是 18446744071562067968ffffffff80000000而不是 21474836480x80000000。 // 即使所有变量都是无符号类型。”体现了这一点。大语言模型LLM在这方面比我们更厉害大语言模型分析C代码几乎每次都能准确找出未定义行为。让其分析OpenBSD代码找出很多未定义行为。提交了修复越界写入问题和非未定义行为逻辑错误的补丁但未提交其他未定义行为补丁原因包括OpenBSD项目对bug报告接受度不高且清除未定义行为是大工程。我们现在该怎么办不能抛弃现有C/C代码库但让其存在缺陷也不行。需要一种大规模修复未定义行为的方法既不依赖质量不佳的AI输出也不让人类审查者不堪重负。在2026年没有大语言模型监督编写C/C代码可能违反SOX法案且不负责任。对于自己的项目会让大语言模型找出未定义行为解释原因并修复然后仔细检查输出。但确认发现需要专业人员而专业人员通常很忙这工作琐碎且微妙不能交给初级程序员。相关文章相关文章有“C语言中无法解析整数”“整数处理存在问题”“Linux内核中的未定义行为”“整数提升”。Blargh这是记录随机技术内容的博客可通过“thomashabets.se”联系也可在github和twitter上找到相关账号。