【C语言】预处理详解(上)

news2024/9/28 3:30:19

文章目录

  • 前言
  • 1. 预定义符号
  • 2. #define 定义常量
  • 3. #define定义宏
  • 4. 带有副作用的宏参数
  • 5. 宏替换的规则

前言

在讲解编译和链接的知识点中,我提到过翻译环境中主要由编译和链接两大部分所组成。
其中,编译又包括了预处理、编译和汇编。当时,我只是粗略的讲解预处理的过程,那么本文将会带着大家去领略预处理的各项操作。还有一些预处理的奇葩操作。

哈哈

1. 预定义符号

C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理阶段就被直接替换掉了。

预处理符号:

__FILE__	//意思:进行编译的源文件
__LINE__	//意思:显示该代码语句所在的行数
__DATE__	//意思:文件被编译的日期
__TIME__	//意思:文件被编译的时间
__STDC__	//意思:如果该C编译器完全遵顼ANSI C的标准,则其值为0。否则就是非定义。

演示案例:
预定义符号的演示

2. #define 定义常量

基本语法:

#define name stuff

举个例子:

//#define 定义常量
#define MAX 10000
#define reg register //为register关键字,创建一个简洁的名字。
#define do_forever for(;;) //用更形象的符号来替换一种实现。
#define CASE break;case	//在写case语句的时候自动把break写上。

// 如果#define定义的stuff过长,可以分成几行来写,除了最后一行外,
//每行的后面都加上一个反斜杠\(续航符)
#define DEBUG_PRINT printf("file:%s\tline:%d\t\
							date:%s\ttime:%s\n", \
							__FILE__,__LINE__ \
							__DATE__,__TIME__)

思考一个问题:#define定义标识符时,要不要在最后加上;号?
比如:

#define MAX 1000;
#define MAX 1000

我的建议是不要加;号。你别看上面的代码可以正常的运行,但是针对某些特定的应用场景,可能会引发一些难以察觉的错误。

比如下面的例子:

#define MAX 1000;
int main()
{
	int max = 0;
	int condition = 1;
	if(condition)
		max = MAX;
	else
		max = 0;
}

上述代码直接运行会报错,而错误的原因是悬空else的问题。因为MAX本身就拥有了一个;号,而我们在代码写的分号会被是作为一个空语句,也就是说,if之后else之前由两条语句。但是如果要在if后里面写多条语句就得有大括号括起来。否则,就会报语法错误。

3. #define定义宏

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

下面时宏的声明方式:

#define name(parament-list) stuff

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

注意:参数列表的左括号一定要与name紧邻。如果两者之间有空格的话,参数列表就会被编译器解释为stuff中的一部分。

举例:

#define SQUARE(x) x*x

这个宏接受了一个参数x。如果在上述声明过后,把SQUARE(5);置于程序中,与编译器就会用5*5这个表达式来替换SQUARE(5)

但是,我们写的这一个宏有潜在的隐患。为什么这么说呢?
请看下面的例子:

#include<stdio.h>
#define SQUARE(x) x*x
int main()
{
	int a = 5;
	printf("%d\n",SQUARE(a + 1));
	return 0;
}

答案
哎呦,这里的答案不是36吗,为什么这里会打印出11?

其实,这是直接替换文本的弊端,它是直接替换的。也就是说,先前的printf里的参数变为了
printf("%d",a+1*a+1);

这样说的话就比较清晰了,有替换产生的表达式并没有按照我们的预期顺序进行运算求值。

那我们该怎么修改上述的代码,使其能够得到正确的答案呢?
方法很简单,就是加括号,改变运算符的优先级。

#include<stdio.h>
#define SQUARE(x) (x)*(x)
int main()
{
	int a = 5;
	printf("%d\n",SQUARE(a + 1));
	return 0;
}

这样就达到了预期的效果了。

为了巩固大家加括号的意识,我再举一个例子。
这里还有一个宏定义:

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

在定义中我们为了避免预算符之间的优先级和结合性,我们给其添上了括号,但是这个宏仍然会出现问题。

int a = 5;
printf("%d",10*DOUBLE(a));

这个会打印出什么结果呢?看上去好像是100,但事实上打印的值确是55。

我们发现替换之后:

printf("%d",10*(5)+(5));

乘法运算的优先级高于加法,所以就会出现55.
为了解决这个问题,我们可以这样写:

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

以上两个例子告诉我们,在写宏时,一定不要节省你的括号。

4. 带有副作用的宏参数

什么叫带有副作用?
请大家看下面几段代码:

int a = 5;
int b = 0; 
b = a + 1; //方案1
b = a++; //方案2

