C语言-程序环境 #预处理 #编译 #汇编 #链接 #执行环境

文章目录

前言

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

二、翻译环境

(一)、整体把握

(一)、编译

1、预处理(预编译)

2、编译

a、词法分析

b、语法分析

c、语义分析

d、符号汇总

3、汇编

(二)、链接

三、运行环境

总结​​​​​​​


前言

路漫漫其修远兮,吾将上下而求索;

PS:本文参考了《程序员的自我修养》,致敬大佬们!


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

在ANSI C(标准C)的任何一种实现中,存在两个不同的环境:环境翻译、执行环境

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

二、翻译环境

(一)、整体把握

在一个工程中会有很多的 .c 文件;

为什么在一个工程中会有多个 .c 文件?

  • 在一个开发组中,每个程序都要自己写自己的代码,倘若一堆程序员均将代码写入一个 .c 文件中,可以想象这是非常难以协同的;所以在一个工程之中,大家均是分模块去写的,故而在一个工程中必然会有多个 .c 文件;

编译器是如何处理多个 .c 文件生成可执行程序的呢?

  • 每一个 .c 文件被称为源文件每个源文件均会单独经过编译器处理生成自己相应的目标文件;然后多个目标文件 加上 链接库 经过链接器的处理最终会生成可执行程序;如下图过程所示:

  • 组成一个程序的每个源文件均会单独通过编译过程分别形成自己所对应目标代码(object code).
  • 每个目标文件由链接器(linker) 将和链接库 捆绑在一起,形成一个单一而完整的可执行程序
  • 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索到程序员个人的程序库,将其需要的函数也链接到程序中;

注:上图中所有的源文件均是由同一个编译器处理成目标文件的;(每个源文件均会单独通过编译器处理生成目标文件);

头文件是如何被处理的呢?

  • 头文件会先合并到源文件中(此合并的意思为拷贝,即将头文件中的内容拷贝放入源文件之中),然后再进行上图所示 的流程;

什么是链接库?

  • 我们在写代码的时候,倘若使用了库函数就得包含对应的头文件,例如 使用了库函数 printf 就得包含其对应的头文件  <stdio.h> ;而所包含的头文件,其涉及到依赖的库也是一个由库文件的提供,只有当你将其编译进你的程序之中,才可以使用此库函数。
  • 故,链接库同理;库函数所依赖的东西会在链接库中提供,然后再将链接库与目标文件链接到最终的可执行程序之中于是乎其整体(使用了库函数的程序)便可以使用此库函数了;

在整个程序生成可执行程序中会遇到两个工具 : 编译器、链接器

编译器、链接器的工具是什么呢?

  • 在VS底下编译器用的是 cl.exe ,链接器用的是 link.exe

2、细节深入

在整体把握中能明显感受到,多个源文件会由同一个编译器单独处理生成目标文件,而链接器会将这些生成的目标文件与链接库一起生成可执行程序;

在编译的过程中,还包含着三个过程:预处理(预编译)、编译、汇编

(一)、编译

1、预处理(预编译)

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

汇编过程的指令如下:

 gcc  test.c  -E  -o  test.i

       gcc  -test.c      是让gcc编译器编译源文件test.c

       -E                    代表着让gcc编译器将预处理阶段处理完便停止

       -o                    (output)代表着指定一个输出 

       -o test.i       就是将gcc 编译器处理完预处理阶段的数据存放到文件test.i 之中

预处理阶段主要处理的是那些源文件中# 开始的预编译指令;比如:#include #define ,

  • 处理#include 预编译指令,将包含的头文件的内容插入到该预编译指令的位置;这个过程是递归进行的,也就是说被包含的头文件也有可能会包含其他文件;
  • 将#define 定义的标识符常量给替换掉,并且删除所定义的符号
  • 删除注释(PS: 注释是给程序员看的,这些注释对于程序本身来说没什么用,于是乎在预处理阶段便删除了)
  • 处理所有的条件编译指令,例如: #if 、#ifdef、#elif、#else、#endif ;
  • 添加行号和文件名标识,方便后续编译器生成调试信息
  • 保留所有的 #pragma 的编译器指令,编译器后续会使用;

源文件经过预处理后会生成以 .i 为后缀名的文件,在 .i 文件中不再包含宏定义,因为宏已经被展开。并且包含的头文件都被插入到 .i 文件中。所以当我们无法知道宏定义或者头文件的包含是否正确的时候便可以查看预处理后的 .i 文件来确认

注:这些操作均是将.c 源文件处理成新的文件,然后在新文件上操作的; 

