【C语言:编译、预处理详解】

文章目录

  • 1.编译
  • 2.预处理
    • 2.1宏定义
      • 2.1.1预定义符号
      • 2.1.2#define定义常量
      • 2.1.3#define定义宏
      • 2.1.4do-while-zero
      • 2.1.5宏的注意事项
      • 2.1.6宏与函数的对比
    • 2.2条件编译
    • 2.3文件包含
  • 3.offsetoff
  • 4.#与##
    • 4.1. #号
    • 4.2 ##号

在这里插入图片描述

1.编译

我们都知道,一个程序如果想运行起来要经过编译、链接然后才能生成.exe的文件。
在这里插入图片描述
编译⼜可以分解为三个过程:

  • 预处理(有些书也叫预编译)、
  • 编译
  • 汇编

在这里插入图片描述

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

  • 删除所有的注释(该步骤在宏替换之前)
  • 将所有的 #define 删除,并替换所有的宏定义
  • 处理所有的条件编译指令,如: #if、#elif、#else、#ifdef、#ifndef、#endif 。
  • 处理#include 预编译指令,将包含的头文件的内容插⼊到该预编译指令的位置。这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件。
  • 添加行号和文件名标识,方便后续编译器生成调试信息等。
  • 保留所有的#pragma的编译器指令,编译器后续会使⽤。

经过预处理后的.i⽂件中不再包含宏定义,因为宏已经被展开。并且包含的头⽂件都被插⼊到.i文件中。所以当我们无法知道宏定义或者头文件是否包含正确的时候,可以查看预处理后的.i文件来确认。

编译阶段:就是将预处理后的⽂件进行⼀系列的:词法分析、语法分析、语义分析及优化 ,⽣成相应的汇编代码⽂件。
汇编阶段:汇编器将汇编代码转转变成机器可执⾏的指令,每⼀个汇编语句⼏乎都对应⼀条机器指令。就是根据汇编指令和机器指令的对照表⼀⼀的进⾏翻译,也不做指令优化。

2.预处理

C语言提供的预处理功能主要有以下三种:

  • 宏定义
  • 条件编译
  • 文件包含

2.1宏定义

2.1.1预定义符号

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

__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

在这里插入图片描述

2.1.2#define定义常量

  1. 基本使用

在这里插入图片描述

  1. 巧妙使用

在这里插入图片描述

  1. 续航符

当需要替换的内容过长的时候,可以使用 \ 实现续航。
在这里插入图片描述

思考:在#define定义标识符的时候,要不要在最后加上 ;
建议不要加上 ; 这样容易导致问题
在这里插入图片描述
如果是加了分号的情况,等替换后,if和else之间就是2条语句,⽽没有⼤括号的时候,if后边只能有⼀条语句 。这⾥会出现语法错误。

2.1.3#define定义宏

#define机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏。
宏的声明方式:

#define name( parament-list ) stuff

其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。参数列表的左括号必须与name紧邻,如果两者之间有任何空⽩存在,参数列表就会被解释为stuff的⼀部分。
在这里插入图片描述
对于上述案例,我们可以清楚的看到括号在宏中起着至关重要的作用,为了让表达式的结果是我们想要的结果,要尽量给宏带上括号。避免在使⽤宏时由于参数中的操作符或邻近操作符之间不可预料的相互作⽤。
要点:宏在使用的时候,只是原样替换,不进行任何的改变。
下面代码的输出结果是什么呢?

#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main()
{int x = 5;int y = 8;int z = MAX(x++, y++);printf("x=%d y=%d z=%d\n", x, y, z);return 0;
}
z = ( (x++) > (y++) ? (x++) : (y++));
x-->5-->6
y-->8-->9-->10
z-->9
所以输出的结果是:x=6 y=10 z=9

2.1.4do-while-zero

假如现在我有一个代码,我就想在if后面跟一条用宏定义多条语句的一条语句,我可以怎么做呢?
在这里插入图片描述
我们发现这样是可以实现目的,但是有点别扭,而且我们习惯在一条语句后面加分号,所以在预处理后的代码中,就会对应有两个分号。
在这里插入图片描述
那到底能怎么处理呢?
在这里插入图片描述
这样是不是就可以满足既是一条语句,还能写分号(没有空语句)的目的了。

