平常我们很少关注编译和链结的过程,因为开发环境都集成开发的环境,比如Visual Studio、Eclipse,这样的IDE一般都将编译和链结的过程一步完成,因此我们必须深入了解这些被隐藏的过程。
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
给定一个普通的输出 Hello World 的程序
通常我们可以使用GCC 进行编译
$ gcc -o hello hello.c 会输出 ELF 格式的执行档
$ ./hello 即可执行
事实上,上述的过程可分解为四个步骤,分别是预处理、编译、组译和链结
C预处理器参照标头档stdio.h的内容,展开macro和验证prototype,并输出成一个.i文件。输出的结果就不会再见到 "#"开头字样,预编译的过程的命令用 -E 表示:
$ gcc -E hello.c -o hello.i
预编译完成後,替换完标头档和macro之後的样子如下
extern int printf (const char *__restrict __format, ...);
........
int main(void)
{
printf("Hello, world!\n");
return 0;
}
上面程序码撷取了标头档文件中相关的部分,省略了stdio.h的其他部分,在这一步骤注释也会被移除。
※不同的原码文件,可能会引用一个标头档(比如stdio.h),编译的时候,标头档也必须一起编译,而编译器会先编译标头档,这是为了确保标头档只需编译一次,不必每次用到的时候都重新编译。
编译过程就是把预处理完的文件进行一系列字汇分析、语法分析、语意分析、最佳化後生成对映的组合语言(hello.s),编译过程的命令如下:
$gcc -S hello.i -o hello.s
组译就是一个将组合语言转换成机器可以执行的指令,组译过程我们可以使用组译器 as 来完成:
as hello.s -o hello.o 或 gcc -c hello.s -o hello.o
会发现其实出来的 hello.o 并无法执行,因为缺少连结的过程。
在编译阶段并不知道printf的位址,所以暂时会以printf符号名称代替
main
LDR R1, [R2 + l2]
BAL printf
而在组译器输出的对应的组合语言,仍然不知道printf的位址,因此暂时不填入
main:
EC 00 00 12
F0 ?? ?? ??
printf 实作於libc.a(C语言标准含市库的静态版本),
其地址为0x1000 Linker重新配置(relocate)
0x2000 <main>:
EC 00 00 12
F0 00 10 00
连结通常是一个比较费解的过程,有静态链结、和动态链结,下次会更详细的分析此过程
From Source to Binary: How A Compiler Works: GNU Toolchain
程序设计师的自我修养
<<: 从零开始学3D游戏设计:模型基础 Part.2 加入表面图案
>>: Golang 转生到web世界 - Cookie与session
字型大小 这次的重点是字型。先来说说字型大小。 现在市面上的电子纸设备大大小小各种尺寸都有:从海信...
k8s - 洗掉 kubernetes 环境重新来过 参考资料 参考资料:在 Ubuntu 上重新安...
在Grails 里建立 controller 是一件很愉快、简单的事情。基本上,你无须使用任何 an...
在此之前,我们已经介绍过BERT的核心概念迁移学习Transfer Learning以及它的输入输出...
Azure Machine Learning 是什麽,为什麽我们该使用它? Azure Machin...