第一个条件转一会领是JLE,即“Junp if Less or Equal”。如果上一条CMP指令的第一个操作数表达式小于或等于(不大于)第二个表达式,JLE将跳转到指令所标明的地址;如果不满足上述条件,则运行下一条指令,就本例而言程序将会调用printf()函数,第二个条件转移指令是JNE,”Jump if Not Equal“,如果上一条CMP指令的两个操作符不相等,则进行相应跳转。 第三个转移指令是JGE,即”Jump if Greater or Equal“,如果CMP的第一个表达式大于或等于第二个表达式(不小于),则进行跳转。这段程序里,如果三个跳转的判断条件都不满足,将不会调用pringtf()函数;不过除非进行特殊干预,,否则这种情况应该不会发生。 现在我们观察 f_unsigned()函数的汇编指令。f_unsigned()函数和 f_signed()函数大体相同。它们的区别集中体现在条件转移指令上:f_unsinged()函数的使用的条件转移指令是 JBE 和 JAE,而 f_signed()函数使用的条件转移指令则是 JLE 和 JGE。 使用 GCC 编译上述程序,可得到 f_unsigned()的汇编指令如下。
栈是计算机科学李最重要的且最基础的数据结构之一。 从技术上讲,栈就是CPU寄存器里面的某个指针所指向的一片内存区域。这里所说的某个指针通常位于x86/x64平台的ESP寄存器/RSP寄存器,以及ARM平台的SP寄存器。 操作站最常见的指令是PUSH和POP,在 x86 和 ARM Thumb 模式的指令集里都有这两条指令。 PUSH指令会对ESP/RSP/SP寄存器的值进行减法运算,使之减去4(32位)或8(64位),然后将操作数写到上述寄存器里的指针所指向的内存中。 POP指令是PUSH的逆操作:他先从栈指针(Stack Pionter,上面三个寄存器之一)指向的内存中读取数据,用以备用(通常是写到其他寄存器里面),然后再将栈指针的数值加上4或8. 在分配栈的空间之后,栈指针,即Stack Pointer所指向的地址是栈的底部。PUSH将减少栈指针的数值,而POP会增加它的数值。栈的“底”实际上使用的是整个栈的最低地址,即是整个栈的启始内存地址。 ARM的栈分为递增栈和递减栈。递减栈(descending stack)的首地址是栈的最高地址,栈向低地址增长,栈指针的值随栈的增长而减少,如STMFA/LMDFA、STMFD/LDMFD、STMED、LDMEA等指令,都是递增栈的操作指令。
如果使用MSVC2008编译上面的问题程序,就会得到报告: c:\tmp6>cl ss.cpp /Fass.asm Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. ss.cpp c:\tmp6\ss.cpp(4) : warning C4717:’f’ : recursive on all control paths. Function will cause runtime stack overflow 但它还是会生成汇编文件
1 2 3 4 5 6 7 8 9 10 11 12
?f@@YAXXZ PROC ; f ; File c:\tmp6\ss.cpp ; Line 2 push ebp mov ebp, esp ; Line 3 call ?f@@YAXXZ ; f ; Line 4 pop ebp ret 0 ?f@@YAXXZ ENDP ; f