找回密码
 立即注册
查看: 156|回复: 0

C251编译器2026年第二弹: 新发现的尚未被官方提及的编译器Bug(最终版)

[复制链接]
已绑定手机

2

主题

2

回帖

84

积分

注册会员

积分
84
发表于 2026-8-15 19:38:21 | 显示全部楼层 |阅读模式

Bug 报告:Keil C251 V5.60.13.0 在 OPTIMIZE(6~9) 下编译器崩溃 (最终精确定位版)

报告编号:C251-2026-0001

报告日期:2026-08-16

报 告 人:sadate(STC技术论坛)

状 态:待 Keil / STC 官方确认


1. 概述

Keil C251 V5.60.13.0 编译器在优化级别 OPTIMIZE(6) 及以上(6/7/8/9,含 SPEED/SIZE 两种目标)编译一段合法且无未定义行为的 C 代码时发生编译器内部崩溃

  • C251.EXE 进程以访问违规(ExitCode = 0xC0000005)退出;
  • 不输出任何错误或警告信息;
  • 不生成 .obj 文件,下游链接报 L210: module not found
  • 该行为与内存模型无关(XSMALL/LARGE 均可触发);
  • 降级到 OPTIMIZE(5) 及以下即恢复正常。

该问题首次出现在真实工程的 函数中(STC32G144K246,XSMALL 模型)。通过深入研究,已构造出与项目零依赖的 8 行最小可复现用例(见第 6 节)。

结论
Keil 官方文档与公开论坛中未检索到针对此问题的已知/已修复 Bug 记录;社区仅有"高优化等级可能对 xdata 产生非法访问"的零散经验性提示。据此判断此为未公开记录的编译器代码生成缺陷


2. 环境

