当前位置: 首页 > news >正文

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

相关文章:

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

目录 一.预定义符号和#define定义常量 二.#define定义宏 三.宏和函数的对比 四、#和##运算符 五、条件编译 在之前&#xff0c;我们已经介绍了.c文件在运行的过程图解&#xff0c;大的方面要经过两个方面。 一、翻译环境 1.预处理&#xff08;预编译&#xff09; 2.编译 3…...

算法——单调栈

单调栈&#xff1a; 保持栈内的元素始终递增或递减。 单调递增 待处理数组{1,5,2,5,7,2,8} public void sameyIncrease(int[] nums) {Stack<Integer> stack new Stack<>();for(int i 0; i < nums.length; i) {//当栈空的时候可以直接进栈或者要进栈的数大于…...

LeetCode讲解篇之695. 岛屿的最大面积

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们遍历二维矩阵&#xff0c;如果当前格子的元素为1进行深度优先搜索&#xff0c;将搜索过的格子置为0&#xff0c;防止重复访问&#xff0c;然后对继续深度优先搜索上下左右中为1的格子 题解代码 func maxAr…...

招联2025校招内推倒计时

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递&#xff09; 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…...

vite学习教程01、vite构建vue2

文章目录 前言一、vite初始化项目二、修改配置文件2.1、修改main.js文件2.2、修改App.vue文件2.3、修改helloworld.vue2.4、修改vite.conf.js2.5、修改vue版本--修改package.json文件 三、安装vue2和vite插件四、启动服务资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝3W&…...

强化学习部分代码的注释分析

引言 对一些代码块进行注释。我直接复制过来的&#xff0c;不能运行的话别怪我。 多臂赌博机 代码来自链接。欢迎回到原来的链接学习。 %I thought what Id do was Id pretend I was one of those deaf-mutes&#xff0c;or should I ?clear all; epsilon[0.5,0.2,0.1,0.0…...

ctf.bugku-备份是个好习惯

访问页面得到字符串 这串字符串是重复的&#xff1b; d41d8cd98f00b204e9800998ecf8427e 从前端、源码上看&#xff0c;除了这段字符串&#xff0c;没有其他信息&#xff1b;尝试解密&#xff0c;长度32位&#xff1b;各种解密方式试试&#xff1b; MD5免费在线解密破解_MD5在…...

C++面试速通宝典——14

220. static关键字的作用 ‌‌‌‌  static关键字在编程中有多种作用&#xff1a; 在类的成员变量前使用&#xff0c;表示该变量属于类本身&#xff0c;而不是任何类的实例。在类的成员函数前使用&#xff0c;表示该函数不需要对象实例即可调用&#xff0c;且只能访问类的静…...

k8s的简介和部署

一、k8s简介 在部署应用程序的方式上面&#xff0c;主要经历了三个阶段&#xff1a; 传统部署:互联网早期&#xff0c;会直接将应用程序部署在物理机上优点:简单&#xff0c;不需要其它技术的参与缺点:不能为应用程序定义资源使用边界&#xff0c;很难合理地分配计算资源&…...

Thingsboard 网关实战 modbus通信 rpc下发控制指令

我们这里说的是Thingsboard通过网关modbus通信接入设备&#xff0c;然后通过rpc下发指令去控制开关信号的设备&#xff0c;不会网关通过modbus接入设备的&#xff0c;可以看我之前的文章&#xff0c;从小白教学。 下面我们就说如何下发rpc开关信号指令 第一步.在modbus配置文…...

基于pytorch的手写数字识别

import pandas as pd import numpy as np import torch import matplotlib import matplotlib.pyplot as plt from torch.utils.data import TensorDataset, DataLoadermatplotlib.use(tkAgg)# 设置图形配置 config {"font.family": serif,"mathtext.fontset&q…...

MySQL 实验 7:索引的操作

MySQL 实验 7&#xff1a;索引的操作 索引是对数据表中一列或多列的值进行排序的一种结构&#xff0c;索引可以大大提高 MySQL 的检索速度。合理使用索引&#xff0c;可以大大提升 SQL 查询的性能。 索引好比是一本书前面的目录&#xff0c;假如我们需要从书籍查找与 xx 相关…...

为Floorp浏览器添加搜索引擎及搜索栏相关设置. 2024-10-05

Floorp浏览器开源项目地址: https://github.com/floorp-Projects/floorp/ 1.第一步 为Floorp浏览器添加搜索栏 (1.工具栏空白处 次键选择 定制工具栏 (2. 把 搜索框 拖动至工具栏 2.添加搜索引擎 以添加 搜狗搜索 为例 (1.访问 搜索引擎网址 搜狗搜索引擎 - 上网从搜狗开始 (2…...

如何设置WSL Ubuntu在Windows开机时自动启动

如何设置WSL Ubuntu在Windows开机时自动启动 步骤详解1. 创建批处理脚本2. 添加到Windows启动项 注意事项结语 在使用Windows Subsystem for Linux (WSL) 时,我们可能希望Ubuntu能够在Windows启动时自动运行。本文将介绍如何实现这一功能,让您的开发环境更加便捷。 步骤详解 …...

使用TensorBoard可视化模型

目录 TensorBoard简介 神经网络模型 可视化 轮次-损失曲线 轮次-准确率曲线 轮次-学习率曲线 迭代-评估准确率曲线 迭代-评估损失曲线 TensorBoard简介 TensorBoard是一款出色的交互式的模型可视化工具。安装TensorFlow时,会自动安装TensorBoard。如图: TensorFlow可…...

《深度学习》OpenCV 图像拼接 原理、参数解析、案例实现

目录 一、图像拼接 1、直接看案例 图1与图2展示&#xff1a; 合并完结果&#xff1a; 2、什么是图像拼接 3、图像拼接步骤 1&#xff09;加载图像 2&#xff09;特征点检测与描述 3&#xff09;特征点匹配 4&#xff09;图像配准 5&#xff09;图像变换和拼接 6&am…...

Hive数仓操作(三)

一、Hive 数据库操作 1. 创建数据库 基本创建数据库命令&#xff1a; CREATE DATABASE bigdata;说明&#xff1a; 数据库会在 HDFS 中以目录的形式创建和保存&#xff0c;数据库名称会存储在 Hive 的元数据中。如果不指定目录&#xff0c;数据库将在 /user/hive/warehouse 下…...

TDSQL-C电商可视化,重塑电商决策新纪元

前言&#xff1a; 在数字化浪潮席卷全球的今天&#xff0c;电子商务行业以其独特的魅力和无限潜力&#xff0c;成为了推动全球经济增长的重要引擎。然而&#xff0c;随着业务规模的急剧扩张&#xff0c;海量数据的涌现给电商企业带来了前所未有的挑战与机遇。如何高效地处理、…...

翔云 OCR:发票识别与验真

在数字化时代&#xff0c;高效处理大量文档和数据成为企业和个人的迫切需求。翔云 OCR 作为一款强大的光学字符识别工具&#xff0c;在发票识别及验真方面表现出色&#xff0c;为我们带来了极大的便利。 一、翔云 OCR 简介 翔云 OCR 是一款基于先进的人工智能技术开发的文字识别…...

HTML ASCII:Web 开发中的字符编码基础

HTML ASCII&#xff1a;Web 开发中的字符编码基础 ASCII&#xff0c;全称为美国信息交换标准代码&#xff08;American Standard Code for Information Interchange&#xff09;&#xff0c;是一种用于电子通信的字符编码标准。它最初于1963年提出&#xff0c;用于在不同的计算…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...