首页 > 其他分享 >【C语言】编译和链接(编译环境和运行环境)

【C语言】编译和链接(编译环境和运行环境)

时间:2024-10-25 19:20:16浏览次数:7  
标签:符号 环境 链接 编译 add C语言 我们 预处理

在这里插入图片描述

文章目录

一、翻译环境和运行环境

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

  1. 翻译环境:在翻译环境中,会通过编译和链接两个大步骤,其中编译又分为了预处理(预编译)、编译和汇编,将源代码转换为可执⾏的机器指令(⼆进制指令),生成可执行程序

  2. 运行环境:即执行环境,在运行环境中会执行可执行程序,并输出结果

如下图:
在这里插入图片描述
   接下来我们就来学习在翻译环境和运行环境中具体会做些什么

二、翻译环境

   上面讲到了,翻译环境是用来将源代码转换为可执⾏的机器指令(⼆进制指令),生成可执行程序的,那么它到底是怎么将源代码转换成可执行的机器指令,又是怎么把机器指令生成可执行程序呢?我们一起来学习一下
   翻译环境是由编译和链接两个⼤的过程组成的,⽽编译⼜可以分解成:预处理(有些书也叫预编译)、编译、汇编三个过程,接下来我们就来学习它们

1.编译

编译要完成的就是将我们的源代码转换成可执行的机器指令,如图:
在这里插入图片描述
   编译器就可以实现编译的功能,也就包括了预处理,编译,汇编这三个操作,我们学习编译也就是学习这三个操作的过程
   由于VS是一个高度集成的开发环境,它已经把编译这样的细节隐藏起来了,在VS中,我们只需要按下ctrl+f5,那么VS就会一下就帮我们把编译、链接和执行这三个动作一起完成了,瞬间就可以看到结果
   所以在VS中我们无法看到.c的源文件编译和链接的完整过程,这个时候我们就可以借助其它的编译器,在下文中就是以gcc为例进行整个编译链接的讲解

预处理

   预处理又称预编译,在预处理阶段,后缀为.c的文件将会被处理为.i的文件,如test.c经过预处理后就会变成test.i
   预处理阶段要做的事主要有以下几点:

  1. 将所有的 #define 删除,并展开所有的宏定义,比如使用宏定义了一个常量,我们一般会这样写:
//使用宏定义了一个常量
#define N 100
//使用宏
int arr[N];

   那么经过预处理之后,#define N 100这条语句就会被删除,并且这个宏定义将会被展开,在这里就是将所有N替换成100,如下:

//预处理后,宏定义语句被删除
//展开宏定义,在这里就是将N替换成100
int arr[100];
  1. 处理所有的条件编译指令,如: #if、#ifdef、#elif、#else、#endif等等,在下一篇预处理详解我们会讲到,这里简单介绍一下
  2. 处理#include 预编译指令,将包含的头⽂件的内容插⼊到该预编译指令的位置,比如包含头文件stdio.h,那么就会将头文件stdio.h中的所有内容插入到原位置
       虽然VS不会生成.i的文件,但是我们还是可以看到头文件的内容,首先使用#include包含stdio.h,然后使用ctrl+单击鼠标,就可以看到stdio.h这个头文件的内容,有两千多行的代码,在预处理后,就会全部插入到我们的源文件中来
  3. 经过预处理后,会删除所有的注释,所以我们写了注释才不会影响代码的运行,因为在我们正在编译前,就已经把它删除了
  4. 添加⾏号和⽂件名标识,⽅便后续编译器⽣成调试信息等
  5. 保留所有的#pragma的编译器指令,它可以保证我们不重复包含头文件

   经过预处理后的 .i ⽂件中不再包含宏定义,因为宏已经被展开,并且包含的头⽂件都被插⼊到 .i⽂件中,当我们⽆法知道宏定义或者头⽂件是否包含正确的时候,可以查看预处理后的 .i ⽂件来确认
   预处理这部分内容还有许多的知识点需要我们掌握,这里就不展开讲了,在下一篇文章,我们会详细讲解预处理的各种指令

