【C++】4、Preprocessor 预处理:条件编译、源文件包含、宏替换、重定义行号、错误信息、编译器预留指令

文章目录

  • 一、概述
  • 二、格式
    • 2.1 条件编译
    • 2.2 源文件包含
    • 2.3 宏替换
      • 2.3.1 语法
      • 2.3.2 C++标准内置的预定义宏
    • 2.4 重定义行号和文件名
    • 2.5 错误信息
    • 2.6 编译器预留指令
  • 三、应用场景

C++的 Build 可分为4个步骤:预处理、编译、汇编、链接。

  • 预处理就是本文要详细说的宏替换、头文件包含等
  • 编译是指对预处理后的代码进行语法和语义分析,最终得到汇编代码或接近汇编的其他中间代码
  • 汇编是指将上一步得到的汇编或中间代码转换为目标机器的二进制指令,一般是每个源文件生成一个二进制文件(VS是.obj,GCC是.o)
  • 链接是对上一步得到的多个二进制文件“链接”成可执行文件或库文件等。

一、概述

Preprocessor(预处理)包含 4 个阶段:

  • Trigraph replacement(字符映射):将系统相关的字符映射到C++标准定义的相应字符,但语义不变,如对不同操作系统上的不同的换行符统一换成规定字符(设为newline);
  • Line splicing(续行符处理):对于“\”紧跟newline的,删去“\”和newline,该过程只进行1遍(如果是“\”后有两个换行只会删去一个“\”);
  • Tokenization(字串分割):源代码作为一个串被分为如下串(Token)的连接:注释、whitespace、preprocessing tokens(标示符等这时都是preprocessing tokens,因为此时不知道谁是标示符,经过下一步之后,真正的预处理符会被处理);
  • 执行Preprocessor:对#include指令做递归进行该1-4步,此步骤时候源代码中不再含有任何预处理语句(#开头的那些)。

预处理之后的效果是:条件编译的测试不通过部分被删去、宏被替换、头文件被插入等。

预处理是以 translation unit 为单位进行的:一个 translation unit 就是一个源文件连同由#include包含(或间接包含)的所有文本文件的全体。一般编译器对一个 translation unit 生成一个二进制文件(VS是.obj,GCC是.o)。

二、格式

Preprocessor指令一般格式如下:

# preprocessing_instruction [arguments] newline

指令如下:(除了以上所列的Preprocessor指令外,其他指令是不被C++标准支持的,尽管有些编译器实现了自己的预处理指令。很据“可移植性比效率更重要”的原则,应该尽量仅适用C++标准的Preprocessor)

  • Null,一个 # 后跟 newline ,不产生任何影响,类似于空语句;
  • 条件编译,由 #if, #ifdef, #ifndef, #else, #elif, #endif 定义;
  • 源文件包含,由 #include 定义;
  • 宏替换,由 #define, #undef, #, ## 定义;
  • 重定义行号和文件名,由 #line 定义;
  • 错误信息,由 #error 定义;
  • 编译器预留指令,由 #pragma 定义。

2.1 条件编译

条件编译由 #if, #ifdef, #ifndef 开始,后跟 0-n 个 #elif ,后跟 0-1 个 #else ,后跟 #endif 。

#include <iostream>#define ABCD 2int main() {
#ifdef ABCDstd::cout << "1: yes\n";
#elsestd::cout<<"2:no\n";
#endif#ifndef ABCDstd::cout << "2: no1\n";
#elif ABCD == 2std::cout << "2: yes\n";
#elsestd::cout << "2: no2\n";
#endif#if !defined(DCBA) && (ABCD < 2 * 4 - 3) // todo: 不知道为什么ABCD < 2 * 4 - 3成立std::cout << "3: yes\n";
# endifstd::cin.get();return 0;
}// code result:
1: yes
2: yes
3: yes

条件编译被大量用于依赖于系统又需要跨平台的代码,这些代码一般会通过检测某些宏定义来识别操作系统、处理器架构、编译器,进而条件编译不同代码,以和系统兼容。

PS:但话又说回来,C++标准的最大价值就是让所有版本的C++实现都一致,所以除非调用系统功能,否则不应该对系统做出任何假设。

2.2 源文件包含

