【C语言深入】深入理解程序的预处理过程

这篇具有很好参考价值的文章主要介绍了【C语言深入】深入理解程序的预处理过程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、程序的编译环境与执行环境简介

我们平时所写的每一个.c文件都会经过编译和连接的过程之后才会形成一个可执行程序:
【C语言深入】深入理解程序的预处理过程
今天我们就来详细的看看编译和连接这两个过程的具体细节。

程序的翻译环境与执行环境
在ANSI C的任何一种实现中,存在两个不同的环境。

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

而我们的编译和连接就是在翻译环境中完成的,当.c原文件经过翻译环境后就会形成一个.exe的可执行文件,而这个可执行文件执行所依赖的就是执行环境。
【C语言深入】深入理解程序的预处理过程
所以在翻译环境中就有这样两个工具:编译器和连接器,分别完成编译和连接的工作。
而我们的每一个.c源文件都会单独经过编译器的编译,编译之后每一个.c源文件都会对应生成一个.obj(windows平台上)的目标文件。最后由连接器把这些目标文件再加上一些链接库连接成一个可执行文件。
具体过程可如下图所示:
【C语言深入】深入理解程序的预处理过程
编译与连接的再细分
关于编译与连接其实还可以继续细分为很多个小步骤,具体细分如下图所示:

【C语言深入】深入理解程序的预处理过程
而我们今天主要讲的还是预处理的内容。

二、程序的“翻译”过程

1、预处理

1.1、头文件的包含

我们每次写C程序之前都要先用#include指令来进行一些头文件的包含,这个头文件的包含其实就是在预处理中完成的。
为了验证此过程,我们需要用到gcc编译器,因为如果是其他集成开发环境的话,这个过程是已经被封装好了的。
我这里使用的是VSCode编辑器里搭建的gcc编译器。
比如说我们现在有这样一个.c源文件:
【C语言深入】深入理解程序的预处理过程
我们可以使用以下指令将预编译的结果重定向到一个文件中去:
【C语言深入】深入理解程序的预处理过程
动linux指令的朋友就会知道,-E选项表示的是在预编译完成后就停下,-o选项表示将结果保存到一个文件中。
完成后我们就可以打开Exercise.i文件来看看:
【C语言深入】深入理解程序的预处理过程
打开后我们就会发现里面有一大堆我们看不懂的东西,直到800多行后我们才看见我们自己写的代码:
【C语言深入】深入理解程序的预处理过程
其实前面那800多行代码就是我们头文件的内容。
而如果我们在代码中并没有引入头文件:
【C语言深入】深入理解程序的预处理过程
那我们在重定向后的结果中就不会看到那一大堆东西了:
【C语言深入】深入理解程序的预处理过程

1.2、去注释

预处理阶段要做的第二个工作就是去注释,比如我们可以在我们的代码中写上一条注释:
【C语言深入】深入理解程序的预处理过程
我们可以看到重定向后的文件中并不会出现这句注释:
【C语言深入】深入理解程序的预处理过程
【C语言深入】深入理解程序的预处理过程
所以在平时写代码的过程中不论我们写了多少注释,其对程序都是没有任何负担的,因为预编译后它们就都全部消失了。

1.3、替换宏

预处理阶段还要做的一件事就是对宏进行替换。
也就是说平时我们用#define定义的各种符号实际上在预编译后就会被全部替换掉,从这也可以理解为什么宏是不可被调试的。
比如我们可以在我们的代码中写上这样的两个宏:
【C语言深入】深入理解程序的预处理过程
当我们执行完预编译之后就会发现.i文件中的宏就给替换掉了:
【C语言深入】深入理解程序的预处理过程

先去注释还是先替换宏呢?