在方案1中,a的值仍然为5 。但在方案2中,a的值就变为6了。相信讲到这里你已经有点感觉了。

所谓带有副作用其实就是以修改参与运算变量的值为代价,实现我们要到达的效果。

当宏参数在宏的定义中出现超过一次的情况,如果参数带有副作用,那么你在使用这个宏的时候就有可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性的效果。

这里我们设置一段代码来证明带有副作用的宏参数所引发的问题:

#define MAX(a,b) ((a>b)?(a):(b))
...
x = 5;
y = 8;
z = MAX(x++,y++);
printf("x=%d,y=%d,z=%d\n",x,y,z);//输出的结果是什么?

输出的结果为:x=6,y=10,z=9

5. 宏替换的规则

在程序中扩展使用#define定义符号和宏,需要涉及几个步骤:

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果有,它们首先被替换。
  2. 替换后的文本会被插入到程序中原来文本的位置。对于宏来说,参数名被它们的值所替代。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果有则重复上述步骤。

注意:

  • 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏来说,不能出现递归。
  • 当预处理器搜索#define定义的符号的时候,字符串常量的内容不在搜索范围

限于篇幅的原因,本文就像先讲到这里。后续的内容都在预处理详解(下)中,欢迎大家指点一二。💖💖💖

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

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

相关文章

【Windows系统开机后识别不到屏幕适配的分辨率导致屏幕无法点亮的解决办法】

问题原因分析&#xff1a; 屏幕驱动板出现故障&#xff0c;驱动出现缺失&#xff0c;未对主板系统进行适配兼容。使用的屏幕分辨率非常小众&#xff0c;系统中没有这个分辨率&#xff0c;识别不到屏幕适配的分辨率后导致屏幕无法点亮。 解决方法&#xff1a; 找主板厂家增加…

CVE-2022-33891漏洞复现

简介 Spark 是用于大规模数据处理的统一分析引擎。它提供了 Scala、Java、Python 和 R 中的高级 API&#xff0c;以及支持用于数据分析的通用计算图的优化引擎。它还支持一组丰富的高级工具&#xff0c;包括用于 SQL 和 DataFrames 的 Spark SQL、用于 Pandas 工作负载的 Spar…

【每日刷题】Day94

【每日刷题】Day94 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 33. 搜索旋转排序数组 - 力扣&#xff08;LeetCode&#xff09; 2. 1290. 二进制链表转整数 - 力扣…

科普文:微服务之万字详解分布式事务原理、协议及其框架

一、分布式事务 首先奉上一张关于事务的相关概念图&#xff0c;给大家做个总览&#xff1a; 1.1 名词解释 事务&#xff1a;事务是由一组操作构成的可靠的独立的工作单元&#xff0c;事务具备ACID的特性&#xff0c;即原子性、一致性、隔离性和持久性。本地事务&#xff1a;当…

树莓派4B部署及测试llamafile

llamafile项目简介 很多初学者学习大语言模型的时候,都会被模型文件中一大堆复杂的python文件或者cuda配置劝退,为了方便更多的零基础的初学者体验大语言模型,llamafile 提出了单文件运行大模型的方案。 GitHub - Mozilla-Ocho/llamafile: Distribute and run LLMs with a…

网络原理(1)——基本概念

1. 网络互连 随着时代的发展&#xff0c;越来越需要计算机之间相互通信&#xff0c;共享软件和数据&#xff0c;以多个计算机协同工作来完成业务&#xff0c;就有了网络互连 网络互连&#xff1a;将多台计算机连接在一起&#xff0c;完成数据共享 数据共享本质是网络数据传输…

中空板式陶瓷膜的高可靠性

中空板式陶瓷膜是一种先进的液固分离材料&#xff0c;具有诸多优点和广泛的应用领域。以下是对中空板式陶瓷膜的详细介绍&#xff1a; 一、产品特点 物理特性优越&#xff1a;中空板式陶瓷膜通常采用刚玉等无机材料为原材料&#xff0c;经过高温烧制而成&#xff0c;具有高强度…

数据结构实验:树和二叉树(附c++源码:实现树有关算法)

目录 一、实验目的 二、问题分析及数据结构设计 三、算法设计&#xff08;伪代码表示&#xff09; 1. 输入字符序列 创建二叉链表 2. 递归前序遍历 3. 递归中序遍历 4. 递归后序遍历 5. 非递归前序遍历 6. 非递归中序遍历 7. 非递归后序遍历 8. 层次遍历 9. 求二叉…

阿里云镜像站,提供了各种第三方镜像地址

