【C语言 |预处理指令】预处理指令详解(包括编译与链接)

目录

一、编译与链接

 1.翻译环境

          -预处理

          -编译

          -汇编

          -链接

2.执行环境

二、预定义符号

三、#define定义常量

四、#define定义宏

五、带有副作用的宏参数

六、宏替换的规则

七、 宏函数的对比

八、#和##

1.#运算符

2.##运算符

九、命名约定

十、#undef

十一、 命令行定义

十二、 条件编译

十三、 头文件的包含

1.本地头文件包含

2.库文件包含

十四、 其他预处理指令


一、编译与链接

在ANSI C的任何⼀种实现中,存在两个不同的环境

第一种呢就是翻译环境,顾名思义就是将源代码被转换为可执行的二进制指令

第二种呢就是执行环境,可用于实际执行代码,并且输出结果

然后我们再来说翻译环境,是如何将一段代码转换为可执行的二进制指令的呢

其实编译这一部分又分为了预处理(预编译),编译,汇编 

 在一个程序中可能会有多个.c文件,这些文件会单独的经过编译处理生成对应的目标文件

Windows环境下生成的目标文件后缀为.obj,Linux环境下生成的目标文件为.o

多个目标文件跟链接库一起经过链接器的处理最终生成可执行程序

链接库呢,它是指运行时库(支持程序运行的基本函数集合)第三方库

知道了上面的操作,我们就可以展开,成为了以下这个过程

 1.翻译环境

          -预处理

在预处理阶段,源文件和头文件会被处理为.i为后缀的文件,处理规则如下

  • 将所有的#define删除,并且展开所有宏定义
  • 处理所有的条件编译指令
  • 处理#include 预编译指令,将包含的头文件的内容插⼊到该预编译指令的位置
  • 删除所有的注释
  • 添加行号文件名标识, ⽅便后续编译器生成调试信息等
  • 或保留所有的#pragma的编译器指令,编译器后续会使用
经过预处理后的.i文件中不再包含宏定义,因为宏已经被展开并且包含的头文件都被插入到.i文件中

          -编译

编译过程就是将预处理后的文件进行一系列的:词法分析、语法分析、语义分析及优化,⽣成相应的汇编代码⽂件
  1. 将源代码程序被输⼊扫描器进行词法分析,把代码中的字符分割成⼀系列 的记号(关键字、标识符、字⾯量、特殊字符等)
  2. 接下来语法分析器,将对扫描产⽣的记号进⾏语法分析,从而产生语法树。这些语法树是以表达式为节点的树
  3. 语义分析器来完成语义分析,即对表达式的语法层⾯分析。静态语义分析通常包括声明和类型的匹配,类型的转换等。这个阶段会报告错误的语法信息。

          -汇编

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

          -链接

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

2.执行环境

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

二、预定义符号

C语⾔设置了⼀些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的,所以运行速度更快
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义


三、#define定义常量

基本语法 

# define name stuff

当然所定义的类型没有限制,可以为了定义值,可以为了替换复杂名字,也可以为了省事,下面这几种都是正确的定义方法: 

#define MAX 1000#define float f //为 float这个关键字,创建⼀个简短的名字#define forever for(;;) //⽤更形象的符号来替换⼀种实现(死循环)#define CASE break;case //在写case语句的时候⾃动把 break写上#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ );

 /如果定义的过长,可以分成几行写,除了最后一行外,后⾯都加⼀个反斜杠  \  (续行符)

 

 所以宏的定义可以各式各样,给了我们很大的自由度,使我们能尽情去发挥自己想象

那么还有一个问题在定义定义的标识符的时候需不需要加;呢,答案是否定的,就比如说

#define PR printf("hehe");int main()
{PR;        //加了分号就相当于 printf("hehe");;  容易发生错误return 0;
}

为了避免上述的这个错误,我们定义的标识符的时候不需要加;


四、#define定义宏

#define 机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)或定义宏(define macro)
 #define name( list ) stuff

举个例子来说明

#define SUPP( x ) x * xint main()
{SUPP(2,3);return 0;
}

SUPP就是我们定义的一个宏,将宏置于函数内部(等预处理的时候,会自动替换成表达式 x * x)

但同时会存在一些问题

#define SUPP( x ) x * xint main()
{SUPP(2);  // 2 * 2  == 4SUPP(2+1);  // 2 + 1 * 2 + 1  == 510 * SUPP(5+2);  //10 * 5 + 2 * 5 + 2  == 62return 0;
}

