有人能帮我理解这个汇编代码吗?我对汇编语言完全陌生,我就是搞不懂.下列程序集代码应产生此功能:
函数(Int a) {返回a* 34 }
这些评论//是我的想法,如果我错了,请纠正我
//esp = stack-pointer, ebp = callee saved, eax = return value
pushl %ebp // a is pushed on stack
movl %esp,%ebp // a = stackpointer
movl 8(%ebp),%eax // eax = M(8 + a).But what is in M(8 + a)?
sall $4,%eax // eax << 4
addl 8(%ebp),%eax // eax = M(8 + a)
addl %eax,%eax // eax = eax + eax
movl %ebp,%esp // eax = t
popl %ebp // pop a from stack
ret谁能给我解释一下怎么弄明白吗?非常感谢!
发布于 2018-12-30 22:54:40
pushl %ebp // a is pushed on stack
movl %esp,%ebp // a = stackpointer正如在评论中所指出的,ebp与a无关。ebp是堆栈基指针--这段代码将ebp的旧值保存到堆栈中,然后将堆栈指针保存在ebp中。
movl 8(%ebp),%eax // eax = M(8 + a).But what is in M(8 + a)?对,是这样。堆栈上的是eax的输入值。
sall $4,%eax // eax << 4对,是这样。(结果被分配回eax。)
addl 8(%ebp),%eax // eax = M(8 + a)不,你误会了。这将8(ebp)堆栈上的值--即a的原始值--添加到eax。加法应用于值,而不是内存地址。
addl %eax,%eax // eax = eax + eax对,是这样。eax的值在这里之后不会被修改,所以这是函数的返回值。
movl %ebp,%esp // eax = t
popl %ebp // pop a from stack
ret此代码反转前两个指令的效果。这是一个标准的清理序列,与a无关。
这一职能的重要部分可概括为:
a1 = a << 4; // = a * 16
a2 = a1 + a; // = a * 17
a3 = a2 + a2; // = a * 34
return a3;发布于 2018-12-31 07:12:16
这是非优化代码,因为您使用-O0编译(编译速度快,跳过大多数优化传递)。遗留的堆栈帧设置/清理只是噪音。arg位于堆栈上,位于返回地址的正上方,即函数输入处的4(%esp)上。(另见如何消除GCC/clang组件输出中的“噪音”?)
令人惊讶的是,除非对旧CPU进行调优,否则编译器使用3条指令进行移位和加法,而不是使用imull $34, 4(%esp), %eax ret,/ / 。2指令是现代gcc和嘎嘎的截止,他们的默认调谐。参见例如如何使用x86中两个连续的leal指令将寄存器乘以37?
但是这可以通过使用LEA的两个指令来完成(不包括mov来复制寄存器);代码会膨胀,因为您编译时没有优化。(或者您调到了一个旧CPU,在那里可能有一些理由可以避免LEA。)
我想你一定是用gcc来做这件事的;禁用其他编译器的优化,总是使用imul来乘以一个非2的幂。但是,我找不到一个gcc版本+选项的戈德波特编译器浏览器,给出了准确的代码。我没试过所有可能的组合。MSVC 19.10 -O2使用与代码相同的算法,包括两次加载a。
用gcc5.5编译(这是最新的gcc,它不仅使用imul,甚至在-O0),我们得到了类似于您的代码,但不完全是这样。(相同的操作顺序不同,并且不会从内存中两次加载a )。
# gcc5.5 -m32 -xc -O0 -fverbose-asm -Wall
func:
pushl %ebp #
movl %esp, %ebp #, # make a stack frame
movl 8(%ebp), %eax # a, tmp89 # load a from the stack, first arg is at EBP+8
addl %eax, %eax # tmp91 # a*2
movl %eax, %edx # tmp90, tmp92
sall $4, %edx #, tmp92 # a*2 << 4 = a*32
addl %edx, %eax # tmp92, D.1807 # a*2 + a*32
popl %ebp # # clean up the stack frame
ret在:gcc5.5 -m32 -O3 -fverbose-asm上使用与GCC版本相同的优化编译戈德波特编译器浏览器:gcc5.5 -m32 -O3 -fverbose-asm,我们得到:
# gcc5.5 -m32 -O3. Also clang7.0 -m32 -O3 emits the same code
func:
movl 4(%esp), %eax # a, a # load a from the stack
movl %eax, %edx # a, tmp93 # copy it to edx
sall $5, %edx #, tmp93 # edx = a<<5 = a*32
leal (%edx,%eax,2), %eax # eax = edx + eax*2 = a*32 + a*2 = a*34
ret # with a*34 in EAX, the return-value reg in this calling convention使用gcc 6.x或更高版本的,我们得到了这种高效的:在现代英特尔CPU上,具有存储源的imul-immediate仅解码为单个微融合uop,而整数乘在Core2和Ryzen之后仅具有3个周期延迟。(https://agner.org/optimize/)。
# gcc6/7/8 -m32 -O3 default tuning
func:
imull $34, 4(%esp), %eax #, a, tmp89
ret但是,对于-mtune=pentium3**,,,奇怪的是,我们没有得到一个LEA**。这看起来像是漏掉的优化。LEA对奔腾3/奔腾-M有一个周期的潜伏期。
# gcc8.2 -O3 -mtune=pentium3 -m32 -xc -fverbose-asm -Wall
func:
movl 4(%esp), %edx # a, a
movl %edx, %eax # a, tmp91
sall $4, %eax #, tmp91 # a*16
addl %edx, %eax # a, tmp92 # a*16 + a = a*17
addl %eax, %eax # tmp93 # a*16 * 2 = a*34
ret这与您的代码相同,但是使用reg mov而不是从堆栈中重新加载来向shift结果添加a。
https://stackoverflow.com/questions/53981965
复制相似问题