编译

   当我们进行预处理后,就来到了编译阶段,编译过程就是将预处理后的⽂件进⾏⼀系列的:词法分析、语法分析、语义分析及优化,⽣成相应的汇编代码⽂件
   我们现在就用一句代码为例,看看词法分析、语法分析、语义分析的大致实现思路,如下:

arr[index] = (index+4)*(2+6);
  1. 词法分析:首先代码就来到了词法分析,这个阶段会将源代码程序输⼊扫描器,扫描器的任务就是简单的进⾏词法分析,把代码中的字符分割成⼀系列的记号(关键字、标识符、字⾯量、特殊字符等)
       上⾯程序进⾏词法分析后得到了16个记号,然后就会生成一个记号表,如下图:
    在这里插入图片描述
       在这个阶段会简单的标记每个记号,然后生成一个记号表,这个符号表在后面链接的地方还会用到,我们到时候会再来说它的另一个作用

  2. 语法分析:来到语法分析阶段后,会将源代码放入语法分析器,将对扫描产⽣的记号进⾏语法分析,从⽽产⽣语法树,这些语法树是以表达式为节点的树,如图:
    在这里插入图片描述
       在这个阶段,我们代码的意思基本上就明确了,相当于在语法分析阶段,会把要组合的记号组合起来,明确这些记号的基本含义
       并且在图上我们也可以看出来,这颗语法树的节点是一个又一个的表达式组成,如果在这个时候出现简单的语法错误就可以发现,比如少写一个括号,就不能像这样构成一个以表达式为节点的语法树,程序就可能会报错

  3. 语义分析:经过词法分析和语法分析后,由语义分析器来完成语义分析,即对表达式的具体语法层⾯的分析
       编译器所能做的分析是语义的静态分析,静态语义分析通常包括声明和类型的匹配,类型的转换等,这个阶段会报告错误的语法信息,如下图:
    在这里插入图片描述
       在语义分析这个阶段就能基本明确程序的语法含义了,如明确了类型、类型的转换等信息,而语法分析中只是对记号表中的记号进行了组合和简单的翻译
       在这个阶段已经可以判断表达式之间的关系了,比如整型加整型是整型,整型赋值给整型等等,并且在这个阶段可以找出语法错误
       比如在赋值表达式的左边算出的结果是一个浮点型,而左边算出来了的却是一个整型,那么就会进行强制类型转换,如果表达式左边是整型,而右边是结构体,就会报错
       而且最关键的一点是,我们通过语义分析已经知道了代码的含义,那么把它翻译成汇编代码也不是难事了,所以在这个阶段会正在将源代码翻译成汇编代码,并做相关的优化

汇编

   经过预处理和编译阶段后就来到了汇编阶段,在编译阶段的语义分析我们就把源代码翻译成了汇编代码,而在汇编阶段做的事情就是将翻译过来的汇编代码再次转换为计算机可以识别的机器指令(二进制指令)
   在翻译时,每⼀个汇编语句⼏乎都对应⼀条机器指令,在翻译期间也不会对代码做什么优化,只是根据汇编指令和机器指令的对照表⼀⼀的进⾏翻译,最终将汇编代码转换成了机器指令,生成一个.obj的目标文件

2.链接

   链接是⼀个复杂的过程,链接的时候需要把⼀堆⽂件链接在⼀起才⽣成可执⾏程序,链接过程主要包括:地址和空间分配,符号决议和重定位等这些步骤,那么我们为什么要使用链接这一个步骤呢?
   链接解决的是⼀个项⽬中多⽂件、多模块之间互相调⽤的问题,比如在一个项目中有两个.c文件,代码如下:
   add.c:

//定义一个函数add
int add(int x, int y)
{
	return x + y;
}

   test.c

#include <stdio.h>

int main()
{
	int a = 3;
	int b = 2;
	int ret = add(a, b);
	printf("%d\n", ret);
	return 0;
}

   当我们实现好这两个文件后,直接运行后发现出现了错误,我们来看看具体报错:
