当前位置: 首页 > article >正文

【C语言】C程序的编译+链接

写在前面

在学习了众多C语言语法和特性后,我们需要认识C语言是这么从一行行代码变成一个可执行程序的。关于本文图片标题:光标移动到对应的图片中会显示标题


文章目录

  • 写在前面
  • 一、程序的翻译环境和执行环境
  • 二、编译环境
  • 三、详解编译环境中的编译、连接
    • 3.1编译
      • 3.1.1**预编译(预处理)阶段**:
      • 3.1.2**编译阶段**:
      • 3.1.3、汇编阶段
    • 3.2连接
  • 四、编译环境总结
  • 五、运行环境


一、程序的翻译环境和执行环境

只要是在ANSI规定的C中的任何一种实现中,都一定存在翻译环境和执行环境。

  • 翻译环境:在这个环境中源代码被转换为可执行的机器指令
  • 执行环境:它用于实际执行代码。

本篇笔记重点详解编译环境


二、编译环境

在编译环境中又分为:编译连接

我们编写C语言时候使用的VS2022或者DevC++工具都是编译工具充当着编译环境的角色。这里不才使用VS2022与gcc进行详解。

我们在VS2022中每次进行运行代码时候,VS2022都会自动的帮我们进行两个环境的连续运行(如点击下图按钮)。在这里插入图片描述
所以每次我们只要点击后(不报错前提),我们就会得到运行界面,但是我们可以打开文件夹查看,当未运行前,文件中只有.c文件(图一)。
图一
在运行后,可以看到多出了Debug文件,文件中有一个.exe文件(图二、三),在这里插入图片描述
在这里插入图片描述
我们可以得到如下结论:

  1. 在翻译环境运行后,会把原来的.c文件生成一个.exe文件
  2. 在成功生成新的.exe文件后,VS2022会自动运行.exe文件,即自动进入执行环境中。
  3. 结合结论1、2可以得出,编译环境中的:编译连接是在.c文件到.exe文件过程中完成的操作。即可以得到下图:在这里插入图片描述

三、详解编译环境中的编译、连接

在编译中又细分为:预编译编译汇编。如下图
在这里插入图片描述

3.1编译

为了更直观感受编译的过程,不才举个简单的栗子🫡🫡

在这里插入图片描述
我们先编写一个test.htest.c文件
test.h文件

struct S{
	int a;
	char c;
};

test.c文件

#include "test.h"
#define M 100
int main(){
	struct S s;//这个结构体是在test.h头文件内包含的!
	int max = M;
	return 0;
}

3.1.1预编译(预处理)阶段

预编译阶段生成的文件后缀为.i的文件。

手动生成一个.i文件, 需要程序猿手动生成,因为在整个编译阶段不需要每个阶段都生成对应的文件。gcc中对应的是 -E 指令搭配 -o指令

  • -E 指令是运行预编译指令
  • -O指令是完成预编译后把结果生成一个新文件,如果不搭配 -o指令就无法看到预编译后程序的结果
  • 在上例中即为:gcc -E test.c -o test.i。运行后得到下图在这里插入图片描述
  1. 打开test.i文件,预处理中处理了C语言中的预处理指令把所有的预处理指令都替换成对应的代码(如下图)。(把标识符常量M 全部替换成100)
    在这里插入图片描述

  2. 预编译也会把全部引用的头文件全部内容拷贝放置在编译后生成的.i文件中如下图。在这里插入图片描述

  3. test.c文件中的结构体变量s后原有注释,解释结构体定义了在哪里,但是经过预编译后注释被删除掉了(如上图中struct S s;后没有了注释)。

3.1.2编译阶段

编译阶段会对预编译阶段生成的.i文件进行操作,生成的文件后缀为.s的文件。

  • 程序猿手动编译的话,只能对.i预编译文件进行编译,不能对.c.h等文件进行编译。

在gcc中,编译指令是-S

  • -S 指令是运行编译指令,在执行编译指令后,会自动生成编译文件(.s文件),当然也可以使用-o重命名。
  • 如在我们上面已经生成.i文件后,我们只需要gcc -S test.i就把test.i文件进行编译了。如下图在这里插入图片描述

我们打开.s文件查看,发现里面所有的代码都转换成汇编代码(如下图)

在这里插入图片描述
说明在编译阶段是把C语言代码转换为汇编代码的过程,在转换过程中会把C代码中的所有语法词法语义 进行分析,保证程序可以运行。并且会把所有的符号(包含全局变量名、函数名等等)进行汇总,为汇编阶段做前期准备。

3.1.3、汇编阶段

汇编阶段会对编译阶段生成的.s文件进行操作,生成的文件后缀为.o的文件,名为:目标文件
在gcc中,编译指令是-c。使用-c不一定是要拥有.s文件,就算是普通的.c文件使用-c指令都会进行完成。

-c 指令是运行汇编指令,在执行汇编指令后,会自动生成目标文件(.o文件),当然也可以使用-o重命名。(在vs2022中是生成.obj文件)

  • 如在我们上面已经生成.s文件后,我们只需要gcc -c test.s就把test.s文件进行汇编了。如下图在这里插入图片描述

