编译器没有反映C中的短路评估



我正试图通过GCC和Clang编译以下带有-O3优化标志的C代码,在查看生成的汇编代码后,我发现这两个编译器都没有实现C标准中提到的短路评估&操作人员

您可以参考下面的汇编代码以获得更多信息,foo函数的前五行代码将按顺序运行,它将比较&实际违反标准的操作员。那么,这里有什么误解吗?

C代码:

#include <stdio.h>
#include <stdbool.h>
void foo(int x, int y) {
bool logical = x && y;
printf("%d", logical);
}
int main(void) {
foo(1, 3);
return 0;
}

生成的装配代码:

foo:                                    # @foo
test    edi, edi
setne   al
test    esi, esi
setne   cl
and     cl, al
movzx   esi, cl
mov     edi, offset .L.str
xor     eax, eax
jmp     printf                          # TAILCALL
main:                                   # @main
push    rax
mov     edi, offset .L.str
mov     esi, 1
xor     eax, eax
call    printf
xor     eax, eax
pop     rcx
ret
.L.str:
.asciz  "%d"

首先,您的示例有错误。

给定x=1和y=3,计算x && y需要计算两个操作数,因为只有当两个操作都为真时,&&才为真。

第二,C 2018 5.1.2.36说:

对一致性实施的最低要求是:

--对易失性对象的访问严格按照抽象机器的规则进行评估。

--在程序终止时,写入文件的所有数据应与根据抽象语义执行程序所产生的结果相同。

--交互式设备的输入和输出动态应按照7.21.3的规定进行。这些要求的目的是尽快出现无缓冲或行缓冲的输出,以确保在程序等待输入之前实际出现提示消息。

这是程序的可观察行为。

这意味着编译器只负责确保上述行为按指定发生。当结果可以从左操作数推导时,不需要生成不计算&&||的右操作数的汇编语言,只要该计算不会改变可观察的行为。编译器没有义务生成您所寻求的汇编语言,只是为了确保可观察的行为符合规定。

当标准将表达式描述为正在评估或未评估时,它是在"抽象机器"(C 2018 5.1.2.31(的上下文中描述它们,而不是描述生成的程序在汇编语言级别上实际必须做什么。其思想是,C标准描述了程序在抽象机器中生成的内容,然后编译器可以生成任何与抽象机器中的程序具有相同可观察行为的程序,即使它以与抽象机器上的程序完全不同的方式获得结果。

确实如此。但编译器会根据要求选择最快的方式。

  1. main-编译器知道结果编译时间,根本不调用函数foo
  2. foo-分支是相当昂贵的操作,因为它们需要刷新管道、潜在的缓存未命中、从慢速内存中读取等。因此在这种情况下分支毫无意义。你的例子太琐碎了

考虑一个不那么琐碎的例子:

bool bar(void);
void foo(int x) {
bool logical = x && bar();
printf("%d", logical);
}
int main(void) {
foo(1);
return 0;
}
foo:
test    edi, edi
jne     .L12
mov     esi, edi
xor     eax, eax
mov     edi, OFFSET FLAT:.LC0
jmp     printf
.L12:
sub     rsp, 8
call    bar
mov     edi, OFFSET FLAT:.LC0
add     rsp, 8
movzx   esi, al
xor     eax, eax
jmp     printf
main:
sub     rsp, 8
mov     edi, 1
call    foo
xor     eax, eax
add     rsp, 8
ret

来自C标准6.5.13:

不同于逐位二进制&运算符,&amp;运营商担保从左到右评价;如果计算第二个操作数,则存在第一次和第二次评估之间的序列点操作数。如果第一个操作数比较等于0,则第二个操作数未评估。

最新更新