cpp预编译指令
概述:这是针对cpp编译的预编译阶段发挥作用的预编译指令的一个简单整理和实验。
预处理指令,可以知道,编译流程大致可分为:预编译、编译、汇编和链接四个阶段,预编译指令因为在预编译阶段发挥作用。常见的#define宏就是预编译指令的一种,常用于进行常用量的声明,在预编译阶段的处理就是把所有使用到的地方进行实际使用代码的替换。除此以外,还有着其他各种各样的预编译指令。
预编译过程由独特的程序负责,包含于编译器中,但常常切割过程的时候会拿出来单独说明,预编译阶段的处理有几个独特的语法规则:
- 预编译指令为所在行就算是一条完整的预编译指令
- 预编译指令的句尾不需要像c/cpp那样使用分号进行收尾
好用的条件编译指令
常用的编译指令有那么几个:
#ifdef aaa exp #else exp1 #endif
如上,如果定义了aaa这么一个宏,执行exp操作,否则执行exp操作,最后以endif来进行语句块收尾;和ifdef相反的是ifndef,如果aaa没有定义,执行exp操作,如果存在定义,那就执行exp1操作。除了上面的定义条件的判断,还有着类同if-else的简单判断:
#if state exp #elif state1 exp1 #else exp2 #endif
简单来说,就是上面state判断为真,就执行exp操作,state1判断为真,执行exp1操作,如果前面的条件都不成立,那就执行exp2的操作了,最后还是用endif来进行收尾。这种分支判断,常见于系统的判断,从而保留不同的代码:
#include <iostream> using std::cout; int main(){ #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) #ifdef _WIN64 cout << "Hello, here is windows64 system.\n"; #elif _WIN32 cout << "Hello, here is windows32 system.\n"; #else cout << "Here is windows system but not the 32bit or 64bit.\n"; #endif #elif defined(__linux) || defined(__linux__) cout << "Here is linux system.\n"; #endif cout << "Hello, world.\n"; return 0; }
上面的应用比较简单,首先是判断那么几个变量是否有定义,这些个变量是跟随在标准库中的,不同的系统有不同的实现,但都致力于提供一样的对外api,回到这里就是根据不同的系统有不同的输出,需要注意的就是defined,它能和#if语句一起达到#ifdef的效果。首先是在windows中进行预编译一下:
PS D:\Desktop> g++ -E test.cpp -o test.i PS D:\Desktop> cat test.i ...... # 3 "test.cpp" using std::cout; int main(){ cout << "Hello, here is windows64 system.\n"; # 17 "test.cpp" cout << "Hello, world.\n"; return 0; }
因为宏替换和头替换太长了,这里专注于main函数部分,在windows系统中,这里就只保留了windows64系统的输出语句和不加条件的简单hello,world。再来看看ubuntu中的表现:
ack@jam-ubuntu:~/Desktop/test$ g++ -E test.cpp -o test.i jam@jam-ubuntu:~/Desktop/test$ ls test.cpp test.i jam@jam-ubuntu:~/Desktop/test$ cat test.i | tail -n 10 # 3 "test.cpp" using std::cout; int main(){ # 15 "test.cpp" cout << "Here is linux system.\n"; cout << "Hello, world.\n"; return 0; } jam@jam-ubuntu:~/Desktop/test$
如上,这样就只保留了linux部分的输出语句了,这就是条件编译。常见的各种宏定义如下:
注:条件编译是预编译器选择性保留代码的指令,if-else是在运行是进行的分支判断,两者不一样。
杂七杂八的命令
除了条件编译指令,最常见的就是#define宏定义命令,#include头文件包含命令,和宏定义命令相对的#undef命令了。
这里还有着一种error指令,可以使编译器显示错误信息,并停止编译,如下:
#include <iostream> #if defined(__cplusplus) #error cpp compiler is denied. #endif using std::cout; int main(){ cout << "Hello, world.\n"; return 0; }
编译起来效果就这样了:
PS D:\Desktop> g++ test.cpp -o test test.cpp:4:2: error: #error cpp compiler is denied. #error cpp compiler is denied. ^~~~~ PS D:\Desktop> g++ -E test.cpp -o test.i test.cpp:4:2: error: #error cpp compiler is denied. #error cpp compiler is denied. ^~~~~ PS D:\Desktop>
它会在初始的预编译阶段就卡壳,使得后面的都不通过,并且输出一条提示信息。
一个多行宏定义
相信接触内核多了,就会见到各种各样的数据结构和宏定义,有一些干脆就使用一个宏来代替函数:
#define __HAL_RCC_GPIOC_CLK_ENABLE() do { \ __IO uint32_t tmpreg; \ SET_BIT(RCC->IOPENR, RCC_IOPENR_GPIOCEN);\ /* Delay after an RCC peripheral clock enabling */ \ tmpreg = READ_BIT(RCC->IOPENR, RCC_IOPENR_GPIOCEN);\ UNUSED(tmpreg); \ } while(0)
如上,就是一个多行宏定义,很常见也很让人头疼的东西。
多变的pragma
作为预编译指令的一种,pragma有着两种语法:#pragma token-string
和#__pragma(token-string)
,这里的token使用不同的值可以指示不同的意义:
#pragma startup fun
在主程序执行前执行这里指定的fun函数,除了startup以外,还有着exit来指定程序结束前的运行,warning来控制编译器对编译中的警告信息的输出,后续加上push 0就表示当前警告级别设置为0并入栈,从而禁用所有警告,如下:
#pragma warning(push 0) //还有出栈操作,pop之前设置的入栈的警告级别从而恢复到前面的级别 #pragma warning(pop) //细致确定哪一个警告的还有 #pragma warning(push) #pragma warning(disable: 4444) #pragma warning(disable: 4466) #pragma warning(pop) //这样在push和pop之间的语句执行就会把4444和4406警告给干掉,编译时就不再出现这样的警告了。
当然,对于个人来说,最常用的还是:#pragma comment(lib, "Ws2_32.lib")
,这样的语句是发挥引入作用,在所有指定依赖路径,引入Ws2_32.lib静态库,当然也可能是动态库的信息头。嗯,还有着和#pragma相同功能的__pragma,这个关键字可以放在宏定义中进行,而#pragma却不行;还有一件事,它们还有着_pragma的兄弟,但也是配合使用而已,所以不再赘述。
常见的pragma once,用的vs多的人,在添加源文件的时候能够很经常的看到这么一个指令,它是在添加的时候由IDE内含到文件里面的,它的作用是使得这个文件在编译中只包含一次。因为在cpp的class定义中,常常会相互嵌套,相互内含,有时候就会出现在编译中来回包含的情况,#pragma once就是用来解决这个问题的。但有时候这种做法在linux中并不适用,所以有着类似的做法是使用条件编译指令的ifdef和define进行配合来手动实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程