Day2.程序运行的基本概念(预处理、编译、组译、链结)

平常我们很少关注编译和链结的过程,因为开发环境都集成开发的环境,比如Visual Studio、Eclipse,这样的IDE一般都将编译和链结的过程一步完成,因此我们必须深入了解这些被隐藏的过程。


https://ithelp.ithome.com.tw/upload/images/20210915/201418904waQVDB5n8.png


#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

电子书阅读器上的浏览器 [Day08] 调整网页字型

字型大小 这次的重点是字型。先来说说字型大小。 现在市面上的电子纸设备大大小小各种尺寸都有:从海信...

k8s - 洗掉 kubernetes 环境重新来过

k8s - 洗掉 kubernetes 环境重新来过 参考资料 参考资料:在 Ubuntu 上重新安...

Java 开发 WEB 的好平台 -- Grails -- (4) 建立第一个 Controller

在Grails 里建立 controller 是一件很愉快、简单的事情。基本上,你无须使用任何 an...

【Day 4】输出之後,BERT转换的Embedding怎麽用?

在此之前,我们已经介绍过BERT的核心概念迁移学习Transfer Learning以及它的输入输出...

[DAY01] Azure Machine Learning 是什麽?

Azure Machine Learning 是什麽,为什麽我们该使用它? Azure Machin...