2、编译

编译的过程就是将预处理后的文件进行一系列的:词法分析、语法分析、语义分析、符号汇总,之后便会生成对应的汇编代码文件;

编译过程的命令如下:

gcc  test.i  -S  -o  test.s

  • gcc test.i  -S 让gcc编译器处理文件test.i 到编译阶段结束便不再处理
  • -o  test.s  将gcc编译器编译阶段产生的数据存放到文件test.s 之中

例子程序:array [index] = (index + 4 ) * (2 + 6 );

a、词法分析

首先源代码被输入到扫描器(Scanner),扫描器的任务就是进行简单的词法分析,把代码中的字符分割成一系列的记号(关键字、标识符、字面量、特殊字符等)。在识别记号的同时,扫描器也完成了其他的工作,比如将标识符放在符号表,将数字、字符串常量存放在文字表等,以便后面的操作使用;

将上述的例子程序进行词法分析,进行扫描后便会得到16个记号,如下图:

记号类型
array标识符
[左方括号
index标识符
]右方括号
=赋值
(左圆括号
index标识符
+加号
4数字
)右圆括号
*乘号
(左圆括号
2数字
+加号
6数字
)右圆括号
b、语法分析

加下来的语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,从而产生语法树。整个分析过程采用了上下文无关语法(Context-free Grammer) 的分析手段。上下文无关语法即由语法分析器生成的语法树就是以表达式(Expression)为节点的树;二我们知道,C语言的一个语句是一个表达式,而复杂的语句是很多表达式的组合。

在上面的例子当中的语句就是一个由赋值表达式、加法表达式、乘法表达式、数组表达式、括号表达式而组成的复杂表达语句;经过语法分析之后便会得到如下图所示的语法树:

在上图中可以得知,整个语句被当作了一个赋值表达式;赋值表达式的左边有个下标表达式(数组表达式),其右边是一个乘法表达式;而在下标表达式(数组表达式)中又由两个符号表达式组成……

符号和数字是最小的表达式,它们不是由其他的表达式来组成的,所以它们又通常被作为整个语法树的叶节点;

在语法分析的同时,很多运算符号的优先级和含义也被确定下来;

另外有些符号具有多重含义,例如* ,在C语言中可以标识乘法表达式,也可以表示对指针取类容的表达式,所以在语法分析阶段必须对这些内容进行区分;倘若出现表达式不合法,比如各种括号不匹配、表达式中缺少操作符等,编译器均会报出语法分析阶段的错误

c、语义分析

由语义分析器(Semantic Analyzer)完成,语法分析仅仅是完成对此表达式语法层面的分析,并不会去了解这个语句是否真的具有意义。编译器能做的就是语义的静态分析。静态语义分析通常包含声明和类型匹配,类型的转换。

经过语义分析阶段以后,整个语法树的表达式便都被标识了类型倘若有些类型需要做隐式转换,语义分析程序便会在语法树中插入相应的转换节点

如下图所示(还是同一例子):

d、符号汇总

符号汇总会将代码中全局、静态的符号全部汇总出来;

注:局部变量是无需汇总的,因为局部变量只有当程序运行起来进入其相对应的作用域时才会创建,故而局部变量无需汇总;

经过词法分析、语法分析、语义分析、符号汇总,在文件test.s 中存放的为源文件中代码所对应的汇编代码

3、汇编

汇编器汇编代码转变成机器可执行的指令(二进制指令),每一个汇编语句几乎都对应着一条机器指令;转换的流程就是根据汇编指令与机器指令的对照表一一地进行翻译,无需做指令优化;

汇编的指令如下:

gcc  test.s  -c  -o  test.o

汇编过程中的具体细节:

首先是会形成符号表,然后再将汇编指令转换成二进制指令;

注:什么叫作形成符号表?所谓形成符号表就是将在上阶段(编译)中的符号汇总再关联地址形成个表;

符号表是怎么形成的呢?

  • 会为对应的符号找补一个地址形成一个表格;(下图中的地址为举例而随便给的)

符号表有什么用?

  • 在链接阶段会使用到符号表;

(二)、链接

  • 合成段表
  • 符号表的合并和符号表的重定位

源文件经过预处理、编译、汇编 会形成 以 .o 为后缀的文件,即目标文件,此文件是有具体格式的,在Linux 环境下有一种 elf 格式(目标文件是中的内容是二进制形式的,在Linux 下按照elf 这种格式来组织其文件中的内容);

elf 格式会将目标文件分成一段一段的,每一段可以放置不同的数据(每个段均会放置某一种属性的数据);