源文件包含指示将某个文件的内容插入到该#include处,这里“某个文件”将被递归预处理(1-4步,见第1节)。文件包含的3种格式为:

  • #include<filename>,在标准包含目录查找filename(一般C++标准库头文件在此)
  • #include"filename",先查找被处理源文件所在目录,如果没找到再找标准包含目录
  • #include pp-tokens其,其中pp-tokens须是定义为或"filename"的宏,否则结果未知。注意filename可以是任何文本文件,而不必是.h、.hpp等后缀文件,例如可以是.c或.cpp文本文件(所以标题是“源文件包含”而非“头文件包含”)。
#ifndef B_CPP
#define B_CPP
int b = 999;
#endif // B_CPP
// file: a.cpp
#include<iostream> // 在标准库目录找
#include"b.cpp"// 先在源文件所在目录找, 再再标准库找#define CMATH <cmath> // 如下两行效果即为 #include<cmath>, 这是一个标准库
#include CMATH // 同上int main() {std::cout << b << '\n'; // 是在b.cpp定义的std::cout << std::log10(10.0) << '\n';std::cin.get();return 0;
}// code result: 注意将a.cpp和b.cpp放在同一文件夹,只编译a.cpp(命令为g++ a.cpp && ./a.out)
999
1

2.3 宏替换

2.3.1 语法

#define 定义宏替换,#define 之后的宏都将被替换为宏的定义,直到用 #undef 解除该宏的定义。

宏定义分为不带参数的常量宏(Object-like macros)和带参数的函数宏(Function-like macros)。其格式如下:

#define identifier replacement-list
#define identifier( parameters ) replacement-list
#define identifier( parameters, ... ) replacement-list
#define identifier( ... ) replacement-list
#undef identifier

对于有参数的函数宏,在replacement-list中,“#”置于identifier面前表示将identifier变成字符串字面值,“##”连接,下面的例子来自cppreference.com:

#include<iostream>// make function factory
#define FUNCTION(name, a) int fun_##name() {return a;} // “#”置于identifier面前表示将identifier变成字符串字面值,“##”连接
FUNCTION(abcd, 12); // 定义func_abc()函数其无参数, 返回 12
FUNCTION(fff, 2);// 定义func_fff()函数其无参数, 返回 2
FUNCTION(kkk, 23);// 定义func_kkk()函数其无参数, 返回 23
#undef FUNCTION#define FUNCTION 34 // 之前已定义过的 fun_abcd()、fun_fff()、fun_kkk() 已定义好, 现在可以重新宏定义了#define OUTPUT(a) std::cout << #a << '\n'int main() {std::cout << "abcd: " << fun_abcd() << std::endl; // use function factorystd::cout << "fff: " << fun_fff() << std::endl;std::cout << "kkk: " << fun_kkk() << std::endl;std::cout << FUNCTION << std::endl; // 新的宏定义是34OUTPUT(million);std::cin.get();return 0;
}// code result:
abcd: 12
fff: 2
kkk: 23
34
million

可变参数宏是C++11新增部分(来自C99),使用时用__VA_ARGS__指代参数“…”,一个摘自C++标准2011的例子如下(标准举的例子就是不一样啊):

#include<iostream>
#define debug(...) fprintf(stderr, __VA_ARGS__) // __VA_ARGS__指代参数“...”
#define showlist(...) puts(#__VA_ARGS__)
#define report(test, ...) ((test) ? puts(#test) : printf(__VA_ARGS__))
int main() {int x = 1;int y = 2;debug("Flag");debug("X = %d\n", x);showlist(The first, second, and third items.);report(x>y, "x is %d but y is %d", x, y);
}// 这段代码在预处理后产生如下代码:
fprintf(stderr, "Flag");
fprintf(stderr, "X = %d\n", x);
puts("The first, second, and third items.");
((x>y) ? puts("x>y") : printf("x is %d but y is %d", x, y));// code result:
FlagX = 1
The first, second, and third items.
x is 1 but y is 2

2.3.2 C++标准内置的预定义宏

