本文主要是介绍1. 编译和链接----你真的了解HelloWord吗,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
- 平时的应用程序开发,很少需要关注编译和链接的过程,因为通常的开发环境都是流行的集成开发环境(IDE),比如VS等,这些IDE通常将编译和链接的过程一步完成,我们通常将这种合并到一起的过程称为构建。其实即使用命令行来编译一个源代码文件,简单的一句
gcc hello.c
命令就包含了非常复杂的过程。- 虽然这些默认配置、编译和链接参数对于大部分的应用程序开发已经足够了,但是在开发过程中,我们会被这种便捷所迷惑,软件的运行机制和机理被掩盖了,一些程序莫名其妙的错误我们无法解决,性能瓶颈也束手无策。只能看到现象,看不到本质,所以要深入了解这些机制。
1.编译器到底隐藏了哪些东西
// hello.c
#include <stdio.h>int main()
{printf("Hello World\n");return 0;
}
在Linux下,当我们使用GCC来编译该程序时,只需要最简单的命令
gcc hello.c
./a.out
事实上,上述过程可以分解为4个步骤:预处理、编译、汇编、链接
1.1 预编译
-
首先是源代码文件
hello.c
和相关的头文件,如stdio.h
等被预编译器cpp 预编译成一个.i
文件。对于c++
来说,它的源文件扩展名可能是.cpp
或者.cxx
,头文件的扩展名可能是.hpp
,而预编译后的文件扩展名是.ii
。 -
第一步的预编译过程相当于如下命令:其中
-E
表示只进行预编译gcc -E hello.c -o hello.i
或者
cpp hello.c > hello.i
- 预编译过程主要处理源文件中以
#
开始的预编译指令,比如#include, #define
等。规则主要如下:- 将所有的
#define
删除,并且展开所有的宏定义 - 处理所有的条件编译指令,比如
#if, #ifdef, #elif, #else, #endif
条件编译 - 处理
#include
预编译指令,将被包含的文件插入到该预编译指令的位置,注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。 - 删除所有的注释
//. /* */
- 添加行号和文件名标识,比如
#2 "hello.c" 2
,以便编译时编译器产生调试用的行号的信息,及用于编译时产生编译错误或警告时能够显示行号。 - 保留所有的
#progma
编译器指令,因为编译器需要使用他们
- 将所有的
可以看到简单的几行代码,预编译之后变成了733行!!!
行号也互相匹配
经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中,所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题
1.2 编译
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。这个过程是整个程序构建的核心部分,也是最复杂的部分。
gcc -S hello.i -o hello.s
或者gcc -S hello.c -o hello.s
-
现在版本的GCC把预编译和编译两个步骤合并成一个步骤,使用一个叫做
ccl
的程序来完成这两个步骤。/usr/lib/gcc/x86_64-linux-gnu/9/cc1
,因此也可以直接调用ccl
来完成/usr/lib/gcc/x86_64-linux-gnu/9/cc1 hello.c
对于C语言来说,这个预编译和编译的程序是cc1
,对于C++来说,有对应的程序叫做cc1plus
;Objective-C是cc1obj
;fortran
是f771
;Java是jc1
所以实际上gcc命令只是这些后台程序的包装,它会根据不同的参数要求去调用预编译程序cc1, 汇编器as,链接器ld
1.3 汇编
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,没有复杂的语法,也没有语义,也不需要指令优化,按照对照表一一翻译就行。
汇编过程可以调用汇编器as
来完成
as hello.s -o hello.o
或者
gcc -c hello.s -o hello.o
或者直接使用gcc命令,从C源代码文件开始,经过预编译、编译和汇编直接输出目标文件
gcc -c hello.c -o hello.o
2.1.4 链接
- 为什么汇编器不直接输出可执行文件而是输出一个目标文件呢?
- 链接过程到底包含了什么内容?
- 为什么要链接?
下面我们来看看怎样调用ld
才可以产生一个能够正常运行的HelloWorld
程序
ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginT.o-L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib -L/lib hello.o--start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-linux-gnu/9crtend.o/usr/lib/crtn.o
如果把所有的路径都省略掉,那么就是
ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc -end-group crtend.o crtn.o
也就是说,我们需要将一大堆文件链接起来才可以看到a.out
,也就是最终的可执行文件。
- 那么
crt1.o, crti.o ....
这些文件是什么? - 做什么用?
-lgcc -lgcc_eh -lc
这些是什么参数?为什么使用?- 为什么要将他们和
hello.o
链接起来才能得到可执行文件?
这些需要后续的章节一点点看了,未完待续。。。
这篇关于1. 编译和链接----你真的了解HelloWord吗的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!