因为被定义的宏是预处理阶段所进行的,在预处理的时候直接替换函数中的表达式,所以难免会有许多操作符,优先级之类的问题,这个解决问题的方法就是在表达式加上对括号就解决了

#define SUPP( x) ( ( x ) + ( x ) )
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

五、带有副作用的宏参数

副作⽤就是表达式求值的时候出现的永久性效果,就好比说
a = 1;//a赋值的同时自己的值也改变了b = a++;  // a = 2,b = 1;a+1;//不带副作⽤
a++;//带有副作⽤

拿下面这个例子来举例子

#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 = 5,y = 8;MAX(x++,y++) 替换为 ((x++)>(y++)?(x++)(y++))x=6   y=9         y=105 > 8假,执行y++

六、宏替换的规则

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

 


七、 宏函数的对比

宏通常被应用于执行简单的运算,比如在两个数中找出较⼤的⼀个时,更有优势⼀些
#define MAX(a, b) ((a)>(b)?(a):(b))//定义宏和函数的两种方式int Max(a,b)
{return ((a)>(b)?(a):(b));
}

 我们从以下几个方面来分析宏 和 函数 的优缺点:

  1. 代码长度:#define所定义的宏,每次使用的时候都会被插入到程序中,除了特别小的宏以外,程序的长度会大幅度增长;而函数的代码只出现一个地方,调用都用同一份
  2. 执行速度:#define所定义的宏更快;二函数存在着调用和返回等额外的步骤速度会慢一些
  3. 操作符优先级:#define所定义的宏求值是在上下文的环境中,结果不可预测 会存在着很多的问题;而函数的参数只在函数调用时候将结果传给函数,表达式课预测
  4. 带有副作用的参数:#define所定义的宏参数可能会被替换多个位置,多次被计算,对值有着不可预测的结果;函数只在传参的时候求值易控制
  5. 参数类型:#define所定义的宏与类型无关,只要操作是合法的,可以适用于任何参数类型;函数的参数是与类型有关,如果类型不同,所需的函数也不同
  6. 调试:宏是不能调试的;函数是可以逐句逐条调试
  7. 递归:宏是不能递归的;函数是可以递归的

八、#和##

1.#运算符

先给大家补充一个知识点,字符串中包含的字符串两个会合成一个字符串

printf("haha""hehe");//两个输出的结果相同printf("hahahehe");
#运算符将宏的⼀个参数转换为字符串字面量。仅允许出现在带参数的宏的替换列表中“字符串化
#define PRI(n) printf("the value of "#n " is %d", n);int main()
{PrT(6); //printf("the value of "#n " is %d", n);return 0;
}

结果为the value of n is 6

不难发现#n,将转换成了一个字符串


2.##运算符

把位于它两边的符号合成⼀个符号,它允许宏定义从分离的文本片段创建标识符。
## 被称为记号粘合
大家想想看如果要实现一个结果,不同的数据类型就得写不同的函数
int int_max(int x, int y)
{return x>y?x:y;
}
float float_max(float x, float y)
{return x>yx:y;
}

那如果使用##,一切都会变的很简单

#define GENERIC_MAX(type)		    \
type type##_max(type x,typey)		\
{									\return (x>y?x:y);				\
}									\
	GENERIC_MAX(int);   //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名GENERIC_MAX(float); //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名

在预处理阶段,预处理中的所有type全部被替换 


九、命名约定

把宏名全部⼤写   ,函数名不要全部⼤写

十、#undef

这条指令用于移除⼀个宏定义
#undef NAME
//如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。

可以看到 定义了一个MAX,正常打印完毕以后,#undef 移除这个宏定义,再次打印MAX就会报错


十一、 命令行定义

许多C 的编译器提供了⼀种能力,允许在命令行中定义符号。用于启动编译过程

十二、 条件编译

在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的。因为我们有条件编译指令
#if 常量表达式//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__//..
#endif#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

十三、 头文件的包含

1.本地头文件包含

#include "filename"

拿双引号引用

查找策略:先在源⽂件所在⽬录下查找,如果该头文件未找到,编译器就像查找库函数头文件⼀样在标准位置查找头文件,如果找不到就提示编译错误。

2.库文件包含

#include <filename.h>