假设此处有两个目标文件,即 test.o 与 add.o 

那么在此链接过程当中,会将test.o 与 add.o(多个目标文件)+ 链接库 进行链接,输出一个可执行程序;(在 Linux 环境下的可执行程序也是 elf 格式);

将elf 格式的文件合并,即在一个采用 elf 格式的文件中,不同的段中会放格子相同类型的数据,再将不同 elf 格式的文件其相同的段合并起来而形成一个文件;这个过程便是合并段表,如下图所示:

简单来说就是,在Linux 下,以 .o 为后缀的文件存在 elf 格式,即会将数据“分类”存在不同的段中(相同类型的数据会放在同一个段中),在链接阶段首先会将test.o 与 add.o 合并成一个文件,由于二者均为 elf 格式的文件,那么它们合并为一个文件的方式将存放着相同类型的段合并成一个段;

什么是符号表的合并与重定义?

(还是以上述所提例子为例)

符号表的合并

在符号表中Add 有两项,用哪一项呢?

经过仔细地发现 test.o 中的符号表中的 Add 的地址是无意义的,在 test.c 中只是进行了简单的声明,并无具体的实现,声明的作用也仅仅是让计算机知道有这么个函数,但是函数的具体实现及其真实的地址是不知道的,在 add.o 中Add 的地址是有意义的地址;故而在合并的过程中,会选择用add.o 中的 Add; 

如上图所示,在合并符号表的同时会对其地址进行筛选,即选择了有效的地址而形成了最终的符号表,这个筛选的动作便是符号表的重定位

如何确定该符号表是有效的?

  • 在 test.c 中仅存在Add 函数的声明而未有定义,故而在 test.o 文件所对应的符号表中其存在的Add 的地址是个无效地址;而至于此地址怎么是无效的,取决于编译器的实现(因为在汇编形成符号表的时候,编译器便认定了在test.s 中的Add 所找补的地址为无效地址,那么在合并符号表的时候便会认为该地址为无效地址);

符号表的重定位有什么用?

  • 符号表的作用是非常之大的;在链接期间,能否使用Add 函数完全取决于在符号表中Add 函数有没有对应其有效的地址,因为在合并之后,只有当Add 、main 均有自己的有效地址,故而我们可执行程序便会使用符号表去查找此函数的地址,便说明我们可以找到该函数(利用其有效的地址);但倘若 add.c 中没有Add 函数,但是在test.c 中仍然存在对于Add 函数的声明,那么经过预处理、编译、汇编所得到的符号表中Add 函数所对应的地址为无效地址,故而在合并符号表时 Add 函数仍任是对应着无效地址,当生成可执行程序的时候,便无法通过此无效地址而找到Add 函数,所以会报错,并且报的是链接性的错误

通过上面的讲述,似乎声明仅仅只是告诉计算机有这么个函数,并无其他实质性的作用;在合并符号表的时候,也会对符号表进行重定义;

可能你便会问了,倘若不对此函数进行声明,只要此函数的具体实现在这个工程中,可以使用该函数吗?

 

  • 当然可以,不对此函数进行声明,可以强行使用;会报警告,但是不影响执行;因为实质上可执行程序找Add 函数是在符号表中查找的

注:C++ 中的重载

在C++中,其编译器会根据其参数的个数、参数的类型等来确定重新产生的名字是什么……(有自己的一套规则),所以当你设计的函数的类型、参数个数不同时,重命名产生的名字也会不一样,那么在符号表中出现的名字也会有所差异;

编译期间会将符号进行汇总,而在汇编期间形成符号表,在链接阶段会合并符号表并进行重定位;这些操作就是为了在链接期间能够跨文件找到函数--> 在符号表中去查找(符号表的存在得以实现跨文件的使用);

三、运行环境

第一步将程序放入内存之后,接下来第二步程序便开始执行

而程序是如何开始执行的呢?

  • 首先是要找到main 函数的位置

main 函数被翻译成了二进制指令在内存中也存在属于main 函数自己的内存空间,故而首先会跳到main 函数存其对应二进制指令的地方开始执行(找到程序的入口);

