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

C/C++常用预编译指令介绍

目录

1、#include指令

2、#define和#undef指令

3、#ifdef、#ifndef、#else、#elif和#endif指令

4、#error指令   

5、编译器预置宏__FILE__、__LINE__和__FUNCTION__

6、#pragma指令

6.1、#pragma once指令

6.2、#pragma message指令

6.3、#pragma warning指令

6.4、#pragma comment指令

6.5、#pragma hdrstop指令

6.6、#pragma resource指令

6.7、#pragma code_seg指令

6.8、#pragma data_seg指令

6.9、#pragma init_seg指令

6.10、#pragma pack指令


       C/C++代码会使用到各式各样的预编译指令,预编译指令一般是以#号开头的,这些指令是在编译期间识别的,由编译器去负责解析这些指令并做出对应的处理。今天我们来介绍一下C/C++中常用的预编译指令。

VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931

1、#include指令

       该指令用来包含头文件,头文件中可能有结构体、枚举体、类或函数的声明,使用到这些对象时需要包含声明这些对象的头文件,比如:

#include “stdio.h”

       包含头文件有#include <filename.h>和#include “filename.h”两种方式,两种方式是有区别的,决定了编译时搜索头文件的路径:

1)对于#include <filename.h> ,编译器在编译时从标准库路径开始搜索filename.h,所以在包含C/C++标准库时需要使用该方式,比如#include <iostream>、#include <vector>。
2)对于#include “filename.h”,编译器从用户的工作路径开始搜索filename.h。对应非标准库的用户自定义头文件,使用这种包含方式。

2、#define和#undef指令

        #define通常用来定义一个宏,比如代码中多处使用的某个固定数值,我们可以用将固定值定义成宏,比如我们在用sqlite数据库存放数据时会设定单个数据库文件的大小:

#define MAX_CHAT_RECORD_DB_FILE_SIZE 1024*1024*500 // 单个db文件的大小上限是200MB

将一些常量或表达式定义成宏的好处是,如果要修改该数值,只要修改宏定义的地方即可,不要去每个使用到的地方去修改。

       定义的宏可以是一个数值,也可以是个表达式,也可以是一段经常使用的代码。比如我们在delete指针时,可以封装成这样的宏:

/*lint -emacro(717, SAFE_DELETE)*/
#define SAFE_DELETE(p) \
do \
{ \
    if ((p) != NULL) \
    { \
        delete (p); \
        (p) = NULL; \
    } \
} while (0)

对于上述宏,在编译器编译时,会将使用到这些宏的地方直接替换成宏的内容。

       除了将一个数值、一个表达式或一段代码定义成宏,可以直接定义一个没有具体内容的宏,仅仅表示代码中定义了该宏。只是起到一个标识的作用,代码中可以使用#ifdef或#ifndef来判断有没有定义该宏。 

#ifdef RTC_VER
// ... // 定义了RTC_VER宏时的处理代码
#else
// ... // 没定义RTC_VER宏时的处理代码
#endif

通过添加宏,去控制代码的逻辑和走向,也可以通过宏去确定当前模式是否支持某些功能。

        #undef指令则用于取消之前定义的宏。

3、#ifdef、#ifndef、#else、#elif和#endif指令

       这些是预编译指令中的条件判断指令,和C/C++代码中if、else、else if等是类似的。这些指令主要处理与宏相关的判断。比如我们经常在头文件中添加如下的一段预编译指令,防止头文件内容被多次包含:

#ifndef VERSION_CONTROL_H
#define VERSION_CONTROL_H

// ... // 头文件的内容

#endif

上述预定义指令的含义是,如果没定义VERSION_CONTROL_H宏,则定义该宏,并展开头文件的内容。如果已经定义了VERSION_CONTROL_H宏,则不会展开头文件的内容,会直接跳过头文件中的内容。

       项目中肯定有多个头文件,这些头文件中用于被用来防止头文件被重复包含的宏名称是不能重复的,重复了会有问题的。在微软Visual Studio开发工具中,可以使用#pragma once指令来替代。

        为啥会出现头文件多次包含的问题呢?其实很好理解,比如头文件header2.h中包含header1.h头文件(假设头文件header1.h中没有添加防止被重复包含的预编译指令),test.cpp中既包含了header1.h头文件,也包含了header2.h头文件,这样就会出现两次包含header1.h的问题,就会出现头文件中的内容被定义两次的问题(包含头文件的地方可以想象成直接替换成头文件中的内容),所以要添加防止头文件中的内容被重复定义的问题。特别是再大型项目中,有多个头文件和cpp文件,很容易出现同一个头文件被包含多次的问题,所以必须要在头文件中添加防止头文件中的内容被重复定义的预编译指令。

       再就是,根据是否定义个某个宏去做不同的处理:

#ifdef RTC_VER
// ... // 定义了RTC_VER宏时的处理代码
#else
// ... // 没定义RTC_VER宏时的处理代码
#endif

4、#error指令   

       使用该指令可以在编译器编译期间在IDE的输出窗口中打印出一段字符串。比如我在头文件中添加一条这样的指令:

#error 1111111111111111111111111

编译时在输出窗口会输出上述字符串:

       该命令可以用在这样的场景下,比如一个项目代码的路径中有两个头文件都定义了同一个名字的宏(宏名称一样,宏值可能不一样),但不确定到底使用的是哪个头文件中定义的宏,就可以在一个头文件中添加:

#error 1111111111111111111111111

在另一个头文件中添加:

#error 222222222222222222222

然后重新编译代码,编译时就会在编译信息输出窗口中就会看到上述指令输出的字符串,输出了哪个字符串,就使用了对应的头文件。

5、编译器预置宏__FILE__、__LINE__和__FUNCTION__

       __FILE__用于指示本行代码所在源文件的文件名(完整路径),__LINE__用于指示本行代码所在源文件中的位置(行数),__FUNCTION__用于指示本行代码所在函数(函数名)。这几个宏在编译期间编译器就将之替换成对应的内容了,在打印日志时比较有用。

       使用这几个宏的测试代码如下:

void TestFunc()
{
    cout << "Current File: " << __FILE__ << endl; //Current File: d:\testdlg.cpp
    cout << "Current Line: " << __LINE__ << endl; //Current Line: 688
    cout << "Current Function: " << __FUNCTION__ << endl; //Current Function: TestFunc
}

6、#pragma指令

        该类指令用来设定编译器的状态或者指示编译器完成一些特定的动作,它支持不同的操作参数。注意该部分指令只有微软的C++编译器才支持,才能识别。

6.1、#pragma once指令

       在头文件的开始处添加这条指令,是为了保证头文件只被编译一次,以解决头文件被多次包含时的问题。也可以使用#ifndef/#define/#endif实现同样的效果,如下:

#ifndef VERSION_CONTROL_H
#define VERSION_CONTROL_H

// ... // 头文件的内容

#endif

6.2、#pragma message指令

        该指令能够让编译器在执行到该条指令时在编译信息输出窗口中输出一段信息,其使用方法为:#pragma message(“消息文本”)。通过这条指令我们可以方便地记录在是否在源代码中定义过某个宏,比如:

#define RTC_VER // 定义了宏RTC_VER 

// ......

#ifdef RTC_VER
#pragma message("Macro RTC_VER is defined") // 如果定义了宏RTC_VER,就输出:Macro RTC_VER is defined
#endif

6.3、#pragma warning指令

        该指令可以将指定编号的警告信息屏蔽掉。比如我们编写了如下的代码:

char szBuf[100] = { 0 };
strcpy( szBuf, "123456");

使用Visual Studio编译,会报出如下警告:

提示strcpy函数不安全,建议使用安全版本的字符串拷贝函数strcpy_s。

       如果我们代码写的没问题,我们就要使用strcpy,则可以将该类警告信息屏蔽掉,如下:

#pragma warning(disable : 4996)    // disable bogus deprecation warning

diasable后面跟的就是警告信息的类型编号。

6.4、#pragma comment指令

       该指令将一个注释记录放入一个对象文件或可执行文件中,其使用方法为:

#pragma comment(comment-type ,["commentstring"])

其中,comment-type 是一个预定义的标识符,指定注释的类型,包括compiler,exestr,lib,linker。比如我们在使用dll库之前需要将dll库对应的lib文件:(程序链接时使用)

#pragma comment(lib, "zlibwapi.lib")  

6.5、#pragma hdrstop指令

       该指令表示预编译头文件到此为止,后面的头文件不进行预编译。

6.6、#pragma resource指令

       该指令表示把指定文件中的资源加入工程,如

#pragma resource "*.dfm"

6.7、#pragma code_seg指令

        该指令能够设置程序中函数代码存放的代码段,开发驱动程序的时候会使用到。使用方法为:

#pragma code_seg(["section-name" [,"section-class"] ])。

