如何从.c文本变成可以打印出”hello world“的可执行文件的呢?
/*include head file*/
#include<stdio.h>
/*the main function*/
int main(int argc,char *argv[])
{
printf("Hello World!");
return 0 ;
}
linux编译并运行:
gcc -o helloWorld helloWorld.c
./helloWorld
Hello World!
将一个.c文本变成可以打印出”hello world“的可执行文件,由4个步骤构成:预处理,编译,汇编,链接。
- 预处理
预处理主要是处理源代码中以#开头的指令(#pragma 除外)
例如本文hello world程序中的#include,预处理之后会将stdio.h的内容插入到预处理指令的位置。
想要只生成预处理之后的内容,可以使用下面的指令:
gcc -E -o helloWorld.i helloWorld.c #-E参数表示只进行预处理
生成的helloWorld.i即为预处理之后的内容,有兴趣的可以打开文件查看里面的内容,会发现stdio.h的位置被其实际内容所替代。预处理之后,注释内容也会被删除,宏定义会被展开。
- 编译
预处理之后就需要对生成的预处理文件进行词法分析,语法分析,语义分析,最终产生汇编代码文件,说白点可以简单理解为将C代码“翻译”成汇编代码。
可以通过命令查看编译之后得到的汇编代码:
gcc -S -o helloWorld.s helloWorld.c #-S参数表示只到生成汇编为止
cat helloWorld.s
.file "helloWorld.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
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl $.LC0, %edi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
- 汇编
汇编是将汇编代码翻译成机器可执行的指令,生成目标文件。整个过程较为简单,几乎只是按照汇编指令和机器指令进行一一翻译。我们可以用下面的命令获得汇编后的内容:
gcc -o helloWorld.o -c helloWorld.c
od helloWorld.o #查看二进制内容
0000000 042577 043114 000402 000001 000000 000000 000000 000000
0000020 000001 000076 000001 000000 000000 000000 000000 000000
0000040 000000 000000 000000 000000 001260 000000 000000 000000
0000060 000000 000000 000100 000000 000000 000100 000015 000012
0000100 044125 162611 101510 010354 076611 044374 072611 137760
(其他内容未显示)
- 链接
链接是以某种方式将各个目标文件整合在一起,生成最后的可执行文件。我们的hello程序中调用了printf函数,但是并不存在于helloWorld.o中,而是存在于libc.so或libc.a中,因此我们需要通过链接将它们融合在一起。
gcc -o helloWorld helloWorld.c
执行上面的命令之后,就得到了我们的helloWorld程序了,在linux下,它是一种ELF格式的文件。
我们可以通过ldd命令看到helloWorld程序链接了系统的库:
ldd helloWorld
linux-vdso.so.1 => (0x00007ffe9ef11000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0d9f038000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0d9f402000)
若删除系统中的libc.so库(记得事先备份),发现能够编译过,却在最后链接失败了。
- 编译过程流程图:
由于整个编译过程分阶段进行,我们可以看到不同类型的问题在不同阶段出现并且有先后顺序。正因如此,链接问题在编译的最后阶段才会出现。
gcc编译系统本身调用了很多其他相关工具,可以加上–verbose观察其详细编译过程,发现gcc命令调用了预处理器,编译器,汇编器,链接器等命令。