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

C++沉思:预处理和编译

预处理和编译

  • 条件编译源代码
    • 使用方式
    • 典型示例
    • 原理
  • 使用static_assert执行编译时断言检查
    • 使用方式
    • 原理

在C++中,编译是将源代码转换为机器代码并组织在目标文件中,然后将目标文件链接在一起生成可执行文件的过程。编译器实际上一次只处理一个文件,这个文件是由预处理器(编译器中处理预处理指令的部分)从单个源文件及其包含的所有头文件中生成的。

条件编译源代码

条件编译使开发人员能够维护单个代码库,但只考虑编译代码的某些部分,以生成不同的可执行文件(通常是为了在不同的平台或硬件上运行,或依赖于不同的库或版本)。

使用方式

若要条件编译部分代码,可以使用#if、#ifdef和#ifndef指令(与#elif、#else和#endif指令一起)。条件编译的一般形式如下:

#if condition1text1
#elif condition2text2
#elif condition3text3
#elsetext4
#endif

要定义用于条件编译的宏,可以使用以下方法之一:

  1. 在源代码中使用#define指令:

    #define VERBOSE_PRINTS
    #define VERBOSITY_LEVEL 5
    
  2. 使用编译器命令行选项
    对于g++而言:-D是一个选项,用于在编译时定义宏

    main.cpp

    #include <iostream>int main() {std::cout << "Hello, World!" << std::endl;#ifdef DEBUGstd::cout << "Debug mode is enabled." << std::endl;#elsestd::cout << "Debug mode is disabled." << std::endl;#endifreturn 0;
    }
    

    编译指令,定义宏DEBUG

    g++ -DDEBUG main.cpp -o main
    

    输出Debug mode is enabled.
    普通编译

    g++ main.cpp -o main
    

    输出Debug mode is disabled.

典型示例

  1. 头文件保护避免重复定义
    现在更多的使用:pragma once

    #ifndef HEADER_NAME
    #define HEADER_NAMEclass A {}
    #endif
    

    两者都可以实现头文件的防重复包含,但前者适用于所有遵循C/C++预处理器约定的编译器,后者则依赖于编译器的支持。

  2. 针对跨平台应用程序
    将带有编译器名称的消息打印到控制台

    void show_compiler() {
    #if defined _MSC_VERcout << "VC++\n";
    #elif defined __clang__cout << "Clang\n";
    #elif defined __GNUG__cout << "GCC\n";
    #elsecout << "unknown compiler\n";
    #endif
    }
    
  3. 针对多个架构的目标特定代码

    void show_architecture()
    {
    #if defined _MSC_VER#if defined _M_X64std::cout << "AMD64";#elif defined _M_IX86std::cout << "INTEL x86";#elif defined _M_ARMstd::cout << "ARM";#elsestd::cout << "unknown";#endif
    #elif defined __clang__ || __GNUC__#if defined __amd64__std::cout << "AMD64";#elif defined __i386__std::cout << "INTEL x86";#elif defined __arm__std::cout << "ARM";#elsestd::cout << "unknown";#endif
    #else#error Unknown compiler
    #endif
    }
    
  4. 特定于配置的代码
    有条件地编译调试和发布版本

    void show_configuration() {
    #ifdef _DEBUGcout << "debug\n";
    #else cout << "release\n";
    #endif
    }
    

原理

当使用预处理指令#if、#ifndef、#ifdef、#elif、#else和#endif时,编译器将至多选择一个分支,其主体将包含在编译单元中。这些指令的主体可以是任何文本,包括其他预处理指令。适用规则如下:

  1. #if、#ifdef和#ifndef必须用#endif匹配。
  2. #if指令可以有多条#elif指令,但只有一条#else指令,且#else必须是#endif之前的最后一条。
  3. #if、#ifdef、#ifndef、#elif、#else和#endif可以嵌套。
  4. #if指令需要一个常量表达式,而#ifdef和#ifndef则需要一个标识符。
  5. defined操作符可以用于预处理器常量表达式,但只能在#if和#elif指令中使用。
  6. defined(identifier)在定义identifier时为true,否则,它被认为是false。
  7. 定义为空文本的标识符被认为是有定义的。
  8. #ifdef identifier等价于#if defined(identifier)。
  9. #ifndef identifier等价于#if !defined(identifier)。
  10. defined(identifier)和defined identifier是等价的。
  11. 宏的名称在整个应用程序中必须是唯一的,否则,将只编译使用宏的第一个头文件中的代码

