C 语言->编译和链接实现原理

news2024/9/24 18:23:23

✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉

🍎个人主页:橘橙黄又青-CSDN博客

今天学习:浅学编译和链接内部实现原理

前提:本文是在gcc编译环境下学习,目前只是浅学习

1. 翻译环境和运⾏环境

在ANSI C的任何⼀种实现中,存在两个不同的环境。

第1种是翻译环境,在这个环境中源代码被转换为可执⾏的机器指令。
第2种是执⾏环境,它⽤于实际执⾏代码。
如图:

2. 翻译环境

那翻译环境是怎么将源代码转换为可执⾏的机器指令的呢?这⾥我们就得展开开讲解⼀下翻译环境所 做的事情。
其实翻译环境是由编译和链接两个⼤的过程组成的,⽽编译⼜可以分解成: 预处理(有些书也叫预编 译)、编译、汇编三个过程。

 

如图:

.c文件生成可执行文件过程:

多个.c⽂件单独经过编译出编译处理⽣产对应的⽬标⽂件。
注: 在Windows环境下的⽬标⽂件的后缀是 .obj ,Linux环境下⽬标⽂件的后缀是 .o
多个⽬标⽂件和链接库⼀起经过链接器处理⽣成最终的可执⾏程序。
链接库是指运⾏时库(它是⽀持程序运⾏的基本函数集合)或者第三⽅库。
如果再把编译器展开成3个过程,那就变成了下⾯的过程:

2.1 预处理(预编译) 

在预处理阶段,源⽂件和头⽂件会被处理成为.i为后缀的⽂件。
gcc 环境下想观察⼀下,对 test.c ⽂件预处理后的.i⽂件,命令如下:
 gcc -E test.c -o test.i
预处理阶段主要处理那些源⽂件中#开始的预编译指令。⽐如:#include,#define,处理的规则如下:
将所有的 #define 删除,并展开所有的宏定义。
处理所有的条件编译指令,如: #if #ifdef #elif #else #endif
处理#include 预编译指令,将包含的头⽂件的内容插⼊到该预编译指令的位置。这个过程是递归进 ⾏的, 也就是说被包含的头⽂件也可能包含其他⽂件
删除所有的注释
添加⾏号和⽂件名标识,⽅便后续编译器⽣成调试信息等。
或保留所有的#pragma的编译器指令,编译器后续会使⽤。
经过预处理后的.i⽂件中不再包含宏定义,因为宏已经被展开。并且包含的头⽂件都被插⼊到.i⽂件
中。所以当我们⽆法知道宏定义或者头⽂件是否包含正确的时候,可以查看预处理后的.i⽂件来确认。

 

来我们看看.i文件是怎么生成的呢?

这里我们发现有700多行代码,没错这里面的都是#include<stdio.h>里面的全部函数 等定义

 输入指令生成test.i文件,文件名可以随便改.i文件为文件后缀就行,那我们举上面的两个例子

案例1:

外部函数包含例子

代码:

test.c文件

#include<stdio.h>
#include "test.h"

int g_val = 2024;

int main()
{
	printf("hehe\n");
	printf("%d\n", g_val);
	return 0;
}

test.h 文件

 

int Add(int x, int y) {
	return x + y;
}

生成de.i文件里面的内容:

案例2:

#denfinen的例子

预处理完之后MAX就变成1000,这都是在test.i文件里面完成的,所以说使用#define到后面是不利于调试的,因为调试是在.exe文件里面完成的,.i文件只是一个过渡阶段。使用后就删除了 。

2.2 编译

编译过程就是将预处理后的⽂件进⾏⼀系列的: 词法分析、语法分析、语义分析及优化,⽣成相应的 汇编代码⽂件。
编译过程的命令如下:
gcc -S test.i -o test.s

输入指令后。生成.s文件,.s文件是汇编指令。

2.2.1 词法分析:

将源代码程序被输⼊扫描器,扫描器的任务就是简单的进⾏词法分析,把代码中的字符分割成⼀系列 的记号(关键字、标识符、字⾯量、特殊字符等)。
来看一个案例:
array[index] = (index+4)*(2+6);