那么在预编译阶段是先去注释还是先替换宏呢?
我们可以设计一下方法来验证这个问题:
【C语言深入】深入理解程序的预处理过程
对于这个例子,如果是先替换再在去注释的话,那么预处理的结果应该是先将BSC替换成//,再去注释。那么我们在结果中就将看不到printf这条语句。
但我们的结果却并非如此:
【C语言深入】深入理解程序的预处理过程
【C语言深入】深入理解程序的预处理过程
我们会发现在我们的结果中依然能够看到printf这条语句出现。
所以这恰恰说明了在预处理阶段是先去注释,然后再进行宏替换,因为先去注释,所以BSC的内容就被去掉了,所以BSC的内容就是空。然后在替换BSC的时候,就等于替换了个空,所以我们的结果中还是可以看到printf这条语句的。

1.3条件编译

预处理阶段还需要做的一件工作就是条件编译。
在实际开发中,有时候因为应用场景的不同或平台的不同,我们需要选择性的对一些代码进行编译,对一些代码不进行编译。这时候就需要用到条件编译了。
例如:
【C语言深入】深入理解程序的预处理过程
我们来看看预编译的结果:
【C语言深入】深入理解程序的预处理过程
【C语言深入】深入理解程序的预处理过程
我们会发现,结果里并没有有printf这条语句。
而如果我们把_A给定义上:
【C语言深入】深入理解程序的预处理过程
按我们在结果中就可以看到这条语句了:
【C语言深入】深入理解程序的预处理过程
关于其他的一些关于条件编译的预定义符号会在之后介绍到。

2、编译

如果我们想要观察,就需要用-S的指令,这个指令会生成一个.s的文件,里面就是编译的结果:
【C语言深入】深入理解程序的预处理过程
【C语言深入】深入理解程序的预处理过程
【C语言深入】深入理解程序的预处理过程
大家应该都看得出,其实.s里放的就是汇编代码。
所以,编译阶段所做的工作就是将C语言代码转化成汇编代码。
而我们上面所列出的语法分析、词法分析……就是在这个转化过程中完成的,但这并不是我们今天的重点。

3、汇编

如果我们想要观察到汇编产生的结果的话,就需要用到-c指令,该指令会生成一个.o的目标文件文件:
【C语言深入】深入理解程序的预处理过程
【C语言深入】深入理解程序的预处理过程
而这个.o文件其实是一个二进制文件,所以汇编阶段所做的工作就是将汇编代码转化成二进制文件。
最后这些.o文件就会被连接器连接起来,形成一个可执行文件。

4、连接

我们的最终目的就是生成可执行文件,所指令就是直接用gcc编译即可:
【C语言深入】深入理解程序的预处理过程
然后就会生成一个可执行文件a.exe:
【C语言深入】深入理解程序的预处理过程
我们可以直接运行a.exe文件:
【C语言深入】深入理解程序的预处理过程

三、C语言与定义符号的介绍和使用

1、#define定义宏

1.1、数值常量宏

我们在编写程序的时候,总会遇到一些常量会被大量的重复使用,如果某一天我们需要对这些常量进行修改,那就得修改所有的地方,太麻烦了。
所以,我们就有了数值常量宏。
我们使用#define指令来定义数值常量宏:

#define MAX 1

当我们需要使用的时候,直接将宏赋值给变量即可:

int a = MAX;

这样如果以后我们需要对使用宏的地方就行修改,就可以直接修改宏的值即可,这大大增强了我们代码的灵活性和可维护性。

1.2、定义表达式

我是我们也可用#define来定义一些带参数的表达式,这些表达式宏使用起来核函数非常类似,也是可以传递参数的,例如求最大值如果用宏来解决看起来就比用函数来解决轻松得多:

#define MAX(x, y) X > y ? x : y

使用起来也可以完全像使用函数一样使用:
【C语言深入】深入理解程序的预处理过程
但宏与函数的最大不同在于红是完全替换的,而函数不是,这也导致使用宏可能会出现一个在函数中不可能出现的问题,那就是操作符的优先及问题,最典型的一个例子就是我们使用宏来定义一个求平方的表达式:

#define SQUARE(x) x * x

