【发布时间】:2020-06-13 10:36:58
【问题描述】:
如果我用-O2 优化编译一个程序,那么任何带有数学运算的函数都会“内联”完成,而在汇编enter link description here 中没有物理cpu 指令。所以在c:
#include <stdio.h>
#include <stdlib.h>
int f(int a, int b, int c){
return a*b+c;
}
int main(){
printf("%i\n",f(1,2,4));
return 0;
}
并编译为cc -O2 -fverbose-asm -S a.c,气体输出:
.text
.p2align 4,,15
.globl f
.type f, @function
f:
.LFB22:
# a.c:5: return a*b+c;
imull %esi, %edi # b, tmp93
# a.c:5: return a*b+c;
leal (%rdi,%rdx), %eax #, tmp92
# a.c:6: }
ret
.LFE22:
.size f, .-f
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%i\n"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB23:
subq $8, %rsp #,
# a.c:8: printf("%i\n",f(1,2,4));
movl $6, %esi # HERE, I can change the value and gcc will not even notice
leaq .LC0(%rip), %rdi #,
xorl %eax, %eax #
call printf@PLT #
# a.c:10: }
xorl %eax, %eax #
addq $8, %rsp #
ret
.LFE23:
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
您可以看到,f 函数甚至不会从 main 调用。所以这个函数被简单地避免了,它的返回值在编译时以某种方式计算到一些指令周期。但是在compile-time中,当编译器不直接对CPU进行操作时,数学计算是如何完成的,而只是汇编呢?还是编译时编译器在编译为 asm 之前执行一些 cpu 任务?那么为什么还需要asm呢,编译器可以直接翻译成opcode并执行程序而无需汇编器的“一步一步”?
【问题讨论】:
-
编译器除了在你的 CPU 上还能在哪里运行? Calculator.exe 或 Excel 可以,为什么它不能做数学?
-
@SamiKuhmonen 如果您在不进行任何优化的情况下进行编译,那么将有一个用于该计算的 asm 生成源。 asm 是一对一映射到 CPU 的操作码生成。编译器不是。因此,如果编译器通过“内联”绕过 asm 生成,则必须有一些编译器间指令代替 asm 来完成这项工作。所以我想知道那个跨编译器的源代码。而且有趣的是,只有在
-O2上它才会执行,在较少或没有优化的情况下,它通常会生成到 asm(如前所述),所以这个跨编译器指令并不总是执行 -
Assembly 并不总是一对一地映射到操作码。不需要任何“跨编译器”的东西。如果您要求,编译器会对其进行优化。编译器已经把你写的代码转换成二进制了,为什么认为它也可以转换常量这么奇怪呢?它知道 2*4 是 8,所以它只能说 8。是的,只有在要求优化时才会进行优化。编译器做用户没有要求它做的事情是不好的。
-
汇编怎么知道 2*4 是 8? Excel 怎么知道是这样的?它不会在任何地方进行任何组装。它计算它,就像编译器一样。在输出操作码之前无需生成汇编。汇编只是人类可读的形式,编译器不需要它。我的意思是编译器已经接受了你编写的所有代码,并且知道如何基于它为多个处理器输出操作码,但它可以计算东西很奇怪?除非被询问,否则不进行优化的原因是调试,例如。
-
Gcc 确实会生成汇编代码,但其他人不会。例如,clang 直接生成机器码,只有在你明确请求时才输出汇编代码。但这不是重点:像常量折叠和内联这样的优化在生成任何程序集或机器代码之前很久就发生在中间表示上。这种中间表示是编译器内部的,通常不会暴露给用户。