在这里插入图片描述
   它说函数add未定义,a是没有声明的标识符,这是为什么呢?这就要涉及到链接了,我们在编译阶段会将我们的源代码翻译成机器指令,生成后缀名为.obj的目标文件,但是我们要注意的是,编译是针对于单个文件的
   什么意思呢?就是一个.c的文件生成一个.obj的目标文件,如果有多个.c的文件则生成多个.obj的文件,它们之间互不影响,所以如果我们想要一个文件中的某个函数在另一个文件中使用就做不到,我们可以画图理解,如图:
在这里插入图片描述
   从图片中我们很明显地看出来了,多个.c文件生成多个.obj文件,之间互不影响,所以如果我们要让一个文件中的某个函数在另一个文件中使用就必须通过链接来完成,接下来我们就一起来学习链接的过程
   在链接过程中,需要用到之前我们在词法分析时生成的符号表,将那些特殊记号记录下来,但是链接时的符号表则更为复杂,会有导出符号表、未解决符号表和地址重定向表三个表,这里我们就简单将它们合并一下,用一个表把类似的原理讲一讲,等后期会出详细的链接过程
   首先我们要知道符号的类型有哪些,如下:

  1. 全局符号(Global symbols):由当前模块定义并能被其他模块引用的符号(指不带static的全局变量)
  2. 外部符号(External symbols):由其他模块定义,并能被当前引用的全局符号
  3. 局部符号(Local symbols):仅由当前模块定义和引用的本地符号。例如,定义的static函数和变量
    接下来我们可以简单的画出add.c中的符号表,在画的时候我们要注意,在add.c中并没有外部符号的引用,出现的符号add拥有正常的声明和定义,所以会直接分配一个虚拟地址,如图:
    在这里插入图片描述

   接下来我们就来画test.c的符号表,为了进行对比,在test.c的符号表中我们也只画出只有add符号的符号表,其它符号就省略掉,如下图:
在这里插入图片描述

   可以对比看出来,在add.c中,add符号被看做全局符号,拥有自己的地址,但是在test.c中,add符号被看做外部符号,只有一段可能错误的地址(不一定就是0地址)
   链接中关键的就是这一步,由于test.c不认识这个符号所以要报错,为了能够正常链接,我们需要做的就是:在test.c中使用extern关键字对add符号进行声明,然后链接器就会知道,这个符号在其它文件中有定义,先给出一个可能错误的地址,之后再修正
   然后最后为了修正test.c中add符号的地址,使它正确,会进行重定位操作,重定位会计算每个定义的符号在虚拟地址空间的绝对地址,将可执行文件中的符号引用处修改为重定位后的地址信息
   将符号表修正后,test.c文件的符号表中的add符号的地址就会修正为正确的地址,test.c文件就可以通过这个地址来访问add函数,这就是链接中的重定位

   前⾯我们⾮常简洁的讲解了⼀个C的程序是如何编译和链接,到最终⽣成可执⾏程序的过程,其实很多内部的细节⽆法展开讲解。⽐如:⽬标⽂件的格式elf,链接底层实现中的空间与地址分配,符号解析和重定位等,如果你有兴趣,可以看《程序员的⾃我修养》⼀书来详细了解

四、运行环境

   在运行环境中有几个要点,我们只需要简单了解一下:

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

   今天的编译和链接就讲到这里啦,后面有机会可能还会仔细讲讲链接的过程,这也可能是我们C语言的倒数第二篇博客,下一篇就是C语言的最后一篇了,有没有非常兴奋和有成就感呢?欢迎在评论区留言,有什么问题也欢迎提出
   那么今天就到这里,bye~

标签:符号,环境,链接,编译,add,C语言,我们,预处理
From: https://blog.csdn.net/hdxbufcbjuhg/article/details/143220552

