当我们编写一个C代码时要执行该代码我们可以通过clang source.c
即可生成a.out
文件用来执行,可这个过程,gcc生成的这个目标文件是如何来的呢?本文旨在讨论与分析它的来源。本文使用的是android-studio的ndk自带的/Android/Sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/bin/clang
。
可执行文件的生成
一个可执行文件需要经过几个步骤:
- 预处理
- 编译
- 汇编
- 链接
编写一个简单的示例
1 |
|
命名该源文件为hello.c,并引入相应的.h文件,hello.h
只有一行代码int add(int a, int b);
预处理
当我们执行clang -E hello.c -o hello.i
时,会生成一个名为hello.i的预处理文件,让我们打开这个文件看看。
可以看到,原本只有数行的代码,经过预处理之后膨胀到750行。所以我们看看它到底做了什么。看看下面的代码:
1 | # 1 "./hello.h" 1 |
预处理把hello.h里的add的声明给“拿”到了源代码里,并添加了其所在.h文件里的位置行号。是不是这样呢?其实源代码还引入了studio.h这个头文件,那我们看看第858+1行是不是倒数第二行,其最后一行的代码是不是extern int __overflow (FILE *, int);
就可以知道。
可以看到,确实是把引入的头文件的代码给引入到源代码里来了,实际上预处理是一个递归的过程,把需要用到的头文件的内容都会在预处理阶段搬到源代码里来,如果没有用到就不会啦。
当我们执行的是这样一段指令代码时clang -E -target arm-linux-android21 hello.c -o hello.i
,
可以发现android的armv7a的预处理的引用的是ndk里的stdio.h,而不是我们常规环境下的stdio.h头文件哦,我们还是以android下的环境为主吧。
编译
编译阶段是把预处理文件转换为汇编文件的这么一个过程,我们可以使用clang -S -target arm-linux-android21 hello.i -o hello.s
来生成一个名为hello.s的汇编文件。
那么在生成汇编文件的过程,编译器到底干了些什么呢?编译器在编译的阶段主要干了这么几件事
- 词法分析
- 语法分析
- 语义分析
- 代码优化
词法分析
扫描器通过有限状态机算法,将源代码的字符序列分割成一系列Token