阿里云提供了各项镜像缓存地址&#xff0c;对于很多国外服务的地址&#xff0c;通过阿里云缓存的地址去下载&#xff0c;速度会非常快。 如下&#xff0c;打开阿里云官方网站&#xff1a; 进入“镜像站”&#xff0c;如下图所示&#xff1a; 有我们常用的 npm、maven、操作系统…

武汉流星汇聚:互联网+跨境购物新风尚,消费者深度依赖跨境电商

在21世纪的数字时代&#xff0c;跨境电商平台以其独特的魅力&#xff0c;正逐步成为连接全球消费者与优质商品的桥梁。随着消费者对优质产品需求的日益增长、全球互联网使用量的不断扩大、跨境物流技术的飞速进步以及全球供应链能力的显著提升&#xff0c;跨境电商平台不仅为消…

uniapp——列表选择样式

案例 代码 <view class"list"><block v-for"(item,index) in 8" :key"index"><view class"item" click"choosePackage(item)" :class"{active:item current}"><view class"i_money&q…

【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro

看到这句话的时候证明&#xff1a;此刻你我都在努力 加油陌生人 前言 今天给大家带来一篇有关Java顺序表和链表的文章&#xff0c;顺序表和链表我之前的专栏也是写过的&#xff0c;是用C语言实现的&#xff0c;也是模仿实现了顺序表和链表里的方法了。 下面是传送门&#xff…

新一代AI技术的发展

人工智能技术正处于迈向全新阶段的关键转折点&#xff0c;从传统的NLP(自然语言处理)迅速迈向更开 放、更通用、多模态的AGI(通用人工智能),AGI的兴起为各行业带来了前所未有的机遇。AGI突破了传 统AI的局限&#xff0c;具备跨领域的广泛应用能力和自主学习能力&#xff0c;在自…

CTFHUB | web进阶 | PHP | Bypass disable_function | PHP-FPM

开启题目 查看源码&#xff0c;发现可以蚁剑连接 连接成功发现无任何发现&#xff0c;所以我们使用 Fastcgi/PHP-FPM 插件&#xff0c;配置如下 刷新目录发现插件上传了一个 php 文件&#xff0c;复制文件名拼接到后面再次连接 发现直接进入终端了&#xff0c;最后发现了 flag

MySQL数据库基础:增删查改

&#x1f48e;所属专栏&#xff1a; MySQL &#x1f48e;1. 常用数据类型 &#x1f48e;1.1 数值类型 数值类型可以指定无符号类型&#xff0c;默认为有符号类型&#xff0c;例如身高体重这种&#xff0c;只可能是正数的&#xff0c;可以指定为无符号 CREATE TABLE example (…

Linux PCI和PCIe总线

1 PCIe中断 - PCI/PCIe设备中断都是level触发&#xff0c;并且请求信号为低电平有效 - PCI总线一般只有INTA#到INTD#的4个中断引脚&#xff0c;所以PCI多功能设备的func一般不会超过4个&#xff0c;但是共享中断除外 2 IOMMU 2.1 ARM SMMU v2 Refer to my blog ARM SMMU v2. 2.…

糟糕界面集锦-控件篇 01

iarchitect 整理&#xff0c;bucher 译 在图形界面中&#xff0c;控件就是程序与用户之间沟通的桥梁&#xff0c;而这座桥梁的好坏则取决于如下两个方面&#xff1a; 控件是否符合需求控件之间是否风格一致 《Visual Basic Programmers Journal 101 Tech Tips for VB Develop…

网络工具(Netcat、iPerf)

目录 1. Netcat2. iPerf 1. Netcat Netcat 是一款简单的 Unix 工具&#xff0c;常用于测试 UDP 和 TCP 连接。 https://www.cnblogs.com/yywf/p/18154209 https://eternallybored.org/misc/netcat/ https://nmap.org/download.html 创建UDP监听端 nc -u -l localPort 创建UDP…

并行程序设计基础——Hello world

目录 一、Fortran 90 MPI实现 二、C MPI实现 三、MPI程序的一些惯例 四、小结 相信许多编程初学者的入门程序都是“Hello World”&#xff0c;我们同样来编写MPI的第一个程序“Hello World”。 一、Fortran 90 MPI实现 我们先给出代码&#xff0c;然后进行代码分析。 pro…

零基础入门汇编语言(第四版王爽)~第1章基础知识

文章目录 前言1.1 机器语言1.2 汇编语言的产生1.3 汇编语言的组成1.4 存储器1.5 指令和数据1.6 存储单元1.7 CPU对存储器的读写1.8 地址总线1.9 数据总线1.10 控制总线1.1~1.10小结检测点1.11.11 内存地址空间概述1.12 主板1.13接口卡1.14 各类存储器芯片1.15 内存地址空间 前言…