2.1.1 MSVC-x86

在MSVC 2010中编译一下:

cl 1.cpp /Fa1.asm

(/Fa 选项表示生产汇编列表文件)

  1. CONST SEGMENT
  2. $SG3830 DB 'hello, world', 00H
  3. CONST ENDS
  4. PUBLIC _main
  5. EXTRN _printf:PROC
  6. ; Function compile flags: /Odtp
  7. _TEXT SEGMENT
  8. _main PROC
  9. push ebp
  10. mov ebp, esp
  11. push OFFSET $SG3830
  12. call _printf
  13. add esp, 4
  14. xor eax, eax
  15. pop ebp
  16. ret 0
  17. _main ENDP
  18. _TEXT ENDS

MSVC生成的是Intel汇编语法。Intel语法与AT&T语法的区别将在后面讨论。

编译器会把1.obj文件连接成1.exe。

在我们的例子当中,文件包含两个部分:CONST(放数据)和_TEXT(放代码)。

字符串"hello,world"在C/C++ 类型为const char*,然而它已经丢失了自己的名称。

编译器需要处理这个字符串,就自己给他定义了一个$SG3830。

所以例子可以改写为:

  1. #include <stdio.h>
  2. const char *$SG3830="hello, world";
  3. int main() {
  4. printf($SG3830);
  5. return 0;
  6. };

我们回到汇编列表,正如我们看到的,字符串是由0字节结束的,这也是C/C++的标准。

在代码部分,_TEXT,只有一个函数:main()。

函数main()与大多数函数一样都有开始的代码与结束的代码。

函数当中的开始代码结束以后,调用了printf()函数:CALL _printf

在PUSH指令的帮助下,我们问候语字符串的地址(或指向它的指针)在被调用之前存放在栈当中。

当printf()函数执行完返回到main()函数的时候,字符串地址(或指向它的指针)仍然在堆栈中。

当我们都不再需要它的时候,堆栈指针(ESP寄存器)需要改变。

ADD ESP, 4

意思是ESP寄存器加4。

为什么是4呢?由于是32位的代码,通过栈传送地址刚好需要4个字节。

在64位系统当中它是8字节。

ADD ESP, 4实际上等同于POP register

一些编辑器(如Intel C++编译器)在同样的情况下可能会用POP ECX代替ADD(例如这样的模式可以在Oracle RDBMS代码中看到,因为它是由Intel C++编译器编译的),这条指令的效果基本相同,但是ECX的寄存器内容会被改写。

Intel C++编译器可能用POP ECX,因为这比ADD ESP, X需要的字节数更短,(1字节对应3字节)。

在调用printf()之后,在C/C++代码之后执行return 0return 0是main()函数的返回结果。

代码被编译成指令XOR EAX, EAX

XOR事实上就是异或,但是编译器经常用它来代替MOV EAX, 0原因就是它需要的字节更短(2字节对应5字节)。

有些编译器用SUB EAX, EAX 就是EXA的值减去EAX,也就是返回0。

最后的指令RET 返回给调用者,他是C/C++代码吧控制返还给操作系统。