C/C++编译过程(转)

转帖自:http://hi.baidu.com/lru52777/blog/item/244aa913b9faaf0e5aaf537c.html

 


总体来说,C/C++源代码要经过:预处理编译汇编连接四步才能变成相应平台下的可执行文件。大多数时候,程序员通过一个命令就能完成上述四个步骤。比如下面这段C的“Hello world!”代码:

File: hw.c

#include <stdio.h>

int main(int argc, char *argv[])
{
printf("Hello World!/n");

return 0;
}


如果用gcc编译,只需要一个命令就可以生成可执行文件hw:

xiaosuo@gentux hw $ gcc -o hw hw.c

xiaosuo@gentux hw $ ./hw Hello World!


我们可以用-v参数来看看gcc到底在背后都做了些什么动作:

稍微整理一下,去掉一些冗余信息后,如下:

cc1 hw.c -o /tmp/ccYB6UwR.s
as -o /tmp/ccq8uGED.o /tmp/ccYB6UwR.s
ld -o hw /tmp/ccq8uGED.o


以上三个命令分别对应于编译步骤中的预处理+编译、汇编和连接。预处理和编译还是放在了一个命令(cc1)中进行的,可以把它再次拆分为以下两步:

cpp -o hw.i hw.c
cc1 hw.i -o /tmp/ccYB6UwR.s



接下来我们按照编译顺序看看编译器每一步都做了什么。

首先是预处理,预处理后的文件hw.i:

# 1 "hw.c"
# 1 "<built-in>"
# 1 "<command line>"

...
__extension__ typedef __quad_t __off64_t;
__extension__ typedef int __pid_t;
__extension__ typedef struct { int __val[2]; } __fsid_t;

...
extern int remove (__const char *__filename) __attribute__ ((__nothrow__));

extern int rename (__const char *__old, __const char *__new) __attribute__ ((__nothrow__));

...

int main(int argc, char *argv[])
{
printf("Hello World!/n");

return 0;
}


:由于文件比较大,所以只留下了少部分具有代表性的内容。

可以看见预处理器把所有要包含(include)的文件(包括递归包含的文件)的内容都添加到了原始的C源文件中,然后把其输出到输出文件,除此之外,它还展开了所有的宏定义,所以在预处理器的输出文件中你将找不到任何宏。这也提供了一个查看宏展开结果的简便方法。

第二步“编译”,就是把C/C++代码“翻译”成汇编代码:

.file "hw.c"
.section .rodata
.LC0:
.string "Hello World!/n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
subl %eax, %esp
subl $12, %esp
pushl $.LC0
call printf
addl $16, %esp
movl $0, %eax
leave
ret
.size main, .-main
.section .note.GNU-stack,"",@progbits
.ident "GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)"


这个汇编文件比预处理后的C/C++文件小了很多,去除了很多不必要的东西,比如说没用到的类型声明和函数声明等。

第三步“汇编”,将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux上一般表现为ELF目标文件。

xiaosuo@gentux hw $ file hw.o
hw.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped


最后一步“连接”,将上步生成的目标文件和系统库的目标文件和库文件连接起来,最终生成了可以在特定平台运行的可执行文件。为什么还要连接系统库中的某些目标文件(crt1.o, crti.o等)呢?这些目标文件都是用来初始化或者回收C运行时环境的,比如说堆内存分配上下文环境的初始化等,实际上crt也正是C RunTime的缩写。这也暗示了另外一点:程序并不是从main函数开始执行的,而是从crt中的某个入口开始的,在Linux上此入口是_start。以上Makefile生成的是动态连接的可执行文件,如果要生成静态连接的可执行文件需要将Makefile中的相应段修改:

hw: hw.o
ld -m elf_i386 -static -o hw /usr/lib/crt1.o /
/usr/lib/crti.o /
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtbeginT.o /
-L/usr/lib/gcc/i686-pc-linux-gnu/3.4.6 /
-L/usr/i686-pc-linux-gnu/lib /
-L/usr/lib/ /
hw.o --start-group -lgcc -lgcc_eh -lc --end-group /
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtend.o /
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../crtn.o


至此,一个可执行文件才最终创建完成。通常的项目中并不需要把编译过程分得如此之细,前三步一般是合为一体的,在Makefile中表现如下:

hw.o: hw.c
gcc -o hw.o -c hw.c


实际上,如果对hw.c进行了什么更改,那么前三步大多数情况下都是不可避免的。所以把他们写在一起也并没有什么坏处,相反倒可以用--pipe参数告诉编译器用管道替代临时文件,从而提升编译的效率。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值