拿单尖括号引用

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
也可以用双引号引用库文件,但是查找的效率就低些,这样也不容易区分是库文件还是本地文件
当一个文件中包含多个头文件,重复的头文会被在预处理的时候多次替换,大大降低了效率,所以我们在声明一个头文件的前面不妨可以加上这段代码
#ifndef __TEST_H__  \\当未定义这个头文件时才会执行下面
#define __TEST_H__.....#endif
#pragma once

这两种都可以避免头文件重复引进


十四、 其他预处理指令

# error
# pragma
# line
...
后面再给大家介绍

希望对你有帮助 

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

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

相关文章

IDEA本地将镜像推送到coding制品仓库

创建制品仓库 假设仓库名称为docker 在IDEA 添加Docker 注册表 IDEA必须先安装docker插件 地址 用户名和密码就是coding的登录名和密码服务器 最好本地安装docker桌面版&#xff0c;更容易操作 测试连接成功 推送镜像到coding的docker制品仓库 选中某个镜像 鼠标右键 注册表…

四.RocketMQ的几种消息发送方式应用

RocketMQ的几种消息发送方式应用 一&#xff1a;普通消息1&#xff09;发送同步消息2&#xff09;发送异步消息3&#xff09;单向发送消息4&#xff09;消费消息-负载均衡模式5&#xff09;消费消息-广播模式 二&#xff1a;顺序消息1.顺序消息指的是:严格按照消息的发送顺序进…

服务器数据恢复—ESXi无法识别数据存储和VMFS文件系统如何恢复数据?

服务器数据恢复环境&#xff1a; 一台某品牌服务器&#xff0c;通过FreeNAS来做iSCSI&#xff0c;然后使用两台同品牌服务器做ESXi虚拟化系统。 FreeNAS层为UFS2文件系统&#xff0c;使用整个存储建一个稀疏模式的文件&#xff0c;挂载到ESXi虚拟化系统。ESXi虚拟化系统中有3台…

react实现时钟翻牌效果

需求&#xff1a;随着数字的变动要求有时钟翻动动效 问题&#xff1a;只在加载时有动效 解决方案&#xff1a;通过判断数字改变&#xff08;这里通过新旧数值变动来判断&#xff0c;不贴代码啦&#xff09;&#xff0c;每次变动的时候手动把animationIterationCount设置为inf…

【blog项目】layui与jquery冲突导致鼠标悬停事件失效、如何调用layui.use()作用域里的方法

blog项目前台展示——查询数据库中的文章类型并展示时出现的bug 1 正常演示 2 用jquery查询数据库并添加到页面后 3 相关代码 <script src"/static/jquery-2.1.4.js"></script> <script src"/static/layui/layui.js"></script> …

分布式与一致性协议之CAP(一)

CAP理论 概述。 在开发分布式系统的时候&#xff0c;会遇到一个非常棘手的问题&#xff0c;那就是如何根据业务特点&#xff0c;为系统设计合适的分区容错一致性模型&#xff0c;以实现集群能力。这个问题棘手在当发生分区错误时&#xff0c;应该如何保障系统稳定运行而不影响…

基于STM32和阿里云的智能台灯(STM32+ESP8266+MQTT+阿里云+语音模块)

一、主要完成功能 1、冷光模式和暖光模式两种灯光 主要支持冷光和暖光模式两种&#xff0c;可以通过语音模块或手机app远程切换冷暖光 2、自动模式和手动模式 主要支持手动模式和自动两种模式&#xff08;app或语音助手切换&#xff09; (1)自动模式&#xff1a;根据环境光照…

第七天 dfs剪枝优化

第七天 dfs剪枝&优化 1可行性剪枝 2最优性剪枝 3重复性剪枝 题 1 输入 5 5 6 …S. XX.X. …X… …D.X …X… 输出 YES —————————————— 题解 #include<iostream> #include<cstdio> using namespace std; const int N 10; int n,m,T; char …

绿色便携方式安装apache+mysql+tomcat+php集成环境并提供控制面板

绿色便携方式安装带控制面板的ApacheMariaDBTomcatPHP集成环境 目录 绿色便携方式安装带控制面板的ApacheMariaDBTomcatPHP集成环境[TOC](目录) 前言一、XAMPP二、安装和使用1.安装2.使用 三、可能的错误1、检查端口占用2、修改端口 前言 安装集成环境往往配置复杂&#xff0c…

