C语言:编译,链接和预处理详解

目录

一.翻译环境和运行环境

二.翻译环境

​编辑 1.预处理(预编译)

(1).#和##运算符

①.#运算符

②## 运算符 

(2).#undef

(3).条件编译 

 ①单分支的条件编译

②多个分支的条件编译 

③判断是否被定义 

​编辑 ④嵌套指令

2.编译

(1).词法分析 

(2).语法分析

(3).语义分析

 3.汇编

4.链接

三.运行环境

 


 

在这之前,我们已经学习了很多的c语言的知识,学习了进行代码的编写和运行,那么,是什么让我们编写完的代码成功的运行和输出我们想要的结果呢?这就是我们今天要讲的内容:编译,链接和预处理详解。

首先,在我们进行对它们进行深入了解之前。我们要先知道什么是翻译环境和运行环境。

一.翻译环境和运行环境

在ANSI C的任何一种实现中,存在两个不同的环境,他们就是翻译环境和运行环境,而我们今天要学习的编译和链接就处在翻译环境当中。

在我们进行代码编写后,我们会形成一个或多个的(.c)文件,计算机会将这些(.c)文件放入到翻译环境中,经过编译,链接,将源代码转换为计算机可执行的机器指令即:二进制指令。之后,放入到Windows环境下(运行环境),他会形成(.exe)为后缀的可执行程序,最后进行输出。

84efa8c19b444894ba2cbb6bfc8e14a7.png 我们一下面图片中的代码为例:

c898c3f0c4414952bee06e85fd5a25d9.png

运行完后,我们在我们文件中就可以发现我们生成的(.exe)文件.

f72b5f5813be47cc99692ff4f604ec21.png

二.翻译环境

那翻译环境是怎么将源代码转换为可执行的机器指令的呢?这里我们就得深入了解一下翻译环境所 做的事情。 

