gcc编译过程简述
在linux系统上,从源⽂件到⽬标⽂件的转化是由编译器完成的。以hello.c程序的编译为例,如下:
dfcao@linux: gcc -o hello hello.c
在这⾥,gcc编译器读取源⽂件hello.c,并把它翻译成⼀个可执⾏⽂件 hello。
这个翻译过程可分为四个阶段逐步完成:预处理,编译,汇编,链接,如下图所⽰。
逐步做下简单分析:
在未编译前,hello.c 的源代码如下
#include <stdio.h>
int main()
{
printf("hello, world\n");
}
第⼀步、预处理阶段
执⾏命令: gcc -o hello.i -E hello.c
或者 cpp -o hello.i hello.c (这⾥cpp不值c plus plus,⽽是预处理器the C Preprocessor)
预处理器cpp根据以字符开头#开头的命令,修改原始C程序。⽐如hello.c中的第⼀⾏为 #include <stdio.h>,预处理器便将stdio.h的内容直接插⼊到程序中。
预处理之后得到⽂本⽂件hello.i,打开如下
# 1"hello.c"
# 1"<built-in>"
# 1"<;命令⾏>"
# 1"hello.c"
# 1"/usr/include/stdio.h"134
...
...
...
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 940"/usr/include/stdio.h"34
# 2"hello.c"2
int main()
{
printf("hello, world\n");
}
在源代码的前⾯插⼊了stdio.h,整个hello.i 的⾏数由hello.c的6⾏变到了855⾏!
第⼆步、编译阶段
执⾏命令: gcc -o hello.s -S hello.i
或者 ccl -o hello.s hello.i
编译器ccl 将⽂本⽂件hello.i 翻译为hello.s,这个⽂件⾥⾯包含⼀个汇编程序,如下
.file "hello.c"
.section .rodata
.LC0:
.string "hello, world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",@progbits
汇编语⾔是⾮常有⽤的,因为它将不同⾼级语⾔的不同编译器提供了通⽤的输出语⾔。例如,C和Fortran 的在此步编译产⽣的输出⽂件都是⼀样的汇编语⾔。
第三步、汇编阶段
执⾏命令: gcc -o hello.o -c hello.s
或者 as -o hello.o hello.s
汇编器as 将hello.s 翻译成机器语⾔保存在hello.o 中。这是个⼆进制⽂件,⽤命令hexdump hello.o打开如下
0000000 457f 464c 010100010000000000000000
000001000010003000100000000000000000000
0000020011c0000000000000034000000000028
0000030000d000a8955 83e5 f0e4 ec83 c710 2404
000004000000000 fce8 ffff c9ff 00c36568 6c6c
0000050 2c6f 7720 726f 646c 00004347 3a43 2820
00000606255 6e75 7574 4c2f 6e69 7261 206f 2e34
0000070 2e36 2d33 75317562 746e 35752029 2e34
0000080 2e36 00330014000000000000 7a01 0052
0000090 7c01 01080c1b040401880000001c0000
00000a0001c000000000000001700004100080e
00000b002850d4253050cc504040000 2e00 7973
00000c0 746d 6261 2e00 747374726261 2e00 6873
00000d0747374726261 2e00 6572 2e6c 65747478
00000e0 2e00 61646174 2e00 73620073 722e 646f
00000f074610061 632e 6d6f 656d 746e 2e00 6f6e
00001006574 472e 554e 732d 6174 6b63 2e00 6572
0000110 2e6c 6865 665f 6172 656d 000000000000
000012000000000000000000000000000000000
*
000014000000000001f00000001000000060000
000015000000000003400000017000000000000
0000160000000000004000000000000001b0000
000017000090000000000000000000003e80000
000018000100000000b00000001000000040000
000019000080000002500000001000000030000
00001a000000000004c00000000000000000000
第四步、链接阶段
执⾏命令: gcc -o hello hello.o
linux下gcc编译的四个步骤
或者 ld -o hello hello.o
注意!hello程序调⽤了printf 函数,这个函数是标准C库中的⼀个函数,他保存在⼀个名为printf.o 的⽂件中,这个⽂件必须以某种⽅式合并到我们的hello.o的程序中。
链接器ld 负责处理这种合并。结果得到hello 可执⾏⽂件,可以被加载到内存中由系统执⾏。
./hello
总结
编译器的编译过程:
源⽂件-->预处理-->编译/优化-->汇编-->链接-->可执⾏⽂件。
平常⽤的最多、看起来⼀句命令就搞定的⼀次编译背后其实⾮常不简单。此博作简单记录,能⼒到了之后再做深⼊分析。#---------------------------------------------------------------------------------#
参考⽂献
《Computer Systems: A Programmer's Perspective》 by Randal Bryant.
“编译过程” by ,
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论