// 其中上面5个宏一定会被定义,下面从__STDC__开始的宏不一定被定义,这些预定义宏不能被 #undef
// 这些宏经常用于输出调试信息。预定义宏一般以“__”作为前缀,所以用户自定义宏应该避开“__”开头
// 现代的C++程序设计原则不推荐适用宏定义常量或函数宏,应该尽量少的使用 #define ,如果可能,用 const 变量或 inline 函数代替
__cplusplus: 在C++98中定义为199711L,C++11中定义为201103L
__LINE__: 指示所在的源代码行数(从1开始),十进制常数
__FILE__: 指示源文件名,字符串字面值
__DATE__: 处理时的日期,字符串字面值,格式“Mmm dd yyyy”
__TIME__: 处理时的时刻,字符串字面值,格式“hh:mm:ss”__STDC__: 指示是否符合Standard C,可能不被定义
__STDC_HOSTED__: 若是Hosted Implementation,定义为1,否则为0
__STDC_MB_MIGHT_NEQ_WC__: 见ISO/IEC 14882:2011
__STDC_VERSION__: 见ISO/IEC 14882:2011
__STDC_ISO_10646__: 见ISO/IEC 14882:2011
__STDCPP_STRICT_POINTER_SAFETY__: 见ISO/IEC 14882:2011
__STDCPP_THREADS__: 见ISO/IEC 14882:2011// 示例如下:
#include <iostream>
int main() {
#define PRINT(arg) std::cout << #arg": " << arg << '\n'PRINT(__cplusplus);PRINT(__LINE__);PRINT(__FILE__);PRINT(__DATE__);PRINT(__TIME__);
#ifdef __STDC__PRINT(__STDC__);
#endifstd::cin.get();return 0;
}
// code result:
__cplusplus: 201703
__LINE__: 6
__FILE__: /cppcodes/run/a.cpp
__DATE__: Aug 26 2023
__TIME__: 21:11:36
__STDC__: 1

2.4 重定义行号和文件名

#line number ["filename"] 的下一行源代码开始, __LINE__ 被重定义为从 number 开始,__FILE__ 被重定义 "filename"(可选),一个例子如下:

#include <iostream>
int main() {
#define PRINT(arg) std::cout << #arg": " << arg << '\n'
#line 999 "WO"PRINT(__LINE__);PRINT(__FILE__);std::cin.get();return 0;
}// code result:
__LINE__: 999
__FILE__: WO

2.5 错误信息

#error [message] 指示编译器报告错误,一般用于系统相关代码,例如检测操作系统类型,用条件编译里 #error 报告错误。例子如下:

int main(){
#error "w"return 0;
#error
}// code result:
/cppcodes/run/a.cpp:2:2: error: "w"
#error "w"^
/cppcodes/run/a.cpp:4:2: error:
#error^
2 errors generated.

2.6 编译器预留指令

#pragma预处理指令是C++标准给特定C++实现预留的标准,所以在不同的编译器上 #pragma 的参数及意义可能不同,如:

  • VC++2010 提供 #pragma once 来指示源文件只被处理一遍,参见MSDN相关条目
  • OpenMP 作为一个共享内存并行编程模型,使用 #pragma omp 指导语句,详见:OpenMP共享内存并行编程详解。
  • GCC 的 #pragma 指令参见GCC文档相关条目。

三、应用场景

预处理的常见使用有:

  • Include guard,见wikipedia条目,该技术用来保证头文件仅被同一文件包含一次(准确地说,头文件内容在一个 translation unit 中仅出现一次),以防止违反C++的“一次定义”原则;
  • 用 #ifdef 和特殊宏识别操作系统、处理器架构、编译器,条件编译,进而实现针对特定平台的功能,多用于可移植性代码;
  • 定义函数宏,以简化代码,或是方便修改某些配置;
  • 用 #pragma 设定和实现相关的配置(见上一节最后给出的链接)。
    sourceforge.net上有一个项目,是关于用宏检测操作系统、处理器架构、编译器(请点链接或见参考文献)。下面是一个例子(来自这里):

关于用宏检测的定义如下链接,代码示例如下:

  • 操作系统宏
  • 处理器架构宏
  • 编译器宏
#ifdef _WIN64//define something for Windows (64-bit)
#elif _WIN32//define something for Windows (32-bit)
#elif __APPLE__#include "TargetConditionals.h"#if TARGET_OS_IPHONE && TARGET_IPHONE_SIMULATOR// define something for simulator   #elif TARGET_OS_IPHONE// define something for iphone  #else#define TARGET_OS_OSX 1// define something for OSX#endif
#elif __linux// linux
#elif __unix // all unices not caught above// Unix
#elif __posix// POSIX
#endif

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

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

