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

编译和链接

一 , 翻译环境和运行环境

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

VS2022 --- IDE (集成开发环境) --> 编辑器 + 编译器(cl.exe) + 链接器(link.exe) + 调试器

1. 翻译环境  : 将这个环境中的源代码 --> 可执行的机器指令(二进制指令)

2. 执行环境  :它用于实际执行代码

二 , 翻译环境 

翻译环境是由 编译  和 链接 两大过程组成的 。  编译又可以分解成 : 预处理(预编译) , 编译 , 汇编。 

  •  多个 .c 文件(xx.c)  单独经过编译器 , 编译处理生成对应的目标文件 (xx.obj)
  • windows 环境下的目标文件的后缀是 .obj   , Linux 环境下目标文件的后缀是 .o 
  • 多个目标文件和链接库一起经过链接器处理生成最终的可执行程序
  • 链接库是指   运行时库 (它是值称程序运行的进本函数集合) 或者第三方库。

以gcc为例  , 拆解编译链接的过程

2.1 预处理(预编译)

 在预处理阶段 : 源文件  , 头文件会被处理成 .i 为后缀的文件

在gcc环境下 , 可以观察test .c 文件预处理后的 .i 文件 , 命令如下:

gcc   -E   test.c  -o  test.i

预处理阶段主要处理源文件中 #开始的预编译指令  。 比如 #include , #define ,规则如下

  •  将所有 #define 删除 , 并展开所有宏定义
  • 处理所有条件的编译指令 , 如#if , #ifdef ,#elif  , #else  , #endif
  • 处理#include 预编译指令 , 将包含的头文件内容插入到该预编译的位置 。这个过程是递归进行的 , 也就是说被包含的头文件 , 也可能包含其他文件
  • 删除所有注释
  • 添加行号和文件名标识 , 方便后续编译器生成调试信息
  • 保留所有#pragma 的编译器指令 , 编译器后续会使用

预处理后 :

1. .i 文件不再包含宏定义 --> 都展开了

2. 头文件被插入到 .i 文件中

3. 不知道宏定义 或者 头文件是否包含正确 --> 看 .i 文件

2.2 编译

编译过程就是将预处理后的文件进行一系列的 : 词法分析 , 语法分析 , 语义分析 及 优化 , 生成相应的汇编代码文件

在gcc 里的编译过程命令如下 :

gcc  -S   test.i   -o  test.s

 例如:用一下的代码进行编译

array [index]  =  (index  + 4  ) *  (2 + 6 )

2.2.1 法分析

将源代码程序扫入扫描器 ---->  词法分析 ( 把代码中的字符分割成一系列的记号 -- 关键字,标识符,特殊字符等)

2.2.2 法分析

语法分析器 --> 扫描后的记号进行语法分析 --> 语法树:一表达式为节点的树

2.2.3 语义分析

语义分析器 --> 对表达式的语法层面分析 。

编译器所能做的分析是语义的静态分析 。 静态分析通常包括声明和类型的匹配 , 类型的转换。这个阶段会报告错误的语法信息

2.3 汇编

汇编器是将或编代码转变成机器可执行的指令 , 每一个汇编语句几乎对应一条机器指令 。 即使更具汇编指令和机器指令的对照表一一的进行翻译 , 不做指令优化。

gcc  -c   test.s -o   test.o

2.4 链接

链接是一个复杂的过程 , 链接的时候需要把一堆文件链接在一起 ,才可生成可执行程序

链接过程包括 : 地址和空间分配(主要是对全局变量和函数来说) , 符号决议 和 重定位等的这些步骤。

链接解决的是一个项目中多文件 , 多模块之间互相调用的问题

比方说 C 项目中有两个 .c 文件 -- Add.c /  test .c

//test.c
#include <stdio.h>
extern int Add(int x, int y);
extern int g_val;
int main()
{int a = 2;int b = 3;int c = Add(a, b);printf("%d\n", c);printf("g_val = %d\n", g_val);return 0;
}
//Add.c
#define _CRT_SECURE_NO_WARNINGS 1
int g_val = 2022;int Add(int x, int y)
{return x + y;
}

 源文件单独经过编译器生成对应的目标文件:

test.c  经过编译器处理生成 test.o

Add.c 经过编译器处理生成Add.c

1. test.c 用了Add.c 文件中的Add 函数   与  g_val 变量

2. 在test .c 中每次用Add函数 与 g_val 变量的时候  必须得确切知道地址   ,每个文件单独编译 , 所以test .c 编译时  --> 不知道它两地址 , 所以把它两地址搁置

3.最后链接时候 , 链接器根据引用的符号 , 在其他模块中找 地址 , 然后把 地址搁置 处的地址重新修正  --> 这个过程也叫:重定位

这只是很简洁的讲解了C程序如何编译 与 链接 ,最总生成可执行程序的过程 ,很多内部细节无法展开详解 , 推荐看 《程序员的自我修养》 这本书来了解!

三 ,运行环境

1. 程序必须载入内存中 。 在有操作系统的环境中 : 一般这个由操作系统完成 。 在独立环境中,程序的载入必须由手工安排 , 也可能是通过可执行代码置入只读内存来完成 。

2. 程序的执行便开始 , 接着调用main 函数

3.开始执行程序代码 。 这时程序将使用一个运行时堆栈 (stack) ,存储函数的局部变量和返回地址。程序同时也可以使用静态(static) 内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值

4.程序终止 。 正常终止main函数;也可能是意外终止。


http://www.mrgr.cn/news/35419.html

相关文章:

  • 启动QT时,出现找不到python27.dll的问题报错
  • K8S创建云主机配置docker仓库
  • Swagger enum 最佳实践:深度剖析与应用指南
  • Go:文件输入输出以及json解析
  • PostgreSQL 页损坏如何修复
  • JVM 中的完整 GC 流程
  • linux网络编程7
  • 进度条QProgressBar
  • 数电学习基础(逻辑门电路+)
  • 失踪人口回归(明天开始继续更新学习内容)
  • 【Linux实践】实验三:LINUX系统的文件操作命令
  • 经典sql题(十一)查找共同好友或相互关注 二
  • 【大数据入门 | Hive】DDL数据定义语言(数据库DataBase)
  • Q必达任务脚本
  • asp.net core日志与异常处理小结
  • vue3开发中易遗漏的常见知识点
  • MySQL 中存储过程参数的设置与使用
  • 代码随想录Day 53|题目:110. 字符串接龙、105.有向图的完全可达性、106. 岛屿的周长
  • Linux下搭建iSCSI共享存储-Tgt
  • 【2024W36】肖恩技术周刊(第 14 期):什么是完美副业?
  • 二叉树进阶oj题【二叉树相关10道oj题的解析和代码实现】
  • 00DSP学习-F28379D学习准备(了解一个工程的构成)
  • 傅里叶变换及其应用笔记
  • JavaScript --json格式字符串和对象的转化
  • PHPStorm如何调整字体大小
  • 在Markdown中实现内部查询