PAYATHON 2026

C3 和 C 中除以零的未定义行为如何影响编译优化?

数据小陈

结论

类似的优化也可能发生在 C 中。C 标准规定,整数除法的除数为零时会产生未定义行为。编译器可以据此进行优化,但不一定会生成 foo(); return true; 这样的代码。C11 委员会草案 N1570,第 6.5.5 节

示例来自 C3,其中用了 uint 和 true。下面改用 C 中常见的 unsigned int 来表达同一个问题。在 C 代码中使用 uint 时,需要由程序或头文件定义它。

unsigned int x = foo();
unsigned int z = 255 / x;
return x != 0;

为什么编译器可以返回 true

只看行为有定义的执行:代码要继续运行到 return,除法中的 x 就不能为零。因此,在这些执行中,x != 0 始终为真,编译器可以把返回值简化为 true(在 C 中也可以写成 1)。如果 x 为零,程序在整数除法处就已经触发未定义行为,标准不再要求它产生特定结果。

这不表示编译器一定会采用页面展示的变换。它也可以删除对后续代码没有影响的 z 赋值和除法,同时保留 return x != 0;。foo() 可能产生可观察的副作用,因此仍需保留这次调用。最终生成什么代码,取决于具体实现和优化过程。

为什么不只是删除除法

后续没有使用 z,所以编译器可以判断,计算结果不会对程序中行为有定义的执行产生可观察影响。删除这项计算是合理的优化。不过,这次除法也可能让编译器得知:在除法正常完成的执行中,x 不可能为零。优化器可以利用这一条件简化后面的判断。

这两种变换都可能成立。未定义行为不是结果已经规定好的运行时错误。程序一旦执行到未定义行为,语言标准就不再约束这次执行的结果。因此,不能要求编译器只删除除法,而让其他语句保持原样。

为什么编译器不一定报错

编译时,编译器通常无法知道 foo() 会返回什么,除数是否为零可能要等到运行时才能确定。静态分析或编译器警告有时能发现风险,但 C 不要求实现对每一种未定义行为都发出诊断。如果问题能在编译时发现,编译器可能会给出警告;是否报告取决于工具及其设置。

避免除数为零

执行除法前,先显式检查 x,并为零值定义程序需要的处理方式:

unsigned int foo(void);

int f(void) {
    unsigned int x = foo();

    if (x == 0) {
        return 0;
    }

    unsigned int z = 255u / x;
    (void)z;
    return 1;
}

这里,x 为零时会明确返回 0;只有确认它非零后,程序才执行除法。如果后续逻辑需要商,就在检查后使用 z;如果商没有用途,这次计算可以直接删掉。

备注:内容仅供参考。