程序的执行流程:

  • 程序必须载入内存中,在有操作系统的环境中,一般载入的这个操作是由操作系统完成的;而若在独立的环境中(即没有操作系统的环境中),程序的必须用手工载入,也可能是通过可执行代码置入只读内存中来完成;
  • 程序的执行便开始,接着便会调用 main 函数
  • 开始执行程序代码,这个时候将使用一个运行时堆栈(stack)每一个函数在调用的时候均会创建函数栈帧,此函数栈帧便是运行时堆栈),存储函数的局部变量和返回地址。程序同时也可以使用静态内存(静态区)存储于静态内存中的变量在程序的整个执行过程中一直会保留着他们的值;
  • 终止程序,正常终止main 函数;也有可能会是意外终止;(正常运行着的函数其程序结束了也便停止了;或者说程序发生了意外,程序崩溃了、内存泄露了、断电了……均属于意外终止)

注:只有将程序载入内存中,才能更好地去运行它;独立环境中的载入,例如:单片机中所讲的"烧板子,烫代码",便是将代码烫到板子上去(没有操作系统,需要借助于外部设备将程序放入内存之中);


总结

1、源文件生成可执行程序如若处理两步便是编译和链接。倘若是四步则是预处理、编译、汇编、链接。

2、预处理:主要处理的是那些源文件中# 开始的预编译指令;比如:#include #define ,

3、编译:词法分析、语法分析、语义分析、符号汇总

4、汇编:将汇编代码转换成机器指令,即二进制指令;

5、链接:

  • 合成段表
  • 符号表的合并和符号表的重定位

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

9月7日微语报,星期六,农历八月初五

&#xff19;月&#xff17;日微语报&#xff0c;星期六&#xff0c;农历八月初五&#xff0c;周末愉快&#xff01; 一份微语报&#xff0c;众览天下事&#xff01; 1、21个部门&#xff1a;符合条件的流动儿童家庭或可配公租房。 2、多所高校2025年招生简章显示&#xff0…

API安全 | 发现API的5个小tips

在安全测试目标时&#xff0c;最有趣的测试部分是它的 API。API 是动态的&#xff0c;它们比应用程序的其他部分更新得更频繁&#xff0c;并且负责许多后端繁重的工作。在现代应用程序中&#xff0c;我们通常会看到 REST API&#xff0c;但也会看到其他形式&#xff0c;例如 Gr…

Jenkins构建CI/CD

CI/CD 软件开发的连续方法基于自动执行脚本&#xff0c;以最大限度地减少在开发应用程序时引入错误的可能性。从新代码的开发到部署&#xff0c;它们需要较少的人为干预甚至根本不需要干预。 它涉及在每次小迭代中不断构建&#xff0c;测试和部署代码更改&#xff0c;从而减少…

对极约束及其性质 —— 公式详细推导