相关文章

  • 【C语言】扫雷详解(手把手教你敲扫雷)
    目录前言正文开始1.扫雷游戏的分析与设计1.1扫雷游戏的功能说明1.2游戏的分析和设计1.2.1数据结构的分析1.2.2文件结构设计2.代码实现2.1.1文件game.h2.1.2文件game.c2.1.3文件test.c2.2讲解2.2.1主体2.2.2有关定义2.2.3函数1.InitBoard()初始化棋盘2.SetMin......
  • php+vscode+xdebug搭建php调试环境
    php.ini中加入===========================[xdebug]zend_extension=G:/phpstudy_pro/Extensions/php/php7.3.4nts/ext/php_xdebug.dllxdebug.mode=debugxdebug.start_with_request=yes====================================================vscode,launch.js文件"......
  • vs编译项目失败,提示 要求“SourceRoot”路径以斜杠或反斜杠结尾
    从git上下载部分项目编译时出现错误,提示如下:严重性代码说明项目文件行禁止显示状态详细信息错误(活动) 要求“SourceRoot”路径以斜杠或反斜杠结尾:“E:\dev_tools\.nuget\packages” MahApps.Metro(net462),MahApps.Metro(net6.0-windows),MahApps.Metro(net8.0-wi......
  • 国标GB28181公网直播EasyGBS国标GB28181软件在环境视频监控中的应用
    在当今快速发展的科技时代,视频监控系统已经渗透到我们生活的方方面面,从城市管理到个人安全,都离不开高效、智能的视频监控解决方案。而国标GB28181-2022平台EasyGBS能够通过简单的网络摄像机通道配置,将传统监控行业里面的高清网络摄像机IPCamera、NVR等GB28181国标协议输出的设......
  • 如何在C语言中进行数据加密
    ##如何在C语言中进行数据加密在讨论C语言中的数据加密时,我们首先需要明确两个核心观点:使用加密库、实现自定义加密算法。其中,使用加密库是最直接且高效的方式,因为这允许开发者利用已经广泛测试和验证的加密算法来保护数据的安全性,而无需深入了解加密算法的内部工作原理。此外,一......
  • 为什么c语言不支持热更新
    ###为什么C语言不支持热更新在讨论为什么C语言不支持热更新时,我们首先需要明确几个核心观点:C语言的编译性质、内存管理机制、以及与操作系统的底层交互方式。编译性质意味着C语言代码在运行前需要被完全编译成机器码,这个过程中产生的是一个静态的、不可变的执行文件。这与热更......
  • 数据结构图的最短路径-弗洛伊德算法(有向图+数据结构课本C++代码一比一转C语言+邻接矩
    弗洛伊德算法有向图代码如下:#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>#include<stdlib.h>#include<limits.h>#defineMaxInt32767#defineMVNum100intPath[MVNum][MVNum];//存放前驱索引的intD[MVNum][MVNum];//存放当前已知的权值//图的邻接......
  • 实验三 C语言函数应用编程
    一、实验目的 能正确使用C语法规则定义,声明,调用函数能正确编写递归函数针对具体问题场景,合理抽象出独立的功能模块,正确定义函数并使用,使得代码更具可读性,可维护性针对具体问题场景,能正确,合理使用全局变量和局部static变量,解决实际问题二、实验准备 1,函数定义,声明,调用的语......
  • 01-计算机基本认知与环境搭建
    计算机基本认识Python基本介绍Python就是一门编程语言,而且是现在世界上最流行的编程语言之一。编程语言就是人类和计算机进行交流的语言,是用来定义计算机程序的形式语言。我们通过编程语言来编写程序代码,再通过语言处理程序执行向计算机发送指令,让计算机完成对应的工作。计......
  • 数据结构 ——— C语言实现链式队列
    目录队列的概念以及示意图数组队列和链式队列链式队列的结构 实现链式队列的准备工作实现链式队列1.初始化2.打印队列的所有数据3.数据入队列(尾插)4.数据出队列(头删)5.访问队头的数据6.访问队尾的数据7.队列数据总个数8.判断队列是否为空9.释放队列的所......