如果我们正常使用文本查看器查看.o文件,会发现全部乱码(如下图)。在汇编处理中,其实是把汇编代码全部转换成二进制代码,说要我们在正常的文本查看器中是查看不了的。
在这里插入图片描述

在汇编阶段中,最主要的是.s文件汇总的符号生成符号表

  • 在Linux环境下gcc编译生成的.o文件和可以执行程序都是使用elf文件格式来组织的。我们可以使用readelf指令来查看gcc编译生成的.o文件或者可以执行程序,我们使用readelf 中的 -s指令来查看生成的符号表(readelf test.o -s)如下图。

在这里插入图片描述
上图中把test.i中的符号汇聚成表,符号表重点在于编译阶段的最后阶段连接做准备。
为了更直观感受符号表。我们再举个栗子:

int sum = 0;//全局变量
int add(int a, int b){
        return a + b;
}
int main(){
        int a = 10;
        int b = 20;
        sum = add(a, b);
        return 0;
}

我们编译后查看符号表:如下图
在这里插入图片描述
上图中可以清晰看到全局变量sumadd函数main函数符号表中形成序号请添加图片描述


3.2连接

接下来讲的编译都代指编译环境中的编译连接都代指编译环境中的连接

连接运行后就会生成可执行程序,所以在gcc中只需要生成可执行程序即完成了连接步骤

  • 连接的主要作用是把每个.c文件编译后(汇编后)形成的符号表进行连接
  • 1.c文件中定义了全局变量或者函数。同一工程中,在2.c文件中我们要使用1.c文件定义的全局变量或者函数可以使用关键字extern标识,

为了更直观感受连接的过程,不才举个简单的栗子🫡🫡

在这里插入图片描述
创建test.cadd.c文件
add.c文件:

int Add(int a, int b) {
	return a + b;
}

test.c文件:

extern Add(int a, int b);
int main() {
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	return 0;
}

我们先把test.cadd.c文件编译生成.o文件,并且查看他们的符号表

test.o文件:在这里插入图片描述
add.c文件:
在这里插入图片描述
在上面两图中,我们可以看到在未连接生成可执行程序前,我们test.o中的Add函数的Size值为0,反add.o文件的Add函数的Size值为20,可以看到即使我们在test.c文件中的Add函数使用了extern关键字也没起到作用。

我们连接生成的可执行程序(gcc -o test add.c test.c),在生成可执行程序test后使用readelf查看一下生成的连接表。如下图
在这里插入图片描述

其中我们可以看到,在生成的可执行程序的连接表中只看到只有一个Add函数,并且Size的值是20

说明连接是处理在同一工程中的每个.c文件产生的符号表,把每个符号表相对应的符号进行连接保留有效值

我们画个简图示例。
在这里插入图片描述
当然,成功连接的前提是名字相同,若是名字不同,在连接结束后发现Size0则报错。如下图
在这里插入图片描述

在VS2022编译器中,报错内容是无法解析的外部命令,说明在text.c文件中的add函数的值为0,在连接中也找不到所对应有值的add函数。


四、编译环境总结

请添加图片描述
程序在编译运行过程中的简化流程图如下图
在这里插入图片描述
我们也可以得到一个小结论图
在这里插入图片描述

五、运行环境

程序执行的过程

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

想要继续提升内功可以看:《程序员的自我修养》

以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有用的话,就请多多为我点赞收藏吧~~~💖💖
请添加图片描述

ps:表情包来自网络,侵删🌹


http://www.kler.cn/a/380404.html

相关文章:

  • [银河麒麟] Geogebra
  • SQLite本地数据库的简介和适用场景——集成SpringBoot的图文说明
  • 深度学习中的并行策略概述:2 Data Parallelism
  • Maven项目中不修改 pom.xml 状况下直接运行OpenRewrite的配方
  • LSTM实现天气模型训练与预测
  • 简单讲解关于微信小程序调整 miniprogram 后, tabbar 找不到图片的原因之一
  • 机场电子采购信息系统
  • APScheduler:强大的Python定时任务调度器
  • Flutter鸿蒙next中的按钮封装:自定义样式与交互
  • AI绘画大热门!用AI做副业兼职3个月赚了10w,想辞职了
  • stl_list
  • 利用蒙特卡洛方法求定积分
  • Redis 初学者指南
  • 论文阅读-用于图像识别的深度残差学习
  • 应用targetsdk版本低于30,不符合华为应用市场审核标准
  • 【学习】软件测试中V模型、W模型、螺旋模型三者介绍
  • Docker Compose部署XXL-JOB
  • STM32实现串口接收不定长数据
  • 【专题】基于服务的体系结构
  • JS实现漂亮的登录页面(氛围感页面)
  • 【linux 多进程并发】0203 网络资源的多进程处理,子进程完全继承网络套接字,避免“惊群”问题
  • TypeScript实用笔记(三):泛型<T>的使用 <T>的12种工具类型的使用
  • python代码主要实现了对供水网络的水质模拟,并对模拟结果进行一系列处理
  • ‌5G SSB(同步信号块)位于物理层‌
  • Python淘宝数据挖掘与词云图制作指南
  • Python 继承、多态、封装、抽象