我在c:
中有这段简单的代码// Helper type TActions will be a union of any number of actions
// TName will be the name of the action we are intresed in
// We use filter, to filter out of TActions all actions that are not named TName
type ActionData<TActions extends IAction<string, any>, TName> =
Filter<TActions, IAction<TName, any>>['data'];
export class Communicator<A extends IAction<any, any>> {
public subscribe<K extends A['name']>(
subscriber: { name: K; handler: (data: ActionData<A, K>) => void; }
) {}
public emit<K extends A['name']>(name: K, data: ActionData<A,K>): void {
}
}
const communicator = new Communicator<ExampleActions>();
let a = '';
communicator.subscribe({ name: ActionName.name1, handler: d=> a = d}); // ok
communicator.subscribe({ name: ActionName.name2, handler: d=> a = d}); // erorr as it should be
communicator.emit(ActionName.name1, a); // ok
communicator.emit(ActionName.name2, a); // erorr as it should be
当我看到此代码的汇编输出时:
#include <stdio.h>
void test() {}
int main()
{
if (2 < 3) {
int zz = 10;
}
return 0;
}
我从here 获取了程序集(默认选项) 我无法看到条件检查的指令在哪里?
答案 0 :(得分:7)
你没有看到它,因为它不在那里。编译器能够执行分析,而且很容易看到此分支将始终进入。
它不会发出只会浪费CPU周期的检查,而是发出一个易于优化的代码版本。
C程序不是 CPU执行的一系列指令。这就是发出的机器代码。 C程序是对已编译程序应具有的行为的描述。只要你得到那种行为,编译器就可以以任何方式自由地翻译它。
它被称为“as-if规则”。
答案 1 :(得分:3)
有趣的是,与其他编译器(ICC和MSVC)不同,即使在if()
,gcc和clang也会优化-O0
。
gcc -O0
并不意味着没有优化,这意味着no extra optimization超出了编译所需的范围。但是gcc必须在发出asm之前通过函数逻辑的几个内部表示进行转换。 (GIMPLE和注册转移语言)。 gcc没有特殊的&#34;哑模式&#34;它将每个C表达的每个部分都盲目地音译为asm。
即使a super-simple one-pass compiler like TCC在表达式(甚至语句)中进行微小优化,也就像意识到始终为真的条件并不需要分支一样。
gcc -O0
是默认设置,您明显使用它是因为zz
的死存储未被优化掉。
gcc -O0
旨在快速编译,旨在提供一致的调试结果。
C语句中的寄存器中没有任何内容,因此您可以在单步执行时使用调试器修改任何C变量。即在单独的C语句之间溢出/重新加载所有内容。 (这就是为什么-O0
的基准测试是无意义的:writing the same code with fewer larger expressions is faster only at -O0
,而不是像-O3
这样的实际设置。
其他有趣的结果:常量传播不起作用,有关gcc使用div
表示变量设置为常量的情况,请参阅Why does integer division by -1 (negative one) result in FPE?,而对于文字常量则更简单
jump
到不同的源代码行(在同一个函数中)并获得一致的结果。 (与优化代码不同,优化代码可能会崩溃或无意义,并且绝对不符合C抽象机)。鉴于gcc -O0
行为的所有要求,if (2 < 3)
仍然可以优化为零asm指令。这种行为并不取决于任何变量的值,而且它只是一个单一的陈述。没有办法永远不会被删除,所以最简单的编译方法就是没有说明:落入{ body }
的{{1}}。
请注意if
的规则/限制远超出C as-if规则,因为函数的机器代码只需实现所有外部可见C源的行为。 gcc -O0
将整个功能优化为
gcc -O3
因为它并不关心为每个C语句保留asm。
请参阅all 4 of the major x86 compilers on Godbolt。
clang类似于gcc,但是main: # with optimization
xor eax, eax
ret
的死存储位于堆栈的另一个位置,以及0
的{{1}}。 clang 10
通常更靠近C的音译到asm,例如它会zz
用于-O0
而不是移位,而gcc使用a multiplicative inverse for division by a constant甚至在{{{ 1}}。但在这种情况下,clang还决定没有任何指令足以满足始终如此的条件。
ICC和MSVC都为分支发出asm,但不是你可能期望的div
/ x / 2
,它们实际上都是-O0
,没有明显的理由:
mov $2, %ecx
即使没有启用优化,MSVC也会使用xor-zeroing peephole优化。
查看哪些本地/窥孔优化编译器甚至在cmp $3, %ecx
处执行操作有点奇怪,但它并没有告诉您有关C语言规则或代码的任何基本信息,它只是告诉您关于编译器内部结构以及编译器开发人员在花时间寻找简单优化与在非优化模式下编译速度之间的选择之间的权衡。
asm从不打算以任何方式忠实地表示C源,让反编译器重构它。只是为了实现等效的逻辑。
答案 2 :(得分:2)
这很简单。它不在那里。编译器对其进行了优化。
这是使用gcc编译而没有优化时的程序集:
.file "k.c"
.text
.globl test
.type test, @function
test:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size test, .-test
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $10, -4(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Debian 6.3.0-18) 6.3.0 20170516"
.section .note.GNU-stack,"",@progbits
这里是优化:
.file "k.c"
.text
.p2align 4,,15
.globl test
.type test, @function
test:
.LFB11:
.cfi_startproc
rep ret
.cfi_endproc
.LFE11:
.size test, .-test
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB12:
.cfi_startproc
xorl %eax, %eax
ret
.cfi_endproc
.LFE12:
.size main, .-main
.ident "GCC: (Debian 6.3.0-18) 6.3.0 20170516"
.section .note.GNU-stack,"",@progbits
如您所见,不仅比较优化了。几乎整个主要部分都被优化掉,因为它不会产生任何可见的东西。从不使用变量zz。您的代码唯一可观察的事情是返回0。
答案 3 :(得分:1)
2总是少于tan 3所以,因为编译器知道2&lt; 3的结果总是正确的,所以在汇编程序中不需要if决定。
优化意味着生成更少的时间/更少的代码。
答案 4 :(得分:0)
if (2<3)
总是如此,因此编译器不会为它设置操作码。
答案 5 :(得分:0)
条件if (2<3)
始终为真。所以一个体面的编译器会检测到这会生成代码,就像条件不存在一样。事实上,如果您使用-O3
进行优化,godbolt.org只生成:
test():
rep ret
main:
xor eax, eax
ret
这也是有效的,因为只要observable behaviour被保留,就允许编译器优化和转换代码。