相关文章

基于Red Hat Enterprise Linux 7操作系统的PostgresSql15的备份恢复(实践笔记)

零、前言 本文是基于阿里云ECS服务器进行的实践操作&#xff0c;操作系统版本&#xff1a;Red Hat Enterprise Linux 7 PG数据库版本&#xff1a;PostgresSql 15 PG安装方式&#xff1a;yum 由于本人新接触pg数据&#xff0c;本次也是出于好奇&#xff0c;就对pg数据库的pg_du…

C#,《小白学程序》第五课:队列(Queue)

1 文本格式 /// <summary> /// 《小白学程序》第五课&#xff1a;队列&#xff08;Queue&#xff09; /// 日常生活中常见的排队&#xff0c;软件怎么体现呢&#xff1f; /// 排队的基本原则是&#xff1a;先到先得&#xff0c;先到先吃&#xff0c;先进先出 /// </su…

深度学习8:详解生成对抗网络原理

目录 大纲 生成随机变量 可以伪随机生成均匀随机变量 随机变量表示为操作或过程的结果 逆变换方法 生成模型 我们试图生成非常复杂的随机变量…… …所以让我们使用神经网络的变换方法作为函数&#xff01; 生成匹配网络 培养生成模型 比较基于样本的两个概率分布 …

CSS 属性值计算过程

目录 例子1&#xff0c;确定声明值2&#xff0c;层叠冲突2.1&#xff0c;比较源重要性2.2&#xff0c;比较优先级2.3&#xff0c;比较源次序 3&#xff0c;使用继承4&#xff0c;使用默认值其他 例子 我们来举例说明<h1> 标签最终的样式&#xff1a; <div><h1…

记录一个诡异的bug

将对接oa跳转到会议转写的项目oa/meetingtranslate项目发布到天宫&#xff0c;结果跳转到successPage后报错 这一看就是successPage接口名没对上啊&#xff0c;查了一下代码&#xff0c;没问题啊。 小心起见&#xff0c;我就把successPage的方法请求方式从Post改为Get和POST都…

Linux(基础篇二)

Linux基础篇 Linux基础篇二5. 系统管理5.1 Linux中的进程和服务5.3 systemctl5.4 运行级别CentOS 6CentOS 7 5.5 关机重启命令 Linux基础篇二 5. 系统管理 5.1 Linux中的进程和服务 计算机中&#xff0c;一个正在执行的程序或命令&#xff0c;被叫做“进程”(process) 启动之…

金融客户敏感信息的“精细化管控”新范式

目 录 01 客户信息保护三箭齐发&#xff0c;金融IT亟需把握四个原则‍ 02 制度制约阻碍信息保护的精细化管控 ‍‍‍‍‍‍‍ 03 敏感信息精细化管控范式的6个关键设计 04 分阶段实施&#xff0c;形成敏感信息管控的长效运营的机制 05 未来&#xff0c;新挑战与新机遇并存 …

【无标题】jenkins消息模板(飞书)

这里写目录标题 Jenkins 安装的插件 发送消息到飞书预览 1 &#xff08;单Job&#xff09;预览 2 &#xff08;多Job&#xff0c;概览&#xff09; Jenkins 安装的插件 插件名称作用Rebuilder Rebuilder。 官方地址&#xff1a;https://plugins.jenkins.io/rebuild 安装方式&a…

vue组装模板(侧边栏+顶部+主体)--项目阶段4

目录 一、前言介绍 二、结构解析 三、页面拆分 &#xff08;一&#xff09;页面拆分 1.侧边栏页面&#xff08;固定&#xff09;--Aside.vue 2.顶部页面&#xff08;固定&#xff09;--Header.vue 3.主体页面&#xff08;不固定的&#xff09;--示例用UserView…

【位运算进阶之----左移(<<)】

今天我们来谈谈左移这件事。 ❤️简单来说&#xff0c;对一个数左移就是在其的二进制表达末尾添0。左移一位添一个0&#xff0c;结果就是乘以2&#xff1b;左移两位添两个0&#xff0c;结果就乘以2 ^ 2&#xff1b;左移n位添n个0&#xff0c;结果就是乘以2 ^ n&#xff0c;小心…

