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

平常我们很少关注编译和链结的过程,因为开发环境都集成开发的环境,比如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 格式的执行档

我们可以使用file hello 命令用於辨识文件类型
$ ./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
程序设计师的自我修养


<<:  html鼠标移上改变图样和底色

>>:  [Day1] MacBook及周边选购心得

Day23 CSS转场动画Transition

我们能够做完一个网页後,接下来我们可以让这个网页有更多的动态、趣味性,今天要介绍的动画效果trans...

Day 26:书单

前言 如果你去过天珑书局,应该会被比一般书店多了不知道几倍的电脑书吓到,幸好这个产业有很多被称为圣经...

30天学会C语言: Day 13-递回体验镇魂曲

递回 在函式里面可以呼叫函式本身 下面例子中,fun() 会先将参数 x 显示到视窗上,之後判断 x...

Day 42 (PHP)

1.header的用法 (1)画画时 指定画面Content-type网页输出,用image/jpe...

2021/11/28更新

ShiojiLogin.py: 加了contracts_timeout=10000的参数,在登入时...