其实翻译环境是由编译和链接两个大的过程组成的,而编译又可以分解成:预处理(有些书也叫预编译、编译、汇编三个过程。

并且在编译和链接中还存在着看不见的编译器(cl.exe)和链接器(link.exe),编译器会将(.c)文件生成对应的(.obj)目标文件(windows环境下)或(.o)目标文件(Linux环境下)。之后目标文件会和链接库在链接器的作用下生成(.exe)的可执行程序

44a9ee26164849cf9dfe1dbe6585c442.png

我们还是一上面的代码为例,去查看是否生成了(.obj)文件:

f9341701205b42f59d44f3187cc17eda.png

 00ffd86883e241b5a4e114dc81177d75.png

我们发现我们不仅成功找到了(.obj)文件,还在寻找的过程中发现了 Add.c文件和test.c文件

总结:

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

在刚才我们也提到了编译也分为3个部分: 预处理(有些书也叫预编译、编译、汇编三个过程。

f6f3f1b70b3b407eafdb3666f34b2abe.png 1.预处理(预编译)

在预处理阶段,源文件和头文件会被处理成为 .i 为后缀的文件。 在 gcc 环境下想观察⼀下,对 test.c 文件预处理后的.i文件,命令如下:

gcc -E test.c -o test.i

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

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

 我们发现我们的#define,注释都被删除,宏定义也都被展开。939fdbca90b34e98889da4e2dd444f81.png

(1).#和##运算符

①.#运算符

#运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。 #运算符所执行的操作可以理解为“字符串化”

当我们有⼀个变量 int a = 10; 的时候,我们想打印出: the value of a is 10 . 就可以写:

#define PRINT(n) printf("the value of "#n " is %d", n);

而代码就会被预处理为: 

printf("the value of ""a" " is %d", a);

②## 运算符 

## 可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称 为记号粘合

 这样的连接必须产生以个合法的标识符。否则其结果就是未定义的

//宏定义
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \return (x>y?x:y); \
}
GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main()
{//调⽤函数int m = int_max(2, 3);printf("%d\n", m);float fm = float_max(3.5f, 4.5f);printf("%f\n", fm);return 0;
}

(2).#undef

这条指令用于移除一个宏定义。 

 cc985e73f0f04a778bbc42ad70be947b.png

我们发现当我们用#undef修饰M后,M就不能够使用了,这就是#undef移除宏定义的作用。 

(3).条件编译 

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

 ①单分支的条件编译

#if 常量表达式

      //...

#endif


#include<stdio.h>
#define M 5
int main()
{
#if M==5printf("%d", M);
#endifreturn 0;
}

877a594fa51c402d9852c1f02f201157.png

②多个分支的条件编译 

#if 常量表达式

      //...

#elif 常量表达式

      //...

#else

     //...

#endif

#include<stdio.h>
#define M 5
int main()
{
#if M==1printf("haha\n" );
#elif M==2printf("hehe\n");
#elif M==5printf("heihei\n");
#endifreturn 0;
}

ac4ab14eb6774a5da40c0c2c9eea98c9.png

③判断是否被定义 

有两种写法

第一种:#if defined(symbol)

第二种:#ifdef symbol

 第一种:

#include<stdio.h>
#define M
int main()
{
#if defined (M)printf("haha\n");
#endifreturn 0;
}

2ec424b36f4144a992c09baf6eef08d4.png

第二种:


#include<stdio.h>
#define M
int main()
{
#ifdef Mprintf("haha\n");
#endifreturn 0;
}

d94e1b02b0e34134862287c18981f1b8.png

当然,这两个种条件编译的反义也有两种形式 

第一种:#if !defined(symbol)

第二种:#ifndef symbol

第一种: 

#include<stdio.h>int main()
{
#if defined(M)printf("hehe\n");
#endif
#if !defined (M)printf("haha\n");
#endifreturn 0;
}

6508cd14a3b347208637ea680b869803.png

第二种: 

#include<stdio.h>int main()
{
#ifdef M printf("hehe\n");
#endif
#ifndef Mprintf("haha\n");
#endifreturn 0;
}

f4535a4b4dfa46b5b85e7fda6f8a5af3.png ④嵌套指令

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

2.编译

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

gcc -S test.i -o test.s

下面以这段代码为例进行词法分析、语法分析、语义分析及优化:

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

(1).词法分析 

将源代码程序被输⼊扫描器,扫描器的任务就是简单的进行词法分析,把代码中的字符分割成一系列的记号(关键字、标识符、字⾯量、特殊字符等)。 上面程序进行词法分析后得到了16个记号:

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

(2).语法分析

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

74fc4e59fc2f4b54ab7a636be6e6775d.png

(3).语义分析

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

c98e8802e6d045379e685b57e0d90e3a.png

 3.汇编

汇编器是将汇编代码转转变成机器可执行的指令,每一个汇编语句几乎都对应一条机器指令。就是根 据汇编指令和机器指令的对照表一一的进行翻译,也不做指令优化。 汇编的命令如下:

 gcc -c test.s -o test.o

4.链接

链接是一个复杂的过程,链接的时候需要把一堆文件链接在一起才生成可执行程序。 链接过程主要包括:地址和空间分配,符号决议和重定位等这些步骤。 链接解决的是一个项目中多文件、多模块之间互相调用的问题。 

一这段代码为例:test.c 经过编译器处理⽣成 test.o add.c 经过编译器处理⽣成 add.o

ea4fddce55134357a0406288e61c6cef.png

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

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

 在这之后他会生成两个符号表,然后将 test.c 中所有引用到 Add 的指令重新修正,让他们的目标地址为真正的 Add 函数的地址,这个地址修正的过程也被叫做:重定位。

e9e58a47dd8c444081644a120a9db42d.png

 

三.运行环境

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

 

 

 

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

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

相关文章

由动静压之比求马赫数的MATLAB函数

函数介绍 输入&#xff1a;动静压之比 p r e pre pre 输出&#xff1a;马赫数 M a c h Mach Mach 【注】仅适合亚音速的情况&#xff0c;如果动静压之比过大或过小&#xff0c;会有相应的提示 函数源代码 function [m] pre2mach(pre) m(5*(pre1).^0.2857-5).^0.5; if pre&l…

封装左侧抽屉可拖拽组件【可多个】

一、案例效果 二、案例代码 封装抽屉组件 <template><div class"drag-drawer"><div class"out-box" :style"style"><mtd-tooltip:content"collapse ? 展开面板 : 收起面板"class"tool-tip":placeme…

AI漏扫工具:SmartScanner

SmartScanner 是一款先进的 AI 漏洞扫描工具&#xff0c;旨在帮助用户识别和修复软件、系统及网络中的安全漏洞。以下是 SmartScanner 的一些主要特点&#xff1a; 1.智能识别 通过机器学习和深度学习技术&#xff0c;SmartScanner 能够快速识别已知和未知的漏洞&#xff0c;提…

推荐一个移动端支持多框架的UI组件库

支持的前端框架&#xff1a;Vue、React、Angular 地址&#xff1a;https://ionicframework.com/docs/

Web端云剪辑解决方案,提供前端产品源码

美摄科技作为业界领先的视频技术服务商&#xff0c;匠心打造Web端云剪辑解决方案&#xff0c;以前沿技术赋能企业用户&#xff0c;开启视频创作与编辑的新纪元。 【云端赋能&#xff0c;重塑剪辑体验】 美摄科技的Web端云剪辑解决方案&#xff0c;颠覆了传统视频编辑的局限&a…

一文说透RTMP、RTSP、RTP、HLS、MPEG-DASH

实时视频传输协议 1. RTMP&#xff08;Real Time Messaging Protocol&#xff09; 简介&#xff1a;RTMP是由Adobe公司开发的实时消息传输协议&#xff0c;主要用于流媒体数据的传输。它基于TCP传输&#xff0c;具有低延迟、高可靠性的特点。特点&#xff1a;RTMP支持多种视频…

【Mysql】Centos 安装 Mysql8.0

官网下载安装包 官网地址&#xff1a;MySQL :: Download MySQL Community Server 查看服务器的名称和版本号 lsb_release -a 查看服务的架构 uname -m 下载对应的版本&#xff0c;这里操作系统选择 Red Hat 就可以了。&#xff08;CentOS 就是将 RHEL 发行的源代码从新编译…

828华为云征文 | 华为云 X 实例服务器存储性能测试与优化策略

目录 引言 1 华为云 X 实例服务器概述 2 存储性能测试方法与工具 2.1 测试方法 2.2 测试工具 3 FIO&#xff08;Flexible I/O Tester&#xff09;读写性能测试 3.1 顺序读写测试 3.2 随机读写测试 4 hdparm性能测试 4.1 实际读取速度测试 4.2 缓存读取速度测试 4.3…

最大似然估计,存在即合理

一、感性认识 认识的第一步来自感性的认识&#xff0c;先来感性的了解一下最大似然估计。现在&#xff0c;假设有两个学院&#xff0c;物理和外语学院。两个学院都各有特点&#xff0c;物理学院的男生占比大&#xff0c;外语学院女生占比大。如果在一次实验从两个学院中随机的…

SPSS26统计分析笔记——3 假设检验

1 假设检验原理 假设检验的基本原理源于“小概率事件”原理&#xff0c;是一种基于概率性质的反证法。其核心思想是小概率事件在一次试验中几乎不会发生。检验的过程首先假设原假设 H 0 {H_0} H0​成立&#xff0c;然后通过统计方法分析样本数据。如果样本数据引发了“小概率事…

《让手机秒变超级电脑!ToDesk云电脑、易腾云、青椒云移动端深度体验》

前言 科技发展到如今2024年&#xff0c;可以说每一年都在发生翻天覆地的变化。云电脑这个市场近年来迅速发展&#xff0c;无需购买和维护额外的硬件就可以体验到电脑端顶配的性能和体验&#xff0c;并且移动端也可以带来非凡体验。我们在外出办公随身没有携带电脑情况下&#x…

Vue3:toRaw与markRaw

目录 一.toRaw 1.性质 2.作用 二.markRaw 1.性质 2.作用 三.toRaw的使用 四.markRaw的使用 五.代码示例 在Vue 3中&#xff0c;toRaw和markRaw是两个用于处理响应式对象的全局函数。 一.toRaw 1.性质 toRaw是一个全局函数&#xff0c;它接受一个由reactive或ref生成…

彻底理解前端模块化

目录 引入历史问题 CommonJSexports导出module.exports导出require导入加载过程缺点 AMD规范&#xff08;基本不用&#xff09;require.js使⽤ CMD规范&#xff08;基本不用&#xff09;SeaJS的使⽤ ES Module简单使用export关键字import关键字export和import结合default⽤法im…

YOLOv10改进 | 特征融合篇,YOLOv10添加iAFF(多尺度通道注意力模块),二次创新C2f结构,提升小目标检测能力

摘要 特征融合,即来自不同层或分支的特征的组合,是现代网络架构中无处不在的一部分。虽然它通常通过简单的操作(如求和或拼接)来实现,但这种方式可能并不是最佳选择。在这项工作中,提出了一种统一且通用的方案,即注意力特征融合(Attentional Feature Fusion),适用于…

RK3568笔记六十三:基于LVGL的Linux相机

若该文为原创文章,转载请注明原文出处。 记录移植韦老师的基于LVGL的Linux相机项目,主要是想学习如何在LVGL下显示摄像头数据。 此项目是基于老师的源码框架移植的,地址是lv_100ask_linux_camera: 基于LVGL的Linux相机 (gitee.com) 个人使用的是RK3568,正点原子板子,所以…

WordPress 要求插件开发人员进行双因素身份验证

全球超过40%的网站由 WordPress 提供支持&#xff0c;其庞大的插件和主题生态系统在全球范围内提供了灵活性和定制性。然而&#xff0c;这种受欢迎程度也使其成为网络攻击的主要目标。 WordPress 将为所有插件和主题开发人员引入强制性双因素身份验证 (2FA)&#xff0c;以应对…

《经典图论算法》约翰逊算法(Johnson)

摘要&#xff1a; 1&#xff0c;约翰逊算法的介绍 2&#xff0c;约翰逊算法的实现步骤 3&#xff0c;约翰逊算法的准确性验证 4&#xff0c;约翰逊算法的代码实现 1&#xff0c;约翰逊算法的介绍 约翰逊算法(Johnson algorithm)是在稀疏图上求每对顶点之间最短路径的一种算法&a…

《解锁高效流程设计:深度剖析责任链模式与实战应用》

《解锁高效流程设计&#xff1a;深度剖析责任链模式与实战应用》 责任链模式 是一种行为设计模式&#xff0c;它允许多个对象来处理请求&#xff0c;而不预先指定具体的处理者。多个处理对象被连接成一条链&#xff0c;沿着这条链传递请求&#xff0c;直到某个处理对象决定处理…

SOMEIP_ETS_130: SD_Multicast_FindService_with_unicast_Flag_to_0

测试目的&#xff1a; 验证DUT能够忽略带有设置为0的单播标志的多播FindService请求&#xff0c;并以单播OfferService消息作为响应。 描述 本测试用例旨在确保DUT在接收到一个设置了单播标志为0的多播FindService请求时&#xff0c;能够忽略该标志并按照SOME/IP协议的要求&…

旧衣回收小程序搭建,开发功能优势

随着人们生活水平、消费水平的提高&#xff0c;在日常生活中产生了大量的限制物品&#xff0c;为了减少浪费&#xff0c;越来越多的人开始重视环保回收。旧衣物作为一种新型的回收方式&#xff0c;也逐渐得到了大众的关注&#xff0c;旧衣物回收市场发展规模也在持续上升&#…