自动化立体库安全使用管理制度

导语 大家好&#xff0c;我是智能仓储物流技术研习社的社长&#xff0c;老K。专注分享智能仓储物流技术、智能制造等内容。 新书《智能物流系统构成与技术实践》 完整版文件和更多学习资料&#xff0c;请球友到知识星球 【智能仓储物流技术研习社】自行下载 关于自动化立体库安…

四、Flask进阶

Flask-Cache pip install flask-caching安装flask_cache初始化 # ext.py from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_caching import Cachedb SQLAlchemy() migrate Migrate() cache Cache(config{CACHE_TYPE: simple # 缓存…

MybatisPlus(简单CURD,MP的实体类注解,MP条件查询,MP分页查询,MP批量操作,乐观锁,代码生成器)

目录 一、MP入门 1. MP是什么 2. MP使用入门 1 说明 2 准备MP项目环境 1) 添加依赖 2) 创建配置文件 3) 创建引导类 3 MP使用入门 1 创建实体类 2 创建Mapper 3 使用测试 3. 小结 二、MP简单CURD【重点】 1. 说明 2. 示例 3. 小结 三、MP的实体类注解[重点] …

字符串漏洞注入深入学习

字符串型漏洞注入&#xff0c;特别是针对Web应用程序的SQL注入&#xff0c;是一种常见的网络安全威胁。它涉及攻击者在不受控制的情况下&#xff0c;通过构造特定的字符串输入&#xff0c;干扰或改变应用程序中原有的SQL查询语句&#xff0c;从而执行恶意的SQL代码。 要深入学…

微软Phi-3,3.8亿参数能与Mixtral 8x7B和GPT-3.5相媲美,量化后还可直接在IPhone中运行

Phi-3系列 Phi-3是一系列先进的语言模型&#xff0c;专注于在保持足够紧凑以便在移动设备上部署的同时&#xff0c;实现高性能。Phi-3系列包括不同大小的模型&#xff1a; Phi-3-mini&#xff08;38亿参数&#xff09; - 该模型在3.3万亿个令牌上进行训练&#xff0c;设计得足…

【Stable Diffusion系列】(一):AI绘画本地部署教程

目录 一、总览 二、本地部署 1、安装cuda 2、安装python 3、安装git 4、方法一 1&#xff09;获取安装包 2&#xff09;update 3&#xff09;run 5、方法二 1&#xff09;git clone 2&#xff09;双击webui-user.bat 3&#xff09;更新 6、设置启动参数 7、…

指针(5)

前言 本节是有关指针内容的最后一节&#xff0c;本节的内容以讲解指针习题为主&#xff0c;那么就让我们一起来开启本节的学习吧&#xff01; sizeof和strlen的对比 1.sizeof 我们在学习操作符的时候&#xff0c;学习了sizeof。sizeof存在的意义是用来计算变量所占用的内存空…

AI大模型日报#0424:全球首个AI基因编辑器、出门问问上市、微软开源Phi-3 Mini、昆仑万维年收49亿

导读&#xff1a; 欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”生成了每条资讯的摘要。标题: 爱诗科技完成A2轮超亿元融资&#xff0c;蚂蚁集团领投摘要: 爱诗科技完成A2轮超亿元融资&#xff0c;成为视频大模型领域融资规模最…

STM32学习和实践笔记(20):定时器

1.定时器介绍 STM32F1的定时器一共有8个&#xff0c;由2个基本定时器&#xff08;TIM6、TIM7&#xff09;、4个通用定时器&#xff08;TIM2-TIM5&#xff09;和2个高级定时器&#xff08;TIM1、TIM8&#xff09;组成。 基本定时器的功能最为简单&#xff0c;类似于51单片机内定…

【行为型模式】中介者模式

一、中介者模式概述 中介者模式定义&#xff1a;用一个中介对象来封装一系列的对象交互&#xff0c;中介者使各对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式。(对象行为型模式) 中介者模式…

python+django校园社交高校交友网站2x7r5.

本课题使用Python语言进行开发。代码层面的操作主要在PyCharm中进行&#xff0c;将系统所使用到的表以及数据存储到MySQL数据库中&#xff0c;方便对数据进行操作本课题基于WEB的开发平台&#xff0c;设计的基本思路是&#xff1a; 前端&#xff1a;vue.jselementui 框架&#…