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.rhkb.cn/news/440053.html

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

相关文章

Spring Boot 整合 Keycloak

1、概览 本文将带你了解如何设置 Keycloak 服务器&#xff0c;以及如何使用 Spring Security OAuth2.0 将 Spring Boot 应用连接到 Keycloak 服务器。 2、Keycloak 是什么&#xff1f; Keycloak 是针对现代应用和服务的开源身份和访问管理解决方案。 Keycloak 提供了诸如单…

Unity MVC框架演示 1-1 理论分析

本文仅作学习笔记分享与交流&#xff0c;不做任何商业用途&#xff0c;该课程资源来源于唐老狮 1.一般的图解MVC 什么是MVC我就不说了&#xff0c;老生常谈&#xff0c;网上有大量的介绍&#xff0c;想看看这三层都起到什么职责&#xff1f;那就直接上图吧 2.我举一个栗子 我有…

人工智能新闻和发展 (24001)- By 10/4/2024

1. 谷歌增强了搜索中的人工智能&#xff0c;允许对图像进行语音提问。 Google adding AI to answer voiced questions about images | AP NewsGoogle is pumping more artificial intelligence into its search engine. New features will enable people to voice questions a…

15分钟学 Python 第39天:Python 爬虫入门(五)

Day 39&#xff1a;Python 爬虫入门数据存储概述 在进行网页爬虫时&#xff0c;抓取到的数据需要存储以供后续分析和使用。常见的存储方式包括但不限于&#xff1a; 文件存储&#xff08;如文本文件、CSV、JSON&#xff09;数据库存储&#xff08;如SQLite、MySQL、MongoDB&a…

无神论文解读之ControlNet:Adding Conditional Control to Text-to-Image Diffusion Models

一、什么是ControlNet ControlNet是一种能够控制模型生成内容的方法&#xff0c;能够对文生图等模型添加限制信息&#xff08;边缘、深度图、法向量图、姿势点图等&#xff09;&#xff0c;在当今生成比较火的时代很流行。 这种方法使得能够直接提供空间信息控制图片以更细粒…

PCL 1.8.1 + VTK 1.8.0 + QT5.14.2+ VS2017 环境搭建

先看看效果: PCL 1.8.1下载安装: Tags PointCloudLibrary/pcl GitHub 安装完成后: 如果VTK想重新编译的,可以看我的这篇博客:

Spring14——案例:利用AOP环绕通知计算业务层接口执行效率

前面介绍了这么多种通知类型&#xff0c;具体该选哪一种呢? 我们可以通过一些案例加深下对通知类型的学习。 34-案例&#xff1a;利用AOP环绕通知计算业务层接口执行效率 需求分析 这个需求也比较简单&#xff0c;前面我们在介绍AOP的时候已经演示过: 需求:任意业务层接口…

冯诺依曼体系|操作系统

目录 一、硬件&#xff1a;冯诺依曼体系 1.冯诺依曼体系结构 2.冯诺依曼体系结构组成 3.内存的重要性 &#xff08;1&#xff09;提升运行速度 &#xff08;2&#xff09;提升运行效率 二、软件&#xff1a;操作系统 1.什么是操作系统 &#xff08;1&#xff09;内部理…

【GeekBand】C++设计模式笔记6_Decorator_装饰模式

1. “单一职责”模式 在软件组件的设计中&#xff0c;如果责任划分的不清晰&#xff0c;使用继承得到的结果往往是随着需求的变化&#xff0c;子类急剧膨胀&#xff0c;同时充斥着重复代码&#xff0c;这时候的关键是划清责任。典型模式 DecoratorBridge 2. Decorator 装饰模…

地理空间数据存储与处理:MySQL空间数据类型的优化与应用!

在 MySQL 数据库中&#xff0c;空间数据类型用于存储和处理地理空间数据。这些数据类型允许我们在开发时可在数据库中存储和操作地理位置、几何形状和地理空间关系等信息。 一、什么是空间数据类型 MySQL 中的空间数据类型主要包括以下几种&#xff1a; GEOMETRY&#xff1a…

