「C语言」C输出hello world!系统发生了什么?
本篇文章全部摘抄自学长博客供以后学习:
http://efraim.me/2015/12/05/tech-linux-2015-12-05/
排版因与博客园编辑器不同而稍作修改。
输出hello world!系统发生了什么?
经典的hello world!
1 #include <stdio.h> 2 3 int main () 4 { 5 printf("hello world!"); 6 }
该段程序,在hello world过程中,系统发生了什么?
0X00 新建hello.c
hello.c文件,是文件由0/1的位(bit)序列,8位组成一组,称为字节,一个个字节表示文件中的一个个字符。
由此引出,系统中的所有信息(磁盘文件,系统程序,网络传输的数据等等),都是一些0/1序列,当我们打开hello.c文件时,系统会按某种规则文件进行解析,最后呈现出人类能看懂的字符,而不是二进制0/1。
区分不同数据对象的唯一方法,就是系统读取这个文件时上下文(可以理解为当时的环境),比如,在不同的上下文中,一个同样的字节序列,有可能表示一个整数,浮点数,字符数或者机器指令。
0x01 解析hello.c
C语言是高级语言,所以这个形式的代码能让人读懂。但系统不认识,为了让系统能认识,需要将hello.c转化成一系机器能认识的语言指令,然后将这些指令按照一种称为可执行目标程序的格式进行打包,并以二进制磁盘文件形式存放,目标程序也可以称为可执行文件。
这里,我用GCC编译器,编译解析hello.c
- 预处理阶段:-E
预处理器(cpp)主要处理根据以字符#开头的命令,修改原始的C程序。比如hello.c中第一行的#include <stdio.h> 命令会告诉预处理其读区系统头文件stdio.h的内容.并把它直接插入到程序文本中。此过程,会得到以.i作为扩展名。
`$ gcc -E hello.c -o hello.i`//预编译hello.c文件 输出hello.i文件
hello.i文件内容都有些什么呢?
1 //hello.i 2 # 1 "hello.c" 3 # 1 "<built-in>" 1 4 # 1 "<built-in>" 3 5 # 325 "<built-in>" 3 6 # 1 "<command line>" 1 7 # 1 "<built-in>" 2 8 # 1 "hello.c" 2 9 # 1 "/usr/include/stdio.h" 1 3 4 10 # 64 "/usr/include/stdio.h" 3 4 11 # 1 "/usr/include/sys/cdefs.h" 1 3 4 12 # 533 "/usr/include/sys/cdefs.h" 3 4 13 # 1 "/usr/include/sys/_symbol_aliasing.h" 1 3 4 14 # 534 "/usr/include/sys/cdefs.h" 2 3 4 15 # 599 "/usr/include/sys/cdefs.h" 3 4 16 # 1 "/usr/include/sys/_posix_availability.h" 1 3 4 17 # 600 "/usr/include/sys/cdefs.h" 2 3 4 18 # 65 "/usr/include/stdio.h" 2 3 4 19 # 1 "/usr/include/Availability.h" 1 3 4 20 # 162 "/usr/include/Availability.h" 3 4 21 # 1 "/usr/include/AvailabilityInternal.h" 1 3 4 22 # 163 "/usr/include/Availability.h" 2 3 4 23 # 66 "/usr/include/stdio.h" 2 3 4 24 # 1 "/usr/include/_types.h" 1 3 4 25 # 27 "/usr/include/_types.h" 3 4 26 # 1 "/usr/include/sys/_types.h" 1 3 4 27 # 33 "/usr/include/sys/_types.h" 3 4 28 # 1 "/usr/include/machine/_types.h" 1 3 4 29 # 32 "/usr/include/machine/_types.h" 3 4 30 # 1 "/usr/include/i386/_types.h" 1 3 4 31 # 37 "/usr/include/i386/_types.h" 3 4 32 typedef signed char __int8_t; 33 typedef unsigned char __uint8_t; 34 typedef short __int16_t; 35 typedef unsigned short __uint16_t; 36 typedef int __int32_t; 37 typedef unsigned int __uint32_t; 38 typedef long long __int64_t; 39 typedef unsigned long long __uint64_t; 40 typedef long __darwin_intptr_t; 41 typedef unsigned int __darwin_natural_t; 42 # 70 "/usr/include/i386/_types.h" 3 4 43 typedef int __darwin_ct_rune_t;
- 编译阶段:-S
编译器(ccl)将hello.i翻译成文件文件hello.s文件,它包含一个汇编语言程序,汇编程序中的每条语句都以一种标准的文本格式确切地描述了一条条低级机器语言指令.所以该过程会检查代码规范,语法,词法分析,具体如下图.只有编译成功之后,才能生成具体的汇编代码。
$ gcc -S hello.i -o hello.s
1 //hello.s 2 .section __TEXT,__text,regular,pure_instructions 3 .macosx_version_min 10, 11 4 .globl _main 5 .align 4, 0x90 6 _main: ## @main 7 .cfi_startproc 8 ## BB#0: 9 pushq %rbp 10 Ltmp0: 11 .cfi_def_cfa_offset 16 12 Ltmp1: 13 .cfi_offset %rbp, -16 14 movq %rsp, %rbp 15 Ltmp2: 16 .cfi_def_cfa_register %rbp 17 subq $16, %rsp 18 leaq L_.str(%rip), %rdi 19 movl $0, -4(%rbp) 20 movb $0, %al 21 callq _printf 22 xorl %ecx, %ecx 23 movl %eax, -8(%rbp) ## 4-byte Spill 24 movl %ecx, %eax 25 addq $16, %rsp 26 popq %rbp 27 retq 28 .cfi_endproc 29 30 .section __TEXT,__cstring,cstring_literals 31 L_.str: ## @.str 32 .asciz "hello world!" 33 .subsections_via_symbols
- 汇编阶段:-c
汇编器(as)将hello.s 文件翻译成机器语言指令,,把这些指令打包成一种叫做可重定向目标程序的格式,并且保存在hello.o文件中.该文件是一个二进制文件,他的字节编码是机器语言指令而不是字符,如果用编辑器打开将是一段乱码。
- 链接阶段:
注意,hello程序调用了printf函数,他是每个C编译器都会提供的标准C库的一个函数.该函数存在于一个名为printf.o的单独的预编译好的文件中,必须将该文件以某种方式合并到我们的hello.o的文件中。连接器(ld),就复制处理这种合并。结果就得到一个hello文件,是一个可执行文件。