小白到运维工程师自学之路 第七十九集 (基于Jenkins自动打包并部署Tomcat环境)2

紧接上文 4、新建Maven项目 clean package -Dmaven.test.skiptrue 用于构建项目并跳过执行测试 拉到最后选择构建后操作 SSH server webExec command scp 192.168.77.18:/root/.jenkins/workspace/probe/psi-probe-web/target/probe.war /usr/local/tomcat/webapps/ /usr/loca…

电商设计之分类模块设计

1、现在店铺流行这些简单的风格 2、 3、夏季新品 4、妖精的口袋----店铺展示 5、小狗电器-----优秀分类案例 6、客服中心 7、有线手持款---这里全都是有线首饰款&#xff0c;方便找到东西 8、裂帛 8.1裂帛分类模块 8.2 我点击了T恤 8.3 买雪纺衫&#xff0c;这里面全都是雪纺 …

mybatis与spring集成与spring aop集成pagehelper插件

Mybatis与Spring的集成 Mybatis是一款轻量级的ORM框架&#xff0c;而Spring是一个全栈式的框架&#xff0c;二者的结合可以让我们更加高效地进行数据持久化操作。 Mybatis与Spring的集成主要有两种方式&#xff1a;使用Spring的Mybatis支持和使用Mybatis的Spring支持。 使用…

python网络爬虫指南二:多线程网络爬虫、动态内容爬取(待续)

文章目录 一、多线程网络爬虫1.1 线程的基础内容、GIL1.2 创建线程的两种方式1.3 threading.Thread类1.4 线程常用方法和锁机制1.5 生产者-消费者模式1.5.1 生产者-消费者模式简介1.5.2 Condition 类协调线程 1.6 线程中的安全队列1.6 多线程爬取王者荣耀壁纸1.6.1 网页分析1.6…

在云服务器上安装Jenkins

说明&#xff1a;Jenkins是一个部署项目的平台&#xff0c;通过Jenkins可以省去从项目开发–>部署项目之间的所有流程&#xff0c;做到代码提交即上线。本文介绍在云服务CentOS上安装Jenkins。 前提 安装Jenkins之前&#xff0c;先要在云服务上安装JDK、Maven、Git&#x…

java八股文面试[多线程]——sleep wait join yield

sleep和wait有什么区别 sleep 方法和 wait 方法都是用来将线程进入阻塞状态的&#xff0c;并且 sleep 和 wait 方法都可以响应 interrupt 中断&#xff0c;也就是线程在休眠的过程中&#xff0c;如果收到中断信号&#xff0c;都可以进行响应并中断&#xff0c;且都可以抛出 In…

林业气象站——林业种植气象观测

林业气象站是一种用于观测林区气象环境的仪器&#xff0c;能够观测林区天气、土壤等自然环境参数&#xff08;温度、湿度、风速、风向、降雨量、气压、放射线、土壤湿度等&#xff09;&#xff0c;为开展环境观测、天气预报、灾害预警、林区虫害防治起到综合指导作用。 林业气…

lab7 thread

文章目录 Uthread: switching between threadstaskhints思路上下文的恢复和保存thread_createthread_schedule Using threads思路 Barrier Uthread: switching between threads 在这个练习中&#xff0c;你将为一个用户级别线程系统设计上下文切换机制&#xff0c;并实现它。 …

js实现数据关联查找更新。数据求和验证

为了实现这个功能我们和后端定义了数据结构 data:{id&#xff1a;‘’&#xff0c;formInfo:,formInfo2:,formInfo3:,formInfo4:, ......deailData:[ // 明细数据 // saleData 查询带出的对应明细序列号数据{ id:, ocopyId:, copyId:, odoId:, ......, saleData:[ { id:, oc…

stm32之4.时钟体系

3.时钟体系(给单片机提供一个非常稳定的频率信号) ①可以使用三种不同的时钟源来驱动系统时钟&#xff08;SYSCLK&#xff09;&#xff0c;CPU运行的频率为168MHZ&#xff1b; HSI(RC振荡器时钟&#xff0c;也就是高速内部时钟&#xff0c;一般来说很少用&#xff0c;因为精度…