iMazing只能苹果电脑吗 Win和Mac上的iMazing功能有区别吗

在当今数字时代&#xff0c;管理和备份手机数据变得越来越重要。无论是转移照片、备份短信&#xff0c;还是管理应用程序&#xff0c;一个强大的工具可以大大简化这些操作。iMazing作为一款备受好评的iOS设备管理软件&#xff0c;已经成为许多用户的选择。但是&#xff0c;许多…

SpringBoot+ElasticSearch7.12.1+Kibana7.12.1简单使用

案例简介 本案例是把日志数据保存到Elasticsearch的索引中&#xff0c;并通过Kibana图形化界面的开发工具给查询出来添加的日志数据&#xff0c;完成从0到1的简单使用 ElasticSearch职责用法简介 ElasticSearch用在哪 ElasticSearch在我这个案例中&#xff0c;不是用来缓解增…

STM32编码器接口解析及抗噪声措施探讨

1. 引言 在现代控制系统中&#xff0c;编码器扮演着非常重要的角色。它就像一个精密的测量工具&#xff0c;可以告诉我们机械部件的位置和运动状态。在STM32微控制器中&#xff0c;编码器接口可以轻松地与各种编码器连接&#xff0c;实现精确的控制。我将在这里探讨STM32编码器…

图文深入理解Oracle Network配置管理(一)

List item 本篇图文深入介绍Oracle Network配置管理。 Oracle Network概述 Oracle Net 服务 Oracle Net 监听程序 <oracle_home>/network/admin/listener.ora <oracle_home>/network/admin/sqlnet.ora建立网络连接 要建立客户机或中间层连接&#xff0c;Oracle…

sublime配置(竞赛向)

我也想要有jiangly一样的sublime 先决条件 首先&#xff0c;到官网上下载最新的sublime4&#xff0c;然后在mingw官网上下载最新的mingw64 mingw64官网&#xff1a;左边菜单栏点击dowloads,然后选择MinGW-W64-builds(可能会有点慢)——然后有时候会变成选LLVM-minGW,接着选择…

人工智能专业就业方向与前景

随着产业结构升级的持续推进&#xff0c;未来行业领域对于人工智能专业人才的需求量会逐渐增加&#xff0c;一部分高校也开始陆续在本科阶段开设人工智能专业&#xff0c;以缓解人工智能领域人才缺口较大的问题。下面是小编整理的人工智能专业就业方向与前景&#xff0c;欢迎阅…

数据结构阶段测试2的一点小补充

数据结构阶段测试2的一点小补充 1.已知⼩根堆为8,15,10,21,34,16,12&#xff0c;删除关键字8之后需重建堆&#xff0c;最后的叶⼦ 节点为() A. 34 B. 21 C. 16 D. 12 解题思路 向下调整算法删除堆顶元素 &#x1f4a1; 答案&#xff1a;C 删除堆顶元素的思路&#xff1a; …

详解Java中的堆内存

详解Java中的堆内存 堆是JVM运行数据区中的一块内存空间&#xff0c;它是线程共享的一块区域&#xff08;注意了&#xff01;&#xff01;&#xff01;&#xff09;&#xff0c;主要用来保存数组和对象实例等&#xff08;其实对象有时候是不在堆中进行分配的&#xff0c;想要了…

python-pptx 中 placeholder 和 shape 有什么区别?

在 python-pptx 库中&#xff0c;placeholder 和 shape 是两个核心概念。虽然它们看起来相似&#xff0c;但在功能和作用上存在显著的区别。为了更好地理解这两个概念&#xff0c;我们可以通过它们的定义、使用场景以及实际代码示例来剖析其差异。 Python-pptx 的官网链接&…

【微服务】服务注册与发现 - Eureka(day3)

CAP理论 P是分区容错性。简单来说&#xff0c;分区容错性表示分布式服务中一个节点挂掉了&#xff0c;并不影响其他节点对外提供服务。也就是一台服务器出错了&#xff0c;仍然可以对外进行响应&#xff0c;不会因为某一台服务器出错而导致所有的请求都无法响应。综上所述&…