如果我们的参数值是一个单独的数字或者变量,那就不会出什么问题:
【C语言深入】深入理解程序的预处理过程
但只要参数变成了表达式,那就大有可能出问题了:
【C语言深入】深入理解程序的预处理过程
答案并不是4 * 4 = 16,这是因为宏是完全替换的,替换后就变成了a + 1 * a + 1 ,那其结果当然就是7了。
解决此问题的方法其实很简单,既然想要参数是一个整体,那就让它成为一个整体就行了,怎么办呢?
不要吝啬括号就行了:
【C语言深入】深入理解程序的预处理过程

怎么解决多条语句问题

但宏定义的表达式还有一个更难解决的问题,那就是当表达式是多条语句时候。例如我现在想定义一个宏,来对两个变量进行初始化,那我们就可以这样写:

#define INIT(x, y) a = 0; \
                   b = 0;

这样写在大多数情况下使用是没有问题的,但有一种情况例外,那就是如下的情况:
【C语言深入】深入理解程序的预处理过程

这样写就会发现代码在编译时就已经报错了,原因想必大家也都知道,如果if和else后面不加大括号的话,那就只能执行一条语句,而宏又是完全替换的,这就相当于在if后面插入了两条语句。所以这里的报错就应该是else未能找到匹配的if:
【C语言深入】深入理解程序的预处理过程
那这个问题应该怎么解决呢?
一个最直接的方法就是在if和else语句后面都加上大括号:
【C语言深入】深入理解程序的预处理过程
但这样的解决方案并不是最好的,这是因为每个人的编码习惯是不一样的,虽然if和else语句后面添加大括号是一种很好的编码规范。但并不是每个人都能做到,特别是一些刚学编程不久的小白。
所以这种方法的通用性不高。
有人可能就会想到可以给宏本身带上大括号:
【C语言深入】深入理解程序的预处理过程
但如我们看到的一样,也报错了,究其原因也还是因为宏是完全替换的,这样子替换后那就变成了在一个大括号后面加上了分号了,而这种语法是非法的。
那人有偶要说了:那不要在INIT(x,y)后面加上分好不就得了?
哎,这不就是再跟语法相抗衡吗?我们编写程序的习惯和语法都是在每一个语句结束机上分号,要是这样干,那写出来的代码不就很奇怪了吗?
那这个问题到底该怎么解决呢?我们要如何在C语言当中写一个可容纳大块代码的宏呢?
其实这个问题适用最终解决方案的,那就是使用do{}while(0)结构。(要注意while后面一定不能加分号)
这样就可以在do后的大括号里写多条语句了:
【C语言深入】深入理解程序的预处理过程
这样我们就看不到任何报错了。

1.3、#undef

#undef就是用来取某个宏的定义的,用法很简单,如下图:
【C语言深入】深入理解程序的预处理过程
当我们对MAX定以后在取消定义,如果再使用,那就会直接报错。

1.4、宏能充当注释吗?

那么宏能充当注释吗?
我想聪明的朋友心里已经有答案了。上面就已经验证过,在预处理阶段去注释是发生在宏替换之前的,也就是说不管你想让宏的内容是"//“形式的注释或者”/**/"形式的注释,他们都在宏替换之前被清除了。所以这样定义的就都等于空白。
所以结论是,宏不能充当注释。文章来源地址https://www.toymoban.com/news/detail-404815.html

到了这里,关于【C语言深入】深入理解程序的预处理过程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 程序环境和预处理(含C语言程序的编译+链接)--2

    文章前言: 上章我们把      程序的翻译环境     程序的执行环境   C语言程序的编译+链接     预定义符号介绍    预处理指令   #define    宏和函数的对比     预处理操作符    #和##的介绍   的相关知识进行了梳理讲解,接下来被把剩余知识    命令定义     预处

    2024年02月14日
    浏览(45)
  • GCC编译过程:预处理->编译->汇编->链接

    目录 引言  概括介绍 一、预处理 二、编译 三、汇编 四、链接 总结 当使用集成开发环境(IDE)进行C语言编程时,点击\\\" 编译 \\\"按钮后,整个C程序从源代码到可执行文件的生成过程会自动完成。IDE会在后台为我们执行C语言的编译过程,将源代码转换为最终的可执行文件。虽

    2024年02月13日
    浏览(36)
  • 【C语言:编译、预处理详解】

    我们都知道,一个程序如果想运行起来要经过编译、链接然后才能生成.exe的文件。 编译⼜可以分解为三个过程: 预处理(有些书也叫预编译)、 编译 汇编 预处理阶段 主要处理那些源文件中以#开始的预编译指令。比如:#include,#define,处理的规则如下: 删除所有的注释

    2024年02月03日
    浏览(42)
  • C语言-程序环境和预处理(2)--带副作用的宏参数,宏与函数的对比,#undef,条件编译,文件包含

    上一篇文章–《C语言-程序环境和预处理(1)》讲述了程序的翻译环境和执行环境,编译、连接,预定义符号,#define,#符号和##符号的相关知识。 链接: 《C语言-程序环境和预处理(1)》 本篇文章,讲述带副作用的宏参数,宏与函数的对比,#undef,条件编译,文件包含的相

    2024年02月08日
    浏览(52)
  • 深入理解机器学习——数据预处理:归一化 (Normalization)与标准化 (Standardization)

    分类目录:《深入理解机器学习》总目录 归一化 (Normalization)和标准化 (Standardization)都是特征缩放的方法。特征缩放是机器学习预处理数据中最重要的步骤之一,可以加快梯度下降,也可以消除不同量纲之间的差异并提升模型精度。 归一化(Normalization)是将一组数据变

    2024年02月08日
    浏览(37)
  • 【c语言】详解c语言#预处理期过程 | 宏定义前言

    c语言系列专栏: c语言之路重点知识整合   创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡𖥦)!!  主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ 代码编译到执

    2024年02月01日
    浏览(38)
  • 初始C语言最后一章《编译、链接与预处理详解》

    感谢老铁们的陪伴和支持,初始C语言专栏在本章内容也是要结束了,这创作一路下来也是很不容易,如果大家对 Java 后端开发感兴趣,欢迎各位老铁来我的Java专栏!当然了,我也会更新几章C语言实现简单的数据结构!不过由于我是Java 技术栈的,所以如果以后有机会学习C

    2024年04月16日
    浏览(32)
  • 【C语言】程序环境和预处理|预处理详解|定义宏(下)

    主页:114514的代码大冒 qq:2188956112(欢迎小伙伴呀hi✿(。◕ᴗ◕。)✿ ) Gitee:庄嘉豪 (zhuang-jiahaoxxx) - Gitee.com 文章目录 目录 文章目录 前言 2.5带副作用的宏参数 2.6宏和函数的对比 3#undef ​编辑 4 命令行定义 5 条件编译 6 文件包含 总结 咱们书接上回 2.5带副作用的宏参数 先来

    2024年01月17日
    浏览(50)
  • C语言——程序环境和预处理(再也不用担心会忘记预处理的知识)

    先简单了解一下程序环境,然后详细总结翻译环境里的编译和链接,然后在总结编译预处理。 在 ANSI C 的任何一种实现中,存在两个不同的环境 翻译环境:这个环境中源代码被转换为可执行的机器指令。 执行环境:执行二进制代码。 计算机如何执行二进制指令? 我们写的C语

    2024年02月09日
    浏览(48)
  • 【C语言】程序环境和预处理

    本章重点 程序的编译环境 程序的执行环境 详解:C语言程序的编译加链接 预定义符号介绍 预处理指令 #define 宏和函数的对比 预处理操作符#和##的介绍 命令定义 预处理指令 #include 预处理指令 #undef 条件编译 程序的编译环境和执行环境 在ANSIC的任何一种实现中,存在两种不同

    2024年01月21日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包