编译器 Keil C251V5.60.13.0(C251 COMPILER, 2007–2023 Arm Ltd.)
工具链路径 E:\Keil_v5\C251\BIN\C251.EXE
目标芯片 STC32G144K246(251 内核,source mode;宏 TB_TARGET=10
操作系统 Windows 11
触发内存模型 XSMALLLARGE 同样可触发
触发优化级别 OPTIMIZE(6)~OPTIMIZE(9)OPTIMIZE(9, SPEED) / OPTIMIZE(9, SIZE) 均可
正常级别 OPTIMIZE(0)~OPTIMIZE(5)

3. 复现步骤

  1. 进入报告目录:upload 附件:repro_min.c

    cd bugreports\c251_xsmall_opt9_crash\report
    
  2. 以崩溃级别编译最小可复现用例 repro_min.c

    E:\Keil_v5\C251\BIN\C251.EXE repro_min.c DEFINE(TB_TARGET=10) OPTIMIZE(9) XSMALL CODE STRING(CODE)
    
  3. 观察退出码与输出产物(崩溃时无任何输出、不生成 .obj)。


4. 预期行为

编译器应正常生成 repro_min.obj(或输出合法的编译错误/警告),进程退出码为 0


5. 实际行为

E:\Keil_v5\C251\BIN\C251.EXE e:\MCU\TinyBASIC\bugreports\c251_xsmall_opt9_crash\report\repro_min.c DEFINE(TB_TARGET=10) OPTIMIZE(9) XSMALL CODE STRING(CODE)
--- 无任何输出 ---
ExitCode = 0xC0000005  (Access Violation / 访问违规)
NO OBJ PRODUCED (CRASH)
  • C251.EXE 在编译过程中静默崩溃,无错误、警告信息;
  • 不生成 .obj,下游 L251 链接报 L210: module not found
  • 同一文件在 OPTIMIZE(4) 下正常编译,成功生成 .obj

6. 最小可复现用例

文件:e:\MCU\TinyBASIC\bugreports\c251_xsmall_opt9_crash\report\repro_min.c(standalone,零项目依赖;m[2] 全部下标合法)。

static union { unsigned char m[2]; } xdata g; extern unsigned int f(unsigned int);

void save(unsigned char *c)
{
    unsigned int n = f(0U), i = 0U;
    unsigned char xdata *p = (unsigned char xdata *)g.m;
    p[i++] = c[0], p[i++] = c[0], p[0] = f(n);
}

说明:

  • 三个操作(两次后递增索引式存储、调用 f(n)、对 p 回写)合并为一条逗号表达式——C90 保证从左到右求值,调用在两个存储之后执行;
  • f(n) 返回值隐式窄化即可,无需 (unsigned char) 强转;

7. 根本原因分析

  1. 崩溃位于优化器/代码生成阶段,而非词法/语法解析OPTIMIZE(5)OPTIMIZE(6) 之间存在明确质变阈值。按 C251 文档,级别 6 开启 LOOP OPTIMIZATION,7~9 进一步叠加 EXTENDED LOOP OPTIMIZATIONGLOBAL OPTIMIZATIONLOOP UNROLLING 等激进变换。崩溃正是从 6 级这一层开始出现。

  2. 精确定位到"显式强转的本地指针 + 索引式存储 + 跨调用复用"的代码生成路径

    • 必须"显式强转 + 本地指针变量":无强转或无本地指针变量而内联强转均不崩溃 → 缺陷位于"强转后的 union 成员地址 → 本地指针寄存器"的分配路径,而非单纯的索引表达式;
    • *p++(单指令指针自增)不崩溃,而 p[i++](需"基址 + 索引"地址计算)崩溃 → 缺陷在索引式存储的****地址计算(基址变址)****路径;
    • 直接 g.m[i] 不崩溃→ 缺陷涉及"本地指针寄存器承载强转后的 union 成员基址"的分配;
    • 回写必须走 p 而非其他数组→ 编译器需在调用点前后维护同一个"强转派生的 xdata 基址 + 索引寄存器"的状态,跨调用保存/恢复时出错。
  3. 与 union 类型强相关:struct 成员不崩溃、union 成员崩溃。union 成员访问使编译器需处理成员地址 = ****联合体基址(offset 0)****的类型化地址计算,再经显式强转后作基址变址;在 OPT≥6 的全局优化/寄存器分配中,疑似对该"强转 + union 成员基址 + 索引变址" IR(内部寄存器)形态生成错误(如索引寄存器保存/恢复遗漏、DPTR 调度不一致),最终访问非法内存崩溃。

  4. 数据流要求是"返回值跨存储区存活"n 必须在索引存储之前由调用赋值并随后使用——说明崩溃路径与****"外部调用返回值 → 寄存器 → 跨索引存储区存活 → 调用点保存/恢复"****的寄存器分配有关。

  5. 与 STC32G / 内存模型无直接因果关系LARGE 模型同样崩溃,说明与模型配置无关,而是上述 IR(内部寄存器) 形态在优化器中的通用缺陷。

说明:编译器内部 IR/寄存器分配细节无法直接观察,需 Keil 官方以 -j(内部调试)复现确认具体故障 pass。当前外部行为证据指向:OPT≥6 优化器处理"显式强转存入本地指针的 xdata union 成员基址 + 调用前多次后递增索引式存储 + 调用后同一指针回写,且外部调用返回值跨存储区存活"时崩溃


8. 规避措施

在官方修复前,任选其一即可安全编译:

  1. 降低优化级别到 ≤5(当前工程采用的方案,实测稳定):

    C251 ... OPTIMIZE(4) XSMALL
    
  2. 使用 #pragma OPTIMIZE(5) 仅对该文件降级,其余文件保持高优化(C251 支持 #pragma OPTIMIZE(n) 文件级指令)。

  3. 改写存储方式为指针式后递增 *p++ = ...(实测 OPT9 下不崩溃)——最小改动即可规避,无需降优化:

    unsigned char xdata *p = (unsigned char xdata *)g.vars_mirror;
    *p++ = ...;   /* 替代 p[pos++] = ...; */
    
  4. 或将 vars_mirror 从 union 成员改为 struct/独立 xdata 数组(实测 struct 不崩溃)。


9. 附录:最小化过程中的两个 C251 前端怪癖

以下两点与本 Bug 无关,但会影响最小化工作,特此记录。

9.1 内存空间关键字不能作形参名

C251 中 codexdatafar 等是内存空间关键字,不能用作函数形参名。如:

void set_error(unsigned char code);   /* ERROR C25: syntax error near ')' */
void set_error(unsigned char x);      /* 正常 */

用保留内存关键字命名形参会报 C25 语法错误(且后续调用点报 C95: too many actual parameters),这是预期的语法限制而非编译器缺陷。

9.2 特定代码形态触发误导性 C25 解析错误

用例(带标签裸 struct struct S {...}; ... struct S *c; + 本报告所述函数体形态)会在任意优化级别下报出大量级联 C25 语法错误(如 syntax error near 's16'redefinition of 'p''array': initialization needs curly braces),而等价的 typedef 风格代码解析完全正常。此现象疑似 C251 前端的另一个独立解析健壮性缺陷,与本次崩溃无关——请以 typedef 风格编写复现用例


回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-9-22 06:03 , Processed in 0.066858 second(s), 46 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表