『hello, world』 如何运行
15 May 2014
《深入理解计算机系统》读书笔记
以经典的“hello, world”为例,分析编译的各个阶段。
/* $begin hello */
#include <stdio.h>
int main()
{
printf("hello, world\n");
}
/* $end hello */
-
预处理阶段。将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
参考文献
- 《深入理解计算机系统》
- -E参数在gcc上的好处 - xiaoshe的专栏
- GCC编程四个过程:预处理-编译-汇编-链接 - 富贵
- 分析.cpp文件编译生成的汇编文件里语句的作用 - JustinYo
- 对gcc编译汇编码解析
- 汇编学习笔记一则 - Seeking Fun
- 简易学习汇编 - ChinaUnix
原文链接:『hello, world』 如何运行,转载请注明来源!
–EOF–