2.1.5宏的注意事项

  1. 源文件的任何地方,宏都可以定义,与是否在函数内外,无关
  2. 宏替换的规则

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

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

注意:

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

从定义处开始,往后都有效(没有出现#undef

在这里插入图片描述

出现#undef

在这里插入图片描述

思考下面的代码:

在这里插入图片描述

2.1.6宏与函数的对比

宏通常被应用于执行简单的运算。
比如在两个数中找出较⼤的⼀个时,写成下⾯的宏,更有优势⼀些。

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

那为什么不⽤函数来完成这个任务?
原因有二:

  1. 用于调用函数和从函数返回的代码可能⽐实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度⽅⾯更胜⼀筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏是类型⽆关的

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到

#include<stdio.h>
#include<stdlib.h>
#define MALLOC(num, type) (type*)malloc((num) * sizeof(type))
int main()
{int* p = (int*)malloc(40);int* q = MALLOC(10, int);//(int*)malloc((10) * sizeof(int))return 0;
}

宏和函数的⼀个对比

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

2.2条件编译

一般情况下,源程序中所有行都参加编译。但有时候希望程序中的一部分内容只在满足一定条件时才进行编译,也就是对这一部分内容指定编译的条件,这就是“条件编译”。
条件编译的指令有以下几种形式:

  1. #ifdef #else # endif
    它的作用是:若指定的标识符以定义(不管真假),则让程序段1参与编译,否则让程序段2参与编译。
    #endif必须要有,#else可以没有

在这里插入图片描述
在这里插入图片描述

  1. #ifndef #else # endif
    它的作用是:若指定的标识符没有定义,则让程序段1参与编译,否则让程序段2参与编译。这种形式与第一种恰恰相反。

在这里插入图片描述

  1. #if #else # endif
    它的作用是:判断指定表达式的结果是否为真,为真就执行程序段1;否则执行程序段2.

在这里插入图片描述
在这里插入图片描述
多分支的条件编译
在这里插入图片描述

利用 #if 实现 #indef / #ifndef

在这里插入图片描述
在这里插入图片描述

2.3文件包含

所谓文件包含,是指一个源文件可以将另一个源文件的全部内容包含进来,即将另外的文件内容包含到本文件之中,插入到当前位置。
C语言有两种文件包含的方式:

  • #include< >
  • #include" "

这两种方式有什么区别呢?- - - - -二者的查找策略不同

  • #include< > :查找头文件直接去标准路径下查找,如果找不到就提⽰编译错误。
  • #include" ":先在源⽂件所在⽬录下查找,如果该头文件未找到,编译器就像查找库函数头⽂件⼀样,再去标准路径下查找头⽂件。如果找不到就提示编译错误。

这样是不是可以说,对于库⽂件也可以使⽤ " " 的形式包含?

是的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地⽂文件了。

文件包含就是将指定文件内容包含到本文件之中,插入到当前位置。那如果我将一个头文件重复包含会怎么样呢?

包含几次,就会将所包含文件的代码复制几次,如果工程比较大,有公共使用的头文件,被⼤家都能使用,又不做任何的处理,那么后果真的不堪设想。

如何解决头⽂件被重复引⼊的问题?

答案:条件编译

#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__

#pragma once

3.offsetoff

在这里插入图片描述
这个其实是一个宏,它的作用就是返回结构体/共用体成员,在结构体/共用体中的偏移量。
在这里插入图片描述
既然我们已经学习了宏,那就来模拟实现以下吧!
我们都知道,偏移量是相较于起始位置的地址,那么是不是就可以将这个起始位置想象为0。
将0强转为一个结构体类型的指针,然后去访问结构体的成员,
取出该成员的地址,这时候该成员的地址是不是就是相较于0地址处的偏移量了呢?
代码如下:

#define MY_OFFSETOF(type,member_name)  (int)&(((type*)0)->member_name)
struct stu
{char c1;int i;char c2;
};
int main()
{struct stu s = { 0 };printf("%d\n", MY_OFFSETOF(struct stu, c1));printf("%d\n", MY_OFFSETOF(struct stu, i));printf("%d\n", MY_OFFSETOF(struct stu, c2));return 0;
}

4.#与##

4.1. #号

平常当我们有⼀个变量 int a = 10; 的时候,我们想打印出: the value of a is 10 是不是会这样写:
在这里插入图片描述
这样写是不是不能是字符串中的变量名联动起来,一个变量就得写一条printf,挺麻烦的。
下面了解一下#:

#运算符是将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。#运算符所执⾏的操作可以理解为”字符串化“。

有了#就可以这样写:
在这里插入图片描述

4.2 ##号

##可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符,## 被称为记号粘合。
这样的连接必须产生⼀个合法的标识符,否则其结果就是未定义的。
右侧为预处理后的结果:
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

Python如何实现邮件的发送

python笔记- 发送邮件 依赖&#xff1a; Python代码实现发送邮件&#xff0c;使用的模块是smtplib、MIMEText&#xff0c;实现代码之前需要导入包&#xff1a; import smtplib from email.mime.text import MIMEText 使用163邮件发送邮件&#xff0c;具体代码实现如下&#x…

Tofu5m目标识别跟踪模块 跟踪模块

Tofu5m 是高性价比目标识别跟踪模块&#xff0c;支持可见光视频或红外网络视频的输入&#xff0c;支持视频下的多类型物体检测、识别、跟踪等功能。 产品支持视频编码、设备管理、目标检测、深度学习识别、跟踪等功能&#xff0c;提供多机版与触控版管理软件&#xff0c;为二次…

vivado 错误路径

错误路径 假路径是指拓扑上存在于设计中的路径&#xff0c;但有以下两种情况之一&#xff1a;&#xff08;1&#xff09;不起作用&#xff1b;或&#xff08;2&#xff09;不需要定时。因此&#xff0c;在定时期间应忽略错误路径分析。 错误路径的示例包括&#xff1a; •添…

渗透测试——1.4主动扫描

主动扫描是别人可以发觉的情报收集 一、nmap的使用 1.nmap<目标主机>:最常用的扫描方式 有nmap版本、扫描时间 “host is up”表示目标主机处于开机状态、“not shown”未开放端口 有四个端口是开的&#xff08;135.139.445.912&#xff09; 2.nmap -p<端口范围…

ASP.NET Core 使用Log4Net写文本日志和数据库日志

1&#xff0c;先安装依赖 2&#xff0c;在项目目录新建Log4Net.config配置文件&#xff0c;必须要安装System.Data.SqlClient&#xff0c;不然日志存不进去数据库 <?xml version"1.0" encoding"utf-8"?> <log4net><!-- Define some outpu…

Spring实战系列(三)了解容器的基本实现

我们可以通过GitHub或者码云下载spring-framework源码&#xff0c;这边是基于5.X版本进行下载学习的。 地址&#xff1a;https://github.com/spring-projects/spring-framework 分析Spring源码是非常一件的难的事情&#xff0c;只能一步步学习&#xff0c;一步步记录。 前面在…

LeetCode day31

LeetCode day31 被创新实践的机器学习大作业和数据库作业折磨力&#xff0c;临近期末&#xff0c;各种大作业以及ddl&#xff0c;搞的咱只能偶尔刷刷力扣&#xff0c;但是csdn就挺难去发布了,大家期末也好好复习过个好年啦&#xff0c;O(∩_∩)O 409. 最长回文串 给定一个包含…

Unity中Shader裁剪空间推导(在Shader中实现)

文章目录 前言一、在Shader中&#xff0c;手动把正交相机的坐标转化到裁剪空间1、我们在属性面板定义一个变量&#xff0c;用于传入摄像机的信息2、获取h、r、w、n、f3、获取OpenGL下的转化矩阵4、 获取DirectX下的转化矩阵5、手动将观察空间下的坐标转换到裁剪空间下6、这里为…

MR实战:统计总分与平均分

文章目录 一、实战概述二、提出任务三、完成任务&#xff08;一&#xff09;准备数据1、在虚拟机上创建文本文件2、上传文件到HDFS指定目录 &#xff08;二&#xff09;实现步骤1、创建Maven项目2、添加相关依赖3、创建日志属性文件4、创建成绩映射器类5、创建成绩驱动器类6、启…

JavaScript基础知识点总结:从零开始学习JavaScript(三)

如果大家感感兴趣也可以去看&#xff1a; &#x1f389;博客主页&#xff1a;阿猫的故乡 &#x1f389;系列专栏&#xff1a;JavaScript专题栏 &#x1f389;ajax专栏&#xff1a;ajax知识点 &#x1f389;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 学习…

2024重新洗牌“IT界”,是谁给它的底气?

​在2023年下半年" IT界"最引发程序员关注的热点话题就是鸿蒙了。 就今年9月25日&#xff0c;从华为发布会宣布明年2024将推出HarmonyOS NEXT版本后&#xff0c;这意味着”鸿蒙不再与android兼容“。鸿蒙将与安卓、ios形成”三足鼎立“之势。 鸿蒙激起千层浪 12月…

2023安洵杯-秦岭防御军wp

reverse 感觉有点点简单## import base64 def ba64_decode(str1_1):mapp "4KBbSzwWClkZ2gsr1qAQu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh"data_1 [0] * 4flag_1 [0] * 3for i in range(32, 127):for y in range(32, 127):for k in range(32, 127):flag_1[0]…

【RocketMQ笔记02】安装RocketMQ可视化工具rocketmq-dashboard

这篇文章&#xff0c;主要介绍如何安装RocketMQ可视化工具rocketmq-dashboard。 目录 一、RocketMQ可视化界面 1.1、下载rocketmq-dashboard 1.2、修改配置文件 1.3、打包工程 1.4、启动rocketmq-dashboard 一、RocketMQ可视化界面 1.1、下载rocketmq-dashboard rocketm…

TYPE C 接口知识

1、Type C 概述 Type-C口有4对TX/RX分线&#xff0c;2对USBD/D-&#xff0c;一对SBU&#xff0c;2个CC&#xff0c;另外还有4个VBUS和4个地线。 当Type-C接口仅用作传输DP信号时&#xff0c;则可利用4对TX/RX&#xff0c;从而实现4Lane传输&#xff0c;这种模式称为DPonly模式…

【Leetcode】重排链表、旋转链表、反转链表||

目录 &#x1f4a1;重排链表 题目描述 方法一&#xff1a; 方法二&#xff1a; &#x1f4a1;旋转链表 题目描述 方法&#xff1a; &#x1f4a1;反转链表|| 题目描述 方法&#xff1a; &#x1f4a1;总结 &#x1f4a1;重排链表 题目描述 给定一个单链表 L 的头节…

软件测试/测试开发丨Python 装饰器常见的报错信息、原因和解决方案

Python 装饰器简介 装饰器&#xff08;Decorator&#xff09;是 Python 非常实用的一个语法糖功能。装饰器本质是一种返回值也是函数的函数&#xff0c;可以称之为“函数的函数”。其目的是在不对现有函数进行修改的情况下&#xff0c;实现额外的功能。 在 Python 中&#xf…

ueditor富文本编辑器中图片上传地址配置以及抓取远程图片地址的配置

一&#xff1a;图片上传保存地址配置 打开文件ueditor.php,找到imagePathFormat进行修改即可 一&#xff1a;远程抓取图片配置 打开文件ueditor.config.js,找到catchRemoteImageEnable&#xff0c;取消注释即可

2024年元旦节放假通知

致尊敬的客户以及全体同仁&#xff1a; 旧岁已展千重锦&#xff0c;新年再进百尺竿。在这辞旧迎新之际&#xff0c;易天光通信提前祝您元旦快乐&#xff01;生意兴隆&#xff0c;身体健康&#xff0c;万事如意&#xff01;根据国家法定假期的规定&#xff0c;并结合公司实际情…

开源verilog模拟 iverilog verilator +gtkwave仿真及一点区别

开源的 iverilog verilator 和商业软件动不动几G几十G相比&#xff0c;体积小的几乎可以忽略不计。 两个都比较好用&#xff0c;各有优势。 iverilog兼容性好。 verilator速度快。 配上gtkwave 看波形&#xff0c;仿真工具基本就齐了。 说下基本用法 计数器 counter.v module…

【接口测试】Postman(一)--接口测试知识准备 _

1.0 前言 ​ 应用程序编程接口&#xff08;Application Programming Interface, API&#xff09;是这些年来最流行的技术之一&#xff0c;强大的Web应用程序和领先的移动应用程序都离不开后端强大的API。API技术的应用给系统开发带来了便利&#xff0c;但也对测试人员提出了更高…