条件移动优化是否针对 C 标准?
Is the conditional move optimization against the C standard?
在C中使用条件移动(汇编cmov
)来优化条件表达式?:
是一种常见的优化。但是,C标准说:
The first operand is evaluated; there is a sequence point between its evaluation and the
evaluation of the second or third operand (whichever is evaluated). The second operand
is evaluated only if the first compares unequal to 0; the third operand is evaluated only if
the first compares equal to 0; the result is the value of the second or third operand
(whichever is evaluated), converted to the type described below.110)
例如下面的C代码
#include <stdio.h>
int main() {
int a, b;
scanf("%d %d", &a, &b);
int c= a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
会生成优化后的相关asm代码如下:
call __isoc99_scanf
movl (%rsp), %esi
movl 4(%rsp), %ecx
movl , %edi
leal 2(%rcx), %eax
leal 1(%rsi), %edx
cmpl %ecx, %esi
movl $.LC1, %esi
cmovle %eax, %edx
xorl %eax, %eax
call __printf_chk
根据标准,条件表达式只会计算一个分支。但是这里对两个分支都进行了评估,这违反了标准的语义。这是针对 C 标准的优化吗?还是很多编译器优化有不符合语言标准的地方?
C 标准描述了一个执行 C 代码的抽象机器。只要不违反抽象,编译器就可以自由执行任何优化,即符合标准的程序无法区分。
由于"as-if rule", i.e. C11 5.1.2.3p6,优化是合法的。
只需要一个符合规范的实现来生成一个程序,当 运行 生成与使用抽象语义 [=32=32=] 程序执行时相同的 可观察行为=] 会产生 。标准的其余部分只是描述了这些抽象语义。
编译后的程序在内部做什么根本不重要,唯一重要的是当程序结束时它没有任何其他可观察到的行为,除了读取 a
和 b
并打印 a + 1
或 b + 2
的值,具体取决于哪个 a
或 b
更大,除非发生某些导致行为未定义的事情。 (错误的输入导致 a、b 未初始化,因此访问未定义;范围错误和有符号溢出也可能发生。)如果发生未定义的行为,则所有赌注都关闭。
由于必须根据抽象语义严格评估对 volatile 变量的访问,因此您可以在此处使用 volatile
来摆脱条件移动:
#include <stdio.h>
int main() {
volatile int a, b;
scanf("%d %d", &a, &b);
int c = a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
编译为
call __isoc99_scanf@PLT
movl (%rsp), %edx
movl 4(%rsp), %eax
cmpl %eax, %edx
jg .L7
movl 4(%rsp), %edx
addl , %edx
.L3:
leaq .LC1(%rip), %rsi
xorl %eax, %eax
movl , %edi
call __printf_chk@PLT
[...]
.L7:
.cfi_restore_state
movl (%rsp), %edx
addl , %edx
jmp .L3
通过我的 GCC Ubuntu 7.2.0-8ubuntu3.2
在C中使用条件移动(汇编cmov
)来优化条件表达式?:
是一种常见的优化。但是,C标准说:
The first operand is evaluated; there is a sequence point between its evaluation and the evaluation of the second or third operand (whichever is evaluated). The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (whichever is evaluated), converted to the type described below.110)
例如下面的C代码
#include <stdio.h>
int main() {
int a, b;
scanf("%d %d", &a, &b);
int c= a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
会生成优化后的相关asm代码如下:
call __isoc99_scanf
movl (%rsp), %esi
movl 4(%rsp), %ecx
movl , %edi
leal 2(%rcx), %eax
leal 1(%rsi), %edx
cmpl %ecx, %esi
movl $.LC1, %esi
cmovle %eax, %edx
xorl %eax, %eax
call __printf_chk
根据标准,条件表达式只会计算一个分支。但是这里对两个分支都进行了评估,这违反了标准的语义。这是针对 C 标准的优化吗?还是很多编译器优化有不符合语言标准的地方?
C 标准描述了一个执行 C 代码的抽象机器。只要不违反抽象,编译器就可以自由执行任何优化,即符合标准的程序无法区分。
由于"as-if rule", i.e. C11 5.1.2.3p6,优化是合法的。
只需要一个符合规范的实现来生成一个程序,当 运行 生成与使用抽象语义 [=32=32=] 程序执行时相同的 可观察行为=] 会产生 。标准的其余部分只是描述了这些抽象语义。
编译后的程序在内部做什么根本不重要,唯一重要的是当程序结束时它没有任何其他可观察到的行为,除了读取 a
和 b
并打印 a + 1
或 b + 2
的值,具体取决于哪个 a
或 b
更大,除非发生某些导致行为未定义的事情。 (错误的输入导致 a、b 未初始化,因此访问未定义;范围错误和有符号溢出也可能发生。)如果发生未定义的行为,则所有赌注都关闭。
由于必须根据抽象语义严格评估对 volatile 变量的访问,因此您可以在此处使用 volatile
来摆脱条件移动:
#include <stdio.h>
int main() {
volatile int a, b;
scanf("%d %d", &a, &b);
int c = a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
编译为
call __isoc99_scanf@PLT
movl (%rsp), %edx
movl 4(%rsp), %eax
cmpl %eax, %edx
jg .L7
movl 4(%rsp), %edx
addl , %edx
.L3:
leaq .LC1(%rip), %rsi
xorl %eax, %eax
movl , %edi
call __printf_chk@PLT
[...]
.L7:
.cfi_restore_state
movl (%rsp), %edx
addl , %edx
jmp .L3
通过我的 GCC Ubuntu 7.2.0-8ubuntu3.2