使用static_assert执行编译时断言检查

C++可以同时执行运行时和编译时断言检查。注意:C++版本需要高于>=C++11才会出现编译错误

  1. 运行时断言只有在程序运行时并且只有在控制流到达它们时才会被验证。当条件依赖于运行时数据时只能选择运行时断言。
  2. 编译时断言(条件可以在编译时求值)能够在开发阶段的早期通知某个特定条件未被满足。在C++11中,编译时断言是通过static_assert执行的。
  3. 静态断言检查常在模板元编程中用于验证模板类型的先决条件是否满足(例如类型是否为POD类型、可复制构造类型、引用类型等)。另一个典型用例是确保类型(或对象)具有预期的大小。

使用方式

使用 static_assert 声明来确保满足以下作用域中的条件:

  1. 命名空间作用域,本例验证item类的大小总是16

    struct alignas(8) item
    {int     id;bool    active;double  value;/* data */
    };static_assert(sizeof(item) == 16,"size of item must be 16 types");
    
  2. 类作用域,本例验证pod_wrapper只能与POD类型一起使用

    is_standard_layout_v 是 C++ 标准库 <type_traits> 中的一个模板元函数,它用于在编译时判断一个类型是否是标准布局类型。标准布局类型需要满足一系列规则,这些规则包括:

    • 没有虚函数(包括虚析构函数)
    • 没有虚基类
    • 所有非静态成员都是标准布局类型
    • 非静态成员之间没有相同的名称
    • 类的继承关系中没有相同的基类
    • 满足其他一些与对齐和大小相关的规则
    template <typename T>
    class pod_wrapper {static_assert(std::is_standard_layout_v<T>,"POD type expected!");T value;
    };struct point
    {int x;int y;/* data */
    };
    pod_wrapper<int>            w1;	//ok
    pod_wrapper<point>          w2;	//ok
    pod_wrapper<std::string>    w3;	//err
    
  3. 函数块作用域,本例验证函数模板是否只有整型参数

    #include <type_traits>template <typename T>
    auto mul(T const a, T const b) {static_assert(std::is_integral_v<T>::value,"Integral type expected");return a * b;
    }
    auto v1 = mul(1, 2);	//ok
    auto v2 = mul(1.2, 3.4);	//err
    

原理

static_assert是一个声明,但它没有引入新名称。这些声明的形式如下:

static_assert(condition, message);

该条件必须在编译时转换为布尔值,且消息必须是字符串字面量。在C++17中,该消息是可选的。当static_assert声明中的条件计算结果为true时,什么都不会发生;当条件的计算结果为false时,编译器生成一个包含指定消息(如果有的话)的错误。

http://www.lryc.cn/news/409344.html

相关文章:

  • 交通数据处理-计算途径某些路段的车辆数
  • 从0到1入门系列 | 崖山公开课再加码,三小时带你入门崖山数据库!
  • Powershell自定义带参数的别名
  • 文件操作相关的精讲
  • 05 循环神经网络
  • C#初级——条件判断语句、循环语句和运算符
  • Laravel路由模型绑定:简化依赖注入的艺术
  • 【vue前端项目实战案例】之Vue仿饿了么App
  • 冷热分离——Java全栈知识(36)
  • 了解Selenium中的WebElement
  • OpenCV facedetect 人脸检测官方示例项目配置
  • 自定义Laravel Artisan风格:打造个性化命令行体验
  • CTF之网站被黑
  • Electron学习笔记(一)基础环境
  • 【C语言】栈的实现(数据结构)
  • 前端三大主流框架对比
  • AOP~面向切面编程介绍
  • Android SurfaceFlinger——GraphicBuffer的提交(三十三)
  • 创维汽车滁州永通体验中心开业仪式暨超充车型区域上市会圆满成功
  • 【PHP】系统的登录和注册
  • 2024.7.29 刷题总结
  • WebSocket程序设计
  • ES(ElasticSearch)倒排索引
  • Android Studio Build窗口出现中文乱码问题
  • java生成随机数
  • 动态定制深度学习:Mojo模型与自定义训练算法的无缝切换
  • 昇思25天学习打卡营第19天|DCGAN生成漫画头像
  • 排序题目:按照频率将数组升序排序
  • 实分析与测度论问题的分类
  • 动态代理更改Java方法的返回参数(可用于优化feign调用后R对象的统一处理)