6.8、#pragma data_seg指令

        该指令建立一个新的数据段并定义共享数据。一般用于DLL中,在DLL中定义一个共享的有名字的数据段,这个数据段中的全局变量可以被多个进程共享,否则多个进程之间无法共享DLL中的全局变量。其使用方法为:

#pragma data_seg("SharedData")
int nSharedVal; //共享数据
#pragma data_seg()

6.9、#pragma init_seg指令

         该指令用于控制全局对象的初始化顺序,其使用方法为:

#pragma init_seg({ compiler | lib | user | "section-name" [, func-name]} )

其中,init_seg后面括号中包含的参数有compiler编译器、 lib库、 user用户,和 user_defined_segment_name四种,前三个指令的初始化优先次序依次降低,但都先于普通的全局变量构造,如cout就是使用compiler级别构造的。比如:

#pragma init_seg(compiler)
#pragma init_seg(lib)
#pragma init_seg(user)
#pragma init_seg("user_defined_segment_name")

       pragma init_seg(compiler)是保留给微软C/C++ 运行库使用的,我们不应该使用它!在我们的代码里,如果希望一些对象先于其他对象初始化,我们可以在对应的cpp文件中使用 #pragma init_seg(lib) 指令,比如:

#pragma init_seg( lib )

CImage::CInitGDIPlus CImage::s_initGDIPlus;
CImage::CDCCache CUIImage::s_cache;

void CImage::ReleaseGDIPlus()
{
    s_initGDIPlus.ReleaseGDIPlus();
}

但要注意,一个源文件只能出现一次init_seg 指令。

6.10、#pragma pack指令

       该指令用来指定当前头文件中定义的结构体数据在内存中的对齐长度,比如:

#pragma pack(1)
struct TestStruct
{
    char a; 
    int b; 
};

cout << sizeof(TestStruct) << endl; 

上述代码是输出TestStruct结构体的长度,如果不使用#pragma pack指定对齐长度,则是8字节。此处添加了#pragma pack(1),则是1字节对齐,那结构体的长度就是5字节了。

相关文章:

  • 杰理强制升级工具4.0使用和原理解析
  • Vue3介绍和安装
  • Linux命令--权限(chmod、chown)--使用/实例
  • flink-sql所有语法详解
  • 【图像分割】基于matlab萤火虫算法图像聚类分割【含Matlab源码 2106期】
  • SQL 入门之第一讲——MySQL 8.0.29安装教程(windows 64位)
  • 用Python进行数学建模(一)
  • 力扣:669. 修剪二叉搜索树,今日份快乐
  • java毕业设计KTV点歌系统mybatis+源码+调试部署+系统+数据库+lw
  • [python] 基于diagrams库绘制系统架构图
  • 2022 年全国职业院校技能大赛(中职组) 网络安全竞赛试题D模块评分标准
  • C++ 语法基础课1 —— 变量、输入输出、顺序语句
  • M的编程备忘录之C++——map和set
  • 《Orange‘s 一个操作系统的实现》第六章
  • Spring Cloud 拉取 Nacos 中配置文件
  • [PHP内核探索]PHP中的哈希表
  • JavaScript 如何正确处理 Unicode 编码问题!
  • 【刷算法】求1+2+3+...+n
  • Brief introduction of how to 'Call, Apply and Bind'
  • Git学习与使用心得(1)—— 初始化
  • Javascript 原型链
  • Java基本数据类型之Number
  • PHP面试之三:MySQL数据库
  • Protobuf3语言指南
  • SpiderData 2019年2月25日 DApp数据排行榜
  • Twitter赢在开放,三年创造奇迹
  • webpack4 一点通
  • WebSocket使用
  • 给新手的新浪微博 SDK 集成教程【一】
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 小程序开发之路(一)
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • # 透过事物看本质的能力怎么培养?
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (一)WLAN定义和基本架构转
  • (译)计算距离、方位和更多经纬度之间的点
  • (转) 深度模型优化性能 调参
  • (转)Google的Objective-C编码规范
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • **python多态
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .net mvc部分视图
  • .net wcf memory gates checking failed
  • .NET4.0并行计算技术基础(1)
  • .net安装_还在用第三方安装.NET?Win10自带.NET3.5安装
  • [ vulhub漏洞复现篇 ] Django SQL注入漏洞复现 CVE-2021-35042
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce
  • [1525]字符统计2 (哈希)SDUT
  • [23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians
  • [8-27]正则表达式、扩展表达式以及相关实战