C语言 程序的编译、链接、执行
一、 程序的环境
在ANSI C的任何一种实现中,存在两种不同环境。第1种是翻译环境,在这个环境中源代码文件被转换为可执行的机器指令(二进制代码)。第2种是执行环境(运行环境),它用于实际执行代码。标准规定,这两种环境可以不在同一台计算机上,即可以在计算机A中编译程序,在计算机B中执行程序。
对于这两种环境按步骤细节有两种划分方式
1.1 方式一
方式一将翻译环境按步骤细分为编译、链接两步,运行环境不变。这就是我们常说的程序的编译、链接、执行三步骤
1.2 方式二
方式二将翻译环境按步骤细分为编译、链接两步,编译又细分为预编译、编译、汇编,执行环境不变。即:预编译、编译、汇编、链接、执行五步骤
说明:方式一与方式二没有本质区别,方式二相对于方式一步骤划分更加细节,下文中以方式二详细介绍每个步骤所做的事情
1.3 函数库
在链接阶段,会将目标文件和函数库一起链接,这里的函数库指的是在源文件中用到C语言提供的各种库函数,这些函数存放在函数库中,在链接时候需要与目标文件一起链接
1.4 文件后缀名约定
C语言标准没有对文件后缀名进行规定,但在大多环境下有一套约定的文件后缀名
.h为后缀名的文件为C语言头文件
.c为后缀名的文件为C语言源代码文件
.i为后缀名的文件,是预编译后的C语言文件
.s为后缀名的文件,是编译过后的汇编代码文件
.o为后缀名的文件,是汇编后的目标文件(机器指令文件也称为二进制指令文件)。windows系统中目标文件以.obj为后缀名
.out为后缀名的文件,是多个目标文件与函数库链接之后的可执行程序。windows系统中以.exe为后缀名
二、翻译环境
windows系统下的大多数IDE对于预编译、编译、汇编、链接实现细节进行了隐藏,为了观察细节,环境使用Linux系统下GCC编译器
2.1 预编译(预处理)
预编译的详细过程可以查看这篇博客:https://blog.csdn.net/kjl167/article/details/124218564
预编译:将源代码文件(.c)进行预编译处理生成预编译文件(.i)
功能:
- 执行预处理指令,如: #include 、#define
- 删除所有注释
gcc -E 源文件 -o 预处理文件名
-E :预编译阶段结束后停止,将预编译后的数据输出到标准输出
-o :将输出到标准输出上的内容输出到指定文件
测试1:#include,将所包含的头文件引入
由于<stdio.h>
头文件内容太多,所以自定义<add.h>
头文件测试
测试2:#define ,标识符常量替换
测试3:会删除源文件的注释
2.2 编译
编译:将预编译后的文件编译为汇编代码
功能:
- 语法分析
- 词法分析
- 语义分析
- 符号汇总(汇总全局符号,如全局变量、函数名)
gcc -S 预处理文件(或源文件) -o 编译文件名
-S :编译阶段结束后停止,将编译后的数据输出到标准输出
-o :将输出到标准输出上的内容输出到指定文件
2.3 汇编
汇编:把汇编代码转换为机器指令(二进制指令)的目标文件
该机器指令文件为elf格式,可用readelf
命令查看内容
功能:
- 形成符号表
gcc -c 编译后文件(或源文件)
-c :进行汇编
2.4 链接
可执行程序(机器指令)为elf格式,可用readelf
命令查看内容
链接:将多个目标文件和函数库链接为可执行程序
功能:
- 合并段表
- 符号表的合并和重定位
gcc 多个目标文件(或源文件) -o 链接后可执行程序名
三、 执行环境
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
- 调用main函数运行
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序,可以是正常终止main函数;也有可能是运行错误终止