15 May 2014

IMG-THUMBNAIL

《深入理解计算机系统》读书笔记

以经典的“hello, world”为例,分析编译的各个阶段。

/* $begin hello */
#include <stdio.h>
int main() 
{
    printf("hello, world\n");
}
/* $end hello */

IMG-THUMBNAIL

  • 预处理阶段。将include要引入的文件载入并替换掉include。将.c文件转换为.i文件。使用gcc命令加上参数-E

      `gcc -E hello.c -o hello.i`
    

    预处理结束后,生成了845行的hello.i文件。

  • 编译阶段。将.i文件编译成.s文件。

      `gcc -S hello.i -o hello.s`
    

    将其翻译成汇编语言。

          .file   "hello.c"
          .section    .rodata
      .LC0:
          .string "hello, world"
          .text
          .globl  main
          .type   main, @function
      main:
      .LFB0:
          .cfi_startproc
          pushq   %rbp
          .cfi_def_cfa_offset 16
          .cfi_offset 6, -16
          movq    %rsp, %rbp
          .cfi_def_cfa_register 6
          movl    $.LC0, %edi
          call    puts
          popq    %rbp
          .cfi_def_cfa 7, 8
          ret
          .cfi_endproc
      .LFE0:.LC0:
          .size   main, .-main
          .ident  "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1"
          .section    .note.GNU-stack,"",@progbits
    
    • .section .rodata后面定义了一个只读字符串常量.LC0,用来储存hello, world
    • .text后面开始是代码区,.globl main定义了主函数入口。
    • .cfi_startproc用在每个函数的开始,用于初始化一些内部数据结构。
    • rbp寄存器是ebp寄存器64位扩展,ebp寄存器扩展基址指针寄存器(extended base pointer)  其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部。

      以 %r 开头的表示 64-bit 寄存器;以 %e 开头的是 32-bit 寄存器。

      pushq,64位,所以是q。保存rbp,以便使用rbp作为栈指针。

    • .cfi_endproc在函数结束的时候使用与.cfi_startproc相配套使用。
    • movq %rsp, %rbp保存栈frame,现在有些编译器开发者致力于优化函数调用,优化这个frame就是其中一项。
    • movl $.LC0, %edi把字符串”hello, world”的地址放入edi。
    • call puts打印并且在后面自动追加换行符\n
  • 汇编阶段。将上一步生成的汇编代码通过汇编器编译成目标文件.o

    gcc -c hello.s -o hello.o

  • 链接阶段。将上一步得到的目标文件与printf.o链接合并到可执行文件hello中。编译完成。

    gcc hello.o -o hello


参考文献

原文链接:『hello, world』 如何运行,转载请注明来源!

EOF