C语言真正的编译过程

说实话,不少人作了好久的C/C++,也用了不少IDE,可是对于可执行程序的底层生成一片茫然,这无疑是一种悲哀,能够想象到大公司面试正好被问到这样的问题,有多悲催不言而喻,这里正因为换工做的缘故,因此打算系统的把以前用到的C/C++补一补。这里权且当作抛砖引玉,大神飘过。面试

【总述】app

从一个源文件(.c)到可执行程序到底经历了哪几步,我想大多数的人都知道,到时到底每一步都作了什么,我估计也没多少人可以说得清清楚楚,明明白白。ide

其实总的流程是这样的。函数

【第一步】编辑hello.c优化

#include <stdio.h>
#include <stdlib.h>
int main()
{
         printf("hello world!\n");
         return 0;
 }

【第二步】预处理翻译

预处理过程实质上是处理“#”,将#include包含的头文件直接拷贝到hell.c当中;将#define定义的宏进行替换,同时将代码中没用的注释部分删除等3d

具体作的事儿以下:调试

(1)将全部的#define删除,而且展开全部的宏定义。说白了就是字符替换code

(2)处理全部的条件编译指令,#ifdef #ifndef #endif等,就是带#的那些blog

(3)处理#include,将#include指向的文件插入到该行处

(4)删除全部注释

(5)添加行号和文件标示,这样的在调试和编译出错的时候才知道是是哪一个文件的哪一行

(6)保留#pragma编译器指令,由于编译器须要使用它们。

 

gcc -E hello.c -o a.c能够生成预处理后的文件。经过查看文件内容和文件大小能够得知a.c讲stdio.h和stdlib.h包含了进来。

【第三步】编译

编译的过程实质上是把高级语言翻译成机器语言的过程,即对a.c作了这些事儿

(1)词法分析,

(2)语法分析

(3)语义分析

(4)优化后生成相应的汇编代码

从 高级语言->汇编语言->机器语言(二进制)

gcc -S hello.c -o a.s能够生成汇编代码

汇编代码以下

.file	"hello.c"
	.section	.rodata
.LC0:
	.string	"hello world!"
	.text
	.globl	main
	.type	main, @function
main:
.LFB2:
	.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
	movl	$0, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
	.section	.note.GNU-stack,"",@progbits

 

gcc -c hello.c -o a.o将源文件翻译成二进制文件。类Uinx系统编译的结果生生成.o文件,Windows系统是生成.obj文件。

编译的过程就是把hello.c翻译成二进制文件

【第四步】连接

就像刚才的hello.c它使用到了C标准库的东西“printf”,可是编译过程只是把源文件翻译成二进制而已,这个二进制还不能直接执行,这个时候就须要作一个动做,

将翻译成的二进制与须要用到库绑定在一块。打个比方编译的过程就向你对你老婆说,我要吃雪糕。你只是给你老婆发出了你要吃雪糕的诉求而已,可是雪糕尚未到。

绑定就是说你要吃的雪糕你的老婆已经给你买了,你能够happy。

gcc hello.c -o a能够生成可执行程序。即gcc不带任何参数。ldd就能够看到你的可执行程序依赖的库。

能够看到a.o的大小是1.1k,毕竟他只是把源文件翻译成二进制文件。a却有7k,应该是他多了不少“绳子”吧。在运行的时候这些“绳子”就将对应的库函数“牵过来”。很形象的比喻是否是?哈哈。libc.so.6 中就对我们用的printf进行了定义。

这就是编译的整个流程

相关文章
相关标签/搜索