上⾯程序进⾏词法分析后得到了16个记号:
16个记号
记号类型
array标识符
[左方括号
index标识符
]右方括号
=赋值
(左圆括号
index标识符
+加号
4数字
)左圆括号
*乘号
(右圆括号
2数字
+加号
6数字
右圆括号

2.2.2 语法分析

接下来语法分析器,将对扫描产⽣的记号进⾏语法分析,从⽽产⽣语法树。这些语法树是以表达式为 节点的树。

2.2.3 语义分析

由语义分析器来完成语义分析,即对表达式的语法层⾯分析。编译器所能做的分析是语义的静态分 析。静态语义分析通常包括声明和类型的匹配,类型的转换等。这个阶段会报告错误的语法信息。

2.3 汇编  

汇编器是将汇编代码转转变成机器可执⾏的指令 ,每⼀个汇编语句⼏乎都对应⼀条机器指令。就是根 据汇编指令和机器指令的对照表⼀⼀的进⾏翻译 ,也不做指令优化。
汇编的命令如下:
gcc -c test.s -o test.o

2.4 链接

链接是⼀个复杂的过程,链接的时候需要把⼀堆⽂件链接在⼀起才⽣成可执⾏程序。
链接过程主要包括: 地址和空间分配,符号决议和重定位 等这些步骤。
链接解决的是⼀个项⽬中多⽂件、多模块之间互相调⽤的问题。
⽐如:
在⼀个C的项⽬中有2个.c⽂件( test.c 和 A dd.c ),代码如下:
test.c代码:
#include <stdio.h>
//test.c
//声明外部函数
extern int Add(int x, int y);
//声明外部的全局变量
int main()
{
     int a = 10;
     int b = 20;
     int sum = Add(a, b);
     printf("%d\n", sum);
     return 0;
}

Add.c代码:

int g_val = 2022;
int Add(int x, int y)
{
     return x+y;
}
我们已经知道,每个源⽂件都是单独经过编译器处理⽣成对应的⽬标⽂件。
test.c 经过编译器处理⽣成 test.o
add.c 经过编译器处理⽣成 add.o
我们在 test.c 的⽂件中使⽤了 add.c ⽂件中的 Add 函数。
我们在 test.c ⽂件中每⼀次使⽤ Add 函数的时候必须确切的知道 Add 的地 址,但是由于每个⽂件是单独编译的,在编译器编译 test.c 的时候并不知道 Add 函数的地址,所以暂时把调⽤ Add 的指令的⽬标地址搁置。等待最后链接的时候由 链接器根据引⽤的符号 Add 在其他模块中查找 Add 函数的地址,然后将 test.c 中所有引⽤到Add 的指令重新修正,让他们的⽬标地址为真正的 Add 函数的地址,对于全局变量 也是类 似的⽅法来修正地址。这个地址修正的过程也被叫做: 重定位

如图:

 

第2点的全部知识点图解:

 

3. 运⾏环境 

1. 程序必须载⼊内存中。在有操作系统的环境中:⼀般这个由操作系统完成。在独⽴的环境中,程序 的载⼊必须由⼿⼯安排,也可能是通过可执⾏代码置⼊只读内存来完成。
2. 程序的执⾏便开始。接着便调⽤main函数。
3. 开始执⾏程序代码。这个时候程序将使⽤⼀个运⾏时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使⽤静态(static)内存,存储于静态内存中的变量在程序的整个执⾏过程 ⼀直保留他们的值。
4. 终⽌程序。正常终⽌main函数;也有可能是意外终⽌。
好啦,今天就到这里了,都看到这里了点一个赞吧,感谢观看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1397215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 Picocli 开发 Java 命令行,5 分钟上手

大家好&#xff0c;我是鱼皮&#xff0c;对不会前端的同学来说&#xff0c;开发 命令行工具 是一种不错的展示系统功能的方式。在 Java 中开发命令行工具也很简单&#xff0c;使用框架&#xff0c;几分钟就能学会啦~ Picocli 入门 Picocli 是 Java 中个人认为功能最完善、最简单…

网工内推 | 网络工程师,CCNP认证优先,带薪年假,补贴多

01 极三科技 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、门店网络运维 2、门店网络项目支持 3、配合店铺Support team处理网络相关问题 任职要求&#xff1a; 1、大专及以上相关学历 2、3年以上相关工作经验 3、精通Cisco 路由器&#xff0c;交换机配置 4…

2.4G收发芯片遥控玩具方案开发,解决方案

2.4G遥控玩具是指通过2.4GHz无线信号进行遥控的玩具。这种玩具通常包括遥控器和遥控车、挖机、飞机、船等各种类型的模型。使用2.4G频段的遥控器可以提供更稳定和可靠的信号传输&#xff0c;具有较长的控制距离和较低的干扰。相比之前使用的其他频段的遥控器&#xff0c;2.4G遥…

前台vue配置

前台 vue环境 1.傻瓜式安装node: 官网下载&#xff1a;https://nodejs.org/zh-cn/2.安装cnpm: >: npm install -g cnpm --registryhttps://registry.npm.taobao.org3.安装vue最新脚手架: >: cnpm install -g vue/cli注&#xff1a;如果2、3步报错&#xff0c;清除缓…

第11章 GUI Page500~504 步骤三十二:打开画板文件02

各个图元类新增GetTypeName_Static()&#xff0c;并将原来的GetTypeName()改为调用静态方法实现&#xff1a; 直线&#xff1a; 圆&#xff1a; 十字&#xff1a; 矩形&#xff1a; 文字&#xff1a; tool_4_save_load.hpp添加两行 tool_4_save_load.cpp增加&#xff1a; 增加…

2024--Django平台开发-订单项目管理(十四)

day14 订单管理系统 1.关于登录 1.1 UI美化 页面美化&#xff0c;用BootStrap 自定义BooStrapForm类实现。 class BootStrapForm:exclude_filed_list []def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)# {title:对象,"percent":对象}fo…

TestNG注释

目录 TestNG注释列表 BeforeXXX和AfterXXX注释放在超类上时如何工作&#xff1f; 使用BeforeXXX和AfterXXX TestNG注释 TestNG是一个测试框架&#xff0c;旨在简化广泛的测试需求&#xff0c;从单元测试&#xff08;隔离测试一个类&#xff09;到集成测试&#xff08;测试由…

SpringBoot——纯注解配置的Spring

1.环境搭建 1.1.创建工程 拷贝ssm工程&#xff1a; 1.2.待改造的问题 我们发现&#xff0c;之所以我们现在离不开xml配置文件&#xff0c;是因为我们有一处很关键的配置&#xff0c;如果他要也能用注解配置&#xff0c;那么我们就可以脱离xml文件了&#xff1a; 1.2.1.jdbc…

vuex前端开发,getters是什么?怎么调用?简单的案例操作

vuex前端开发,getters是什么&#xff1f;怎么调用&#xff1f;简单的案例操作&#xff01; 下面通过一些简单的案例&#xff0c;来了解一下&#xff0c;vuex当中的getters到底是什么意思&#xff0c;有哪些实际的操作案例。 Vuex的getters主要用于对store中的state进行计算或过…

使用jackson对java类中包含泛型属性的bean进行序列化和反序列化

最近在做项目时&#xff0c;需要对java的bean对象进行序列化转换为string字符串后&#xff0c;存入redis&#xff0c;然后再从redis中读取该string对象&#xff0c;反序列化为bean对象。正常的简单类型的对象使用jackson可以非常方便进行互为转换操作&#xff0c;但我们操作的对…

icu4c库中icudtl.dat文件剪裁

背景 在工程中需要把ansi编码转utf-8,引入icu4c库&#xff0c;默认编译出来的.dat文件有30多M&#xff0c;由于仅仅需在MacOS系统下要把Windows中的ansi编码转成utf-8&#xff0c;需要进行裁剪。 编译icu4c工程 源码下载&#xff1a;https://github.com/unicode-org/icu&…

HTML 属性

HTML 属性学习笔记 HTML 属性真的好神奇呢&#xff01;它们像是元素的额外说明&#xff0c;总是伴随着特定的格式出现在元素的开始标签里。接下来&#xff0c;我就记录下一些常见的 HTML 属性吧&#xff01; HTML 属性的基本概念 每个 HTML 元素都可以有自己的属性&#xff…

1739. 迷宫的所有路径-深度优先搜索-DFS

代码&#xff1a; #include<bits/stdc.h> using namespace std; int n; int fx[4]{0,1,0,-1}; int fy[4]{1,0,-1,0}; bool vis[100][100]; int q[35][3]; int c; void print(int k){c;cout<<c<<":";for(int i1;i<k;i){cout<<q[i][1]<…

测试 ASP.NET Core 中间件

正常情况下&#xff0c;中间件会在主程序入口统一进行实例化&#xff0c;这样如果想单独测试某一个中间件就很不方便&#xff0c;为了能测试单个中间件&#xff0c;可以使用 TestServer 单独测试。 这样便可以&#xff1a; 实例化只包含需要测试的组件的应用管道。发送自定义请…

多线程排序(java版)

&#x1f4d1;前言 本文主要是【排序】——多线程排序的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&#x…

湖(岛屿)

from book&#xff1a;挑战程序设计竞赛

【Linux系统编程】程序地址空间

进程地址空间 进程地址空间是指每个进程在计算机内存中所占用的地址空间。地址空间是指能被访问的内存地址范围&#xff0c;它由若干个连续的内存块组成。每个进程都有自己的地址空间&#xff0c;这意味着每个进程都有自己的内存地址范围&#xff0c;不会与其他进程冲突。进程地…

回溯法:0-1背包问题

问题描述 给定种物品和一背包。 物品的重量是&#xff0c; 其价值为&#xff0c;背包的容量为 c。 问应该如何选择装入背包中的物品&#xff0c;使得装入背包中物品的总价值最大&#xff1f;注意物品不重复! 实例&#xff1a;物品价值V{12, 11, 9, 8}, 物品重量W{8, 6, 4, 3},…

操作系统-中断和异常(中断作用 类型 内外中断 中断机制原理)

文章目录 总览中断的作用中断类型内中断的例子外中断中断的分类中断机制的基本原理小结 总览 中断的作用 刚启动CPU上运行的是内核程序 中断类型 内中断的例子 当前执行指令非法是指参数有问题等其他原因 陷入指令是主动转换状态&#xff0c;但它并不是特权指令 外中断 时钟…