Title: 对极约束及其性质 —— 公式详细推导 文章目录 前言1. 对极约束 (Epipolar Constraint)2. 坐标转换 (Coordinate Transformations)3. 像素坐标 (Pixel Coordinates)4. 像素坐标转换 (Transformations of Pixel Coordinates)5. 本质矩阵 (Essential Matrix)6. 线坐标 (Co…

单调栈的实现

这是C算法基础-数据结构专栏的第二十四篇文章&#xff0c;专栏详情请见此处。 引入 单调栈就是满足单调性的栈结构&#xff0c;它最经典的应用就是给定一个序列&#xff0c;找出每个数左边离它最近的比它大/小的数。 下面我们就来讲单调栈的实现。 定义 单调栈就是满足单调性…

pycharm破解教程

下载pycharm https://www.jetbrains.com/pycharm/download/other.html 破解网站 https://hardbin.com/ipfs/bafybeih65no5dklpqfe346wyeiak6wzemv5d7z2ya7nssdgwdz4xrmdu6i/ 点击下载破解程序 安装pycharm 自己选择安装路径 安装完成后运行破解程序 等到Done图标出现 选择Ac…

精准设计与高效开发:用六西格玛设计DFSS实现新能源汽车开发突破

快速变化的市场需求和激烈的竞争迫使制造企业不得不持续创新和优化产品开发流程。如何在保证产品质量的前提下&#xff0c;加快产品开发周期&#xff0c;成为许多企业亟待解决的问题。六西格玛中的DFSS&#xff08;Design for Six Sigma&#xff09;模型提供了一种系统的方法&a…

【银河麒麟高级服务器操作系统实例】虚拟化平台系统服务中断现象分析及处理建议

服务器环境以及配置 【机型】虚机 处理器&#xff1a; Kunpeng-920 内存&#xff1a; 40G 【内核版本】 4.19.90-23.8.v2101.ky10.aarch64 【OS镜像版本】 银河麒麟操作系统 Kylin-Server-10-SP1-Release-Build20-20210518-arm64 【第三方软件】 智能运维系统、mysq…

5G移动网络运维实验(训)室解决方案

随着第五代移动通信技术&#xff08;5G&#xff09;的快速普及和工业互联网的迅猛发展&#xff0c;全球制造业正面临着前所未有的深刻变革。5G技术凭借其超高的传输速率、极低的延迟以及大规模的连接能力&#xff0c;为工业自动化、智能制造等领域带来了革命性的技术支持。为了…

Vatee万腾平台:赋能企业,共筑智慧经济新高地

在智慧经济时代的大潮中&#xff0c;企业如何把握机遇&#xff0c;实现转型升级&#xff0c;成为行业内的佼佼者&#xff1f;Vatee万腾平台以其卓越的技术实力、前瞻性的战略眼光和全方位的服务体系&#xff0c;正逐步成为企业数字化转型的坚实后盾&#xff0c;赋能企业&#x…

软考真题之软件设计师的程序语言设计题型(上午题)

目录 编程程序和解释程序 相关习题 函数 编译,解释和翻译阶段 符号表 ​编辑 相关习题 ​编辑 词法分析 语法分析 语义分析 目标代码生成 相关习题 中间代码生成 正规式 相关习题 有限自动机 相关习题 上下文无关文法 相关习题 比较偏的真题 编程程序和解…

Python OpenCV 影像处理:傅立叶转换

►前言 上篇介绍基于计算影像的梯度&#xff0c;通过在影像中找到梯度值的变化来识别边缘。 本篇将介绍傅立叶变换的基本原理&#xff0c;了解傅立叶变换是如何将影像从空间域转换到频率域的&#xff0c;以及为什么这种转换在影像处理过程中是有用的。以及傅立叶变换的实际应…

微服务日常总结

1.当我们在开发中&#xff0c;需要连接多个库时&#xff0c;可以在yml中进行配置。 当在查询的时候&#xff0c;跨库时&#xff0c;需要通过DS 注解来指定&#xff0c;需要yml配置需要保持一致。 2. 当我们想把数据存入到clob类型中&#xff0c;需要再字段 的占位符后面加上j…

微服务--Nacos

一、Nacos简介 Nacos&#xff08;Naming and Configuration Service&#xff09;是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它致力于帮助开发者快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos支持几乎所有主流类型的服…

AFSim 仿真系统----性能工具

什么是 WPR/WPA&#xff1f; Windows 性能记录器 (WPR) 和 Windows 性能分析器 (WPA) 是 Windows 性能工具包中提供的性能监控工具。它们是免费的工具&#xff0c;可以通过下载和安装 Windows 评估和部署工具包 (ADK) 来获得。 WPR 是一个工具&#xff0c;允许用户动态部署事…

【LeetCode】09.回文数

题目要求 解题思路 主要是提防越界问题 代码实现 class Solution { public:bool isPalindrome(int x) {//处理边界if(x<0) return false;long tempx,ret0;while(temp){retret*10temp%10;temp/10;}return xret;} };

线性代数|机器学习-P34神经网络和学习函数

文章目录 1. 神经网络2. 损失函数3. 距离矩阵 1. 神经网络 构建一个神经网络步骤如下&#xff1a; 构建一个神经网络 构造一个学习函数 F ( x , v ) F(x,v) F(x,v),x代表权重 A k , b k A_k,b_k Ak​,bk​&#xff0c;v代表样本特征向量,ReLu激活函数 v 1 R e L u [ F ( A …

Windows安装anaconda注意事项及jupyter notebook更换目录

anaconda的介绍就不罗嗦了&#xff0c;既然准备安装了&#xff0c;说明你已经有所了解了。直入主题&#xff0c;Anaconda官网下载&#xff0c;实在太慢&#xff0c;可到https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/下载&#xff0c;注意&#xff0c;这是清华镜像站…

深度评测热门翻译工具,携手你的翻译得力助手

随着互联网技术的飞速发展&#xff0c;全球化交流日益频繁&#xff0c;跨语言沟通的需求也随之激增。对于外语水平有限的朋友来说&#xff0c;翻译器是一个必不可少的工具。今天我就分享几款我用的翻译器吧。 1.福晰在线翻译 链接直达>>https://fanyi.pdf365.cn/doc …

在嵌入式板子上搭建和自定义live555服务器---编译问题和方法整理

live555 官方网站 点我直达&#xff0c;live555是一个简单的专注于实现RTSP服务器的开源库。它自带解析H264 H265 mp3等源的API&#xff0c;有一个简单的推流文件参考RTSP服务器例程testH264VideoStreamer也有官方实现的LIVE555 Media Server。无论是命令行使用还是用API实现定…