• 语言基础(3):C++预编译


    一、预编译概述

    1.1 预编译定义

    • 预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。
    • 目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源码,检查预处理指令,对源代码进行相应转换,并删除程序中的注释和多余空白字符。
    • 预处理指令以#号开头,#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。

     

    1.2 预编译功能

    预编译指令通常用于文件包含,宏定义,编译控制(包括:条件编译、编译器警告配置、字节对齐、放置注释、上报消息等等),预编译还有一些更深层次的使用方法,下文将会介绍,综述预编译功能主要为:

    1. 上述基础功能
    2. 预编译头机制
    3. 元编程-代码生成

     

    二、常用预编译指令

    指令

    用途

    #

    空指令,无任何效果

    ##

    用于把参数链接到一起

    #include

    包含一个源代码文件

    #define

    定义宏

    #undef

    取消已定义的宏

    #if

    如果给定条件为真,则编译下面代码

    #ifdef

    如果宏已经定义,则编译下面代码

    #ifndef

    如果宏没有定义,则编译下面代码

    #elif

    如果前面的#if给定条件不为真,当前条件为真,则编译下面代码

    #endif

    结束一个#if……#else条件编译块

    #line

    重置代码行号和文件名

    #error

    停止编译并显示错误信息

    #pragma

    设定编译器的状态或者是指示编译器完成一些特定的动作

    #pragma warning

    选择性的修改编译器的警告消息行为

    #pragma pack

    设置结构体等字节长度对齐

    #pragma comment

    导入lib或dll

    #pragma deprecated

    抑制函数使用

    #pragma message

    弹出消息

    #pragma once

    保证文件编译一次

     

    2.1 文件包含

    #include "xxx.h"       // 优先搜索当前目录

    #include <yyy.h>     // 优先搜索系统目录

    PS:使用后定义符号,解决重复包含或者使用#pragma once

     

    2.2 宏定义

    2.2.1 标识宏定义

    #ifndf _XXX_H_         // 常用于头文件标识定义

    #define _XXX_H_

    // header file content

    ……

    #endif

     

    #undef _YYY_H_    // 如果需要也可取消一个标识定义

    不过头文件中一般时不要要取消定义的,在调试( #define _DEBUG_ )等其他用途中常会使用此功能;

     

    2.2.2 标识或功能宏

    还有一些常用的宏定义,用于定义常量或这完成某种功能,但要注意宏定义的形式,不然可能产生意想不到的效果,举例:

    #define PI 3.14                               // define const variable

    #define Cube(n) (n)*(n)*(n)             // calculate n's cube

     /* may work as above macro, but may cause unexpected result */

    #define Cube_2(n) n*n*n          

     

    举个栗子,对比使用宏Cube和Cube_2,我们就能发现区别:

    int num = 8 + 2;

    volume = Cube(num);          // 展开结果为:(8 + 2)*(8 + 2)*(8 + 2)

    volume2 = Cube_2(num);   // 展开结果为:8 + 2*8 + 2*8 + 2

    因此计算结果的大不相同;

     

    2.2.3 ##运算符

    ##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。看下面的例子:

    #define NUM(a,b,c) a##b##c

    #define STR(a,b,c) a##b##c

    main()

    {

    printf("%d ",NUM(1,2,3));

    printf("%s ",STR("aa","bb","cc"));

    }

    最后程序的输出为:

    123

    aabbcc

     

    2.3 编译控制

    2.3.1 #line改变行号和文件

    使用语法,#line number ["filename"],举例:

    #line 1000 "123.cpp"

    cout << "行号:" <<__LINE__ << " 文件名:" << __FILE__ << endl;

    此时输出结果为:

    行号:1000

    文件名:123.cpp

    此编译命令常用与调试,辅助定位代码问题

     

    2.3.2 #error上报编译错误

    在某些情况下,控制编译抛出错误,举个栗子:

    #ifdef XXX

    ...

    #error "XXX has been defined"

    #else

     

    2.3.3 #pragma系列

    1. 设置告警信息

    命令格式:

    #pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...])

    #pragma warning( push[ ,n ] ) // 保存警告信息的现有的警告状态 [,把全局警告等级设定n].  

    #pragma warning( pop )   // 弹出警告信息,在入栈和出栈之间所作的一切改动取消

    举个栗子:

    /* 不显示4507和34号警告信息,4385号警告只显示一次,164号警告作为错误 */

    #pragma warning( disable : 4507 34; once : 4385; error : 164 )

     

    1. 字节对齐
    • 数据成员对齐规则:struct或union的数据成员,第一个数据成员offset为0,以后每个数据成员的对齐按照#pragma pack指定的数值和数据成员自身长度中,比较小的那个进行;
    • 结构或联合整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行;

    #pragma pack(x) // x is int number

    struct A

    {

    char x;

    double y;

    int z;

    }a;

    sizeof(a)==13      // if x = 1

    sizeof(a)==14      // if x = 2

    sizeof(a)==16      // if x = 4

    sizeof(a)==24     // if x = 8default 相当于#pragma pack()

     

    1. 导入文件

    #pragma comment( lib, "xxapi" ) // 动态导入 xxapi.lib

     

    1. 抑制老代码

    #pragma deprecated

    When the compiler encounters a deprecated symbol, it issues C4995:

    void func1(void) {}

    void func2(void) {}

    int main() 

    {

       func1();

       func2();

     

       #pragma deprecated(func1, func2)

       func1();   // C4995

       func2();   // C4995

    }

     

    1. 上报编译消息

    #pragma message("This is a test msg.")

     

     

    三、预编译进阶使用

    C++使用“头文件-源文件”的编译模型,每个源文件为一个编译单元,产生一个obj文件,然后所有obj被link到一起,生成exe文件。

     

    在较大的系统软件中,有数以万的源文件,而每个头文件可能会包含数十甚至上百个头文件,

    在每一个编译单元,这些头文件都会被从硬盘读进来一遍,然后被解析一遍,无数头文件的重复load与解析以及密集的磁盘操作,严重拖慢了程序的编译速度。

     

    针对这个问题,有多种优化方式 [ref: C++ 预编译解析 ], 这里我们只看预编译头,是如何在这个问题上做出贡献的:

    • 将所有稳定代码放入"stdafx.h",VS工程默认放入了"targetver.h" windows平台的,“stdio.h”VC编译器的,“tchar.h”支持宽字符的等代码,根据实际加入自己的;
    • stdafx.cpp 包含 stdafx.h, 设置stdafx.cpp文件的属性,预编译头设置为 创建, 其他cpp文件按照实际属性设置为 使用 或者 不使用
    • 工程对预先编译的代码进行编译,会生成一个pch文件(precompiled header),在首次编译生成pch文件之后,就不会再编译stdafx.cpp的内容,从而达到加快编译速度的目的;

     

    四、预编译元编程

    此处不探讨此功能,参见:

    C/C++ 预处理元编程

     

  • 相关阅读:
    php类型运算符
    今天我开始写自己的东西
    挑选简历
    SQL Server和Oracle数据库索引介绍
    排序算法分析与设计实验
    软件框架 转
    【转】Ajax的原理和应用
    Web Service
    [转]异地分布式敏捷软件开发(Distributed Agile Software Development)
    [转]如何有效的使用C#读取文件
  • 原文地址:https://www.cnblogs.com/wnwin/p/9602047.html
Copyright © 2020-2023  润新知