C语言:预编译过程的剖析

目录

一.预定义符号和#define定义常量

二.#define定义宏

三.宏和函数的对比

四、#和##运算符

五、条件编译


在之前,我们已经介绍了.c文件在运行的过程图解,大的方面要经过两个方面。

一、翻译环境

1.预处理(预编译)

2.编译

3.汇编

4.链接

二、运行环境

我们在这里,主要介绍以下预处理阶段的事情,重点是#define定义宏宏和函数对比的各自优点和缺点

 

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

(1)将所有的 #define 删除,并展开所有的宏定义

(2)处理所有的条件编译指令,如:#if、#ifdef、#elif、#else、#endif

(3)处理#include 预编译指令将包含的头文件的内容插入到该预编译指令的位置。这个过程是递归进行的,也就是说也被包含的头文件可能包含其他文件

(4)删除所有的注释

(5)添加行号和文件名标识,方便后续编译器生成调试信息等。

(6)保留所有的#pragma 的编译器指令,编译器后续会使用。

一.预定义符号和#define定义常量

1)预定义符号

在C语言中,设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的

1.    _ _FILE_ _          //进行编译的源文件

2.    _ _LINE_ _          //文件当前的行号

3.    _ _DATE_ _         //文件被编译的日期

4.    _ _TIME_ _          //文件被编译的时间

5.    _ _STDC_ _          //如果编译器遵循ANSI  C,其值为1,否则未定义

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
	FILE* pf = fopen("test1.txt", "w+");
	printf("file:%s   line:%d   date:%d\n", __FILE__, __LINE__, __DATE__);
	return 0;
}

这里,可以看出,我们是可以直接运行这些与定义符号的。 

 2)#define定义常量

# define MAX 100
# define reg register     //为register这个关键字,创建一个新的名字
#define do_forever for(;;)    //死循环,起一个更加形象的名字
#define CASE break;case      //在写case语句的时候自动把break写上
//如果定义的stuff过长,可也分成几行写, 除了最后一行外,每行的后面都加上一个反斜杠(续行符)
#define DEBUG_PRINT printf("fine:%s  tline:%d\t  \
                          date:%s \ time:%s\n    \
                           ",__FILE__,__LINE__,  \
                             __DATE__,TIME__   )

这里需要注意的是,当定义的语句过长的时候,用  '\'来换行继续写,这里当续行符来使用

注意:在使用#define定义标识符的时候不要在最后加上分号(防止我们出错) 

二.#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或者定义宏(define macro)。

#define name(  parament-list  )    stuff

这里 parament-list  是一个由逗号隔开的符号表,它们可能出现在stuff中。

注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

举例:

#define SQUARE( x )  x*x
int a = 5;
printf("%d\n", SQUARE( a + 1));

这里看这一段代码,我们可能会说执行的结果为36,但是实际上它将打印11

我们为了得到36的正确结果,在定义宏的时候,为了避免出现的错误,我们一般是在宏定义表达式的两边加上一对括号

#define DOUBLE(x)   (  (x) + (x)  ) 

 注意:所有用于对数值表达式进行求值得宏定义都应该用这种加括号的方式避免在使用宏时由于参数中得操作符或操作符之间不可预料得相互作用。

宏的替换规则

1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换

2.替换文本随后被插入到程序中原来文本的位置。对于宏, 参数名被它们的值所替换

3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就是重复上述处理过程。

 注意:1 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归

            2 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

三.宏和函数的对比

1.宏通常被应用于简单的运算。(如下:)

#define MAX(a,b)    (  (a) > (b) ? (a) : (b)  )

 函数来完成上述代码比较两数大小也是可以的,但是于宏相比,宏更好

宏的优势:

1.宏比函数再程序的规模和速度上更胜一筹。

2.宏的参数是于类型无关的。(很重要)

3. 宏的参数可以出现类型,函数做不到。(很重要)

宏的参数出现了类型。 

#define MALLOC(num, type)  (type )malloc(num*sizeof(type))
MALLOC(10,int);


//上述的代码,相当于下面的代码
(int*)malloc(10 * sizeof(int));

 宏的劣势:

1.每次使用宏的时候,一份宏定义的代码将插入到程序中除非宏比较短否则可能大幅度增加程序的长度

2.宏是没办法调试的。

3.宏由类型无关,也就不够严谨。

4.宏可能会带来运算符优先的问题,导致程序容易出错。

宏和函数的对比
属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序长度会大幅度增加函数的代码只出现于一个地方;每次使用函数的时候,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,相对慢一些
操作符优先级宏参数的求值是再所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多加括号函数参数只在函数调用的时候求值一次,将结果值传递给函数。
带有副作用的参数参数可能被替换到宏中的多个位置,如果宏的参数被多次计算,带由副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果容易控制
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数类型不同,就需要不同的函数,即使它们的任务是不同的。
调试宏不方便调试函数可以逐语句调试
递归不能递归可以递归

四、#和##运算符

1.  #运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。

2.  #运算符所执行的操作可以理解为“字符串化”

int a = 10;
#define PRNT(n) printf("the value of "#n " is %d",n);

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

当定义一个比较两个数较大值的时候,类型不同的数据就得写不同的函数,例如:

int int_max(int x, int y)
{
	return x > y ? x : y;
}

float float_max(float x, float y)
{
	return x > y ? x : y;
}

这时候,用##来实现宏定义,这时候就非常简单了,下面的代码只要传不同的类型,就可以实现不同类型的函数定义。

#define GENERIC_MAX(type)         \
type type##_max(type x ,type y)   \
{                                 \
	return (x > y ? x : y);       \
}                                 \

五、条件编译

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

1.

#if  常量表达式

#endif

2.多个分支的条件编译

#if      常量表达式

#elif     常量表达式

#else

#endif

3.判断是否被定义

#if defined(symbol)

#ifdef

#if !defined(symbol)

#ifdef symbol

4.嵌套指令(举例)

#if defined(OS_UNIX)

               #ifdef OPTION1

                          unix_version_option1();

                #endif

                #ifdef OPTION2

                           unix_version_option2();

                #endif

#elif defined(OS_MSDOS)

                #ifdef OPTION2

                            msdos_version_option2();

                #endif

#endif

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

广联达 Linkworks办公OA Service.asmx接口存在信息泄露漏洞

漏洞描述 广联达科技股份有限公司以建设工程领域专业应用为核心基础支撑&#xff0c;提供一百余款基于“端云大数据”产品/服务&#xff0c;提供产业大数据、产业新金融等增值服务的数字建筑平台服务商。广联达OA存在信息泄露漏洞&#xff0c;由于某些接口没有鉴权&#xff0c…

干货:京东云GPU服务器性能NVIDIA A30/A10/V100/P40测评

京东云GPU服务器性能如何&#xff1f;京东云GPU云主机提供NVIDIA A30、A10、V100、P40等多款GPU卡&#xff0c;新推出的8卡A30规格&#xff0c;配备24G显存&#xff0c;支持NVLink&#xff0c;更好为深度学习的推理与训练、高性能计算应用提供GPU算力&#xff0c;京东云服务器网…

新个性化时尚解决方案!Prompt2Fashion:自动生成多风格、类型时尚图像数据集。

今天给大家介绍一种自动化生成时尚图像数据的方法Prompt2Fashion。 首先创建了一组描述&#xff0c;比如“适合婚礼的休闲风格服装”&#xff0c;然后用这些描述来指导计算机生成图像。具体来说&#xff0c;他们使用了大型语言模型来写出这些服装的描述&#xff0c;接着将这些描…

JavaSE——面向对象10:抽象类、接口

目录 一、抽象类 (一)抽象类的引出 (二)抽象类基本介绍 (三)注意事项和使用细节 (四)抽象类的最佳实践——模板设计模式 二、接口 (一)接口快速入门 (二)基本介绍 (三)注意事项与使用细节 (四)接口VS继承 (五)接口的多态性 1.多态参数 2.多态数组 3.接口存在多态…

文件上传之%00截断(00截断)以及pikachu靶场

pikachu的文件上传和upload-lab的文件上传 目录 mime type类型 getimagesize 第12关%00截断&#xff0c; 第13关0x00截断 差不多了&#xff0c;今天先学文件上传白名单&#xff0c;在网上看了资料&#xff0c;差不多看懂了&#xff0c;但是还有几个地方需要实验一下&#…

高性能架构—存储高性能

1 &#x1f4ca;关系型数据库 存储技术飞速发展&#xff0c;关系型数据的ACID特性以及强大的SQL查询让其成为各种业务系统的关键和核心存储系统。 很多场景下的高性能设计最核心的就是关系型数据库的设计&#xff0c;很多数据库厂商再优化和提升单个数据库服务器的性能方面做了…

统一 SASE 架构中的网络和安全融合

网络威胁情报技术的进步 传统的网络边界一片混乱&#xff0c;剩下的只是无人管理的设备、分散在私有云和公共云中的资产、无法读取的应用程序流量泛滥&#xff0c;混合工作结构正在给现有网络的功能带来压力。 更重要的是&#xff0c;这些问题早在生成式人工智能和大型语言模…

【C++11】新特性

前言&#xff1a; C11 是C编程语言的一个重要版本&#xff0c;于2011年发布。它带来了数量可观的变化&#xff0c;包含约 140 个新特性&#xff0c;以及对 C03 标准中约600个缺陷的修正&#xff0c;更像是从 C98/03 中孕育出的新语言 列表初始化 C11 中的列表初始化&#xff0…

智能手表(Smart Watch)项目

文章目录 前言一、智能手表&#xff08;Smart Watch&#xff09;简介二、系统组成三、软件框架四、IAP_F411 App4.1 MDK工程结构4.2 设计思路 五、Smart Watch App5.1 MDK工程结构5.2 片上外设5.3 板载驱动BSP5.4 硬件访问机制-HWDataAccess5.4.1 LVGL仿真和MDK工程的互相移植5…

免费版U盘数据恢复软件大揭秘,拯救你的重要数据

我们的生活和工作越来越离不开各种存储设备&#xff0c;其中优盘因其小巧便携、方便使用的特点&#xff0c;成为了我们存储和传输数据的重要工具之一。为了防止你像我一样会遇到数据丢失抓狂的情况&#xff0c;我分享几款u盘数据恢复软件免费版工具来即时补救。 1.福昕U盘数据…

Oracle中TRUNC()函数详解

文章目录 前言一、TRUNC函数的语法二、主要用途三、测试用例总结 前言 在Oracle中&#xff0c;TRUNC函数用于截取或截断日期、时间或数值表达式的部分。它返回一个日期、时间或数值的截断版本&#xff0c;根据提供的格式进行截取。 一、TRUNC函数的语法 TRUNC(date) TRUNC(d…

鸿蒙harmonyos next flutter混合开发之开发plugin(获取操作系统版本号)

创建Plugin为my_plugin flutter create --org com.example --templateplugin --platformsandroid,ios,ohos my_plugin 创建Application为my_application flutter create --org com.example my_application flutter_application引用flutter_plugin&#xff0c;在pubspec.yam…

一键生成PPT的AI工具-Kimi!

一键生成PPT的AI工具-Kimi&#xff01; 前言介绍Kimi为什么选择Kimi如何使用Kimi在线编辑PPT下载生成的PPT自己编辑 结语 &#x1f600;大家好&#xff01;我是向阳&#x1f31e;&#xff0c;一个想成为优秀全栈开发工程师的有志青年&#xff01; &#x1f4d4;今天不来讨论前后…

Jenkins Pipline流水线

提到 CI 工具&#xff0c;首先想到的就是“CI 界”的大佬--]enkjns,虽然在云原生爆发的年代,蹦出来了很多云原生的 CI 工具,但是都不足以撼动 Jenkins 的地位。在企业中对于持续集成、持续部署的需求非常多,并且也会经常有-些比较复杂的需求,此时新生的 CI 工具不足以支撑这些很…

前缀和算法详解

对于查询区间和的问题&#xff0c;可以预处理出来一个前缀和数组 dp&#xff0c;数组中存储的是从下标 0 的位置到当前位置的区间和&#xff0c;这样只需要通过前缀和数组就可以快速的求出指定区间的和了&#xff0c;例如求 l ~ r 区间的和&#xff0c;就可以之间使用 dp[l - 1…

鸿蒙OpenHarmony

开源鸿蒙系统编译指南 Ubuntu编译环境配置第一步&#xff1a;Shell 改 Bash第二步&#xff1a;安装Git和安装pip3工具第三步&#xff1a;远程仓配置第四步&#xff1a;拉取代码第五步&#xff1a;安装编译环境第六步&#xff1a;本地编译源码 Windows开发环境配置第一步&#x…

巧用armbian定时任务控制开发板LED的亮灭

新买了个瑞莎 3E 开发板,号称最小SBC,到了之后简直玩开了花,各种折腾后 安装好armbian系统,各种调优。 不太满意的地方:由于板子太小的原因,导致两个USBTYPEC的接口距离很近,所以买的OTG转接口如果有点宽的话 会显得特别拥挤。 还有就是每天晚上天黑了之后,卧室…

Uniapp API

1.uni.showToast 显示消息提示框 unishowToast({ obj参数 }) 2.uni.showLoading 显示 loading 提示框, 需主动调用 uni.hideLoading 才能关闭提示框。 3.uni.showModal 显示模态弹窗&#xff0c;可以只有一个确定按钮&#xff0c;也可以同时有确定和取消按钮。类似于一个A…

躺平成长:微信小程序运营日记第二天

在进行属于生活的开源之后&#xff0c;自己更加感受到自己存在的渺茫&#xff0c;同时更加开始深刻领会&#xff0c;开源的重要性&#xff0c;在开源&#xff0c;开放&#xff0c;创造&#xff0c;再创新的思维模式下&#xff0c;不发布八部金刚功相关的训练视频&#xff0c;自…