预处理 #pragma 命令详解

本文详细介绍了C++中#pragma预处理器指令的常用功能,包括#pragmaonce防止多次包含、pragmawarning控制编译器警告、pragmacomment与library和linker选项、pragmaregion折叠代码、pragmaoptimize代码优化、pragmamessage传递信息、OpenMP并行指令以及pragmapack调整内存对齐和pragmaalias别名设置。
摘要由CSDN通过智能技术生成

目录

1.引言        

2.#pragma once

3.#pragma waring(...)

4.#pragma comment

4.1.lib

4.2.linker

5.#pragma region … /endregion …

6.#pragma optimize

7.#pragma message (message string)

8.#pragma omp parallel for

9.#pragma pack([ show ] | [ push | pop ] [, identifier ] , n)

10.#pragma alias(…)

11.#pragma code_seg

12.总结


1.引言        

        Pragma是一个预处理器指令,以#pragma开头,#pragma指令可用于条件语句以提供预处理器功能,或为编译器提供实现所定义的信息。用于告诉编译器执行特定的操作或者忽略特定的警告。

        #pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。

        下面就#pragma平常用的比较多的用法讲解一下。

2.#pragma once

作用:指定该文件在编译代码文件时仅由编译器包含或打开一次。使用#pragma once可减少生成次数,和使用预处理宏定义来避免多次包含文件的内容的效果是一样的,但是需要键入的代码少,可减少错误率,例如:

//使用pragma once
#pragma once

它的功能相当于: 

//使用宏定义方式
#ifndef _MY_TEST_HEADER_H_
#define _MY_TEST_HEADER_H_

#endif // !_MY_TEST_HEADER_H_

3.#pragma waring(...)

启用编译器警告消息的行为和选择性修改,语法为:

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

#pragma warning( push[ , n ] )    
#pragma warning( pop )

warning-specifier能够是下列值之一:

#pragma warning( disable : 4507 34; once : 4385; error : 164 )   //这1行跟下面3行效果一样

#pragma warning( disable : 4507 34 )     //不发出4507和34警告,即有4507和34警告时不显示
#pragma warning( once : 4385 )          //4385警告信息只报告一次
#pragma warning( error : 164 )          //把164警告信息作为一个错误

注意:对于范围4700-4999内的警告编号(与代码生成相关联),在编译器遇到函数的左大括号时生效的警告状态将对函数的其余部分生效。在函数中使用waring杂注更改编号大于4699的警告的状态只会在函数末尾之后生效。以下示例演示如何正确放置waring杂注来禁用代码生成警告消息然后还原该消息。

#pragma warning(disable:4700)  //不发生4700警告
void Test()         //这里之前的状态管用,若把上面一行移动到函数里面,则照常发生警告
{  
   int x;  
   int y = x;       //没有C4700的警告
   #pragma warning(default:4700)   //4700的警告恢复到默认值
}  
  
int main() 
{  
   int x;  
   int y = x;   //发生C4700的警告
}

下面来看warning推送和弹出的用法,语法为:

#pragma warning( push [ ,``n ] )    
#pragma warning( pop )

其中 n 表示警告等级(1 到 4)

warning( push )指令存储每个警告的当前警告状态。
warning( push, n)指令存储每个警告的当前状态并将全局警告级别设置为 n。
warning( pop )指令 弹出推送到堆栈上的最后一个警告状态。
在 push 和 pop 之间对警告状态所做的任何更改都将被撤消。

#pragma warning( push )  
#pragma warning( disable : 4705 )  
#pragma warning( disable : 4706 )  
#pragma warning( disable : 4707 )  
// Some code                          //代码的书写,这里不会发出4705、4706、4707的警告
#pragma warning( pop )               //会将每个警告(包括4705、4706、4707)的状态还原为代码开始的状态

当你编写头文件时,你能用push和pop来保证任何用户修改的警告状态不会影响正常编译你的头文件。在头文件开始的地方使用push,在结束地方使用pop。例如,假定你有一个不能顺利在4级警告下编译的头文件,下面的代码改变警告等级到3,然后在头文件的结束时恢复到原来的警告等级。

#pragma warning( push, 3 )    
// Declarations/ definitions            //要书写的代码
#pragma warning( pop )

4.#pragma comment

4.1.lib

用于指导编译器进行库的链接,以确保程序能够正确地与外部库进行链接。这在开发大型项目时尤为重要。通过#pragma comment指令,我们可以告诉编译器要链接的库的名称。如:

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

4.2.linker

在目标文件中放置连接程序选项。你可以用这个描述类型指定连接程序选项来代替在Project Setting对话框中Link页内的选项。例如,你可以指定/include选项以强迫包含一个符号:

#pragma comment(linker, "/include:__mySymbol")

5.#pragma region … /endregion …

#pragma region是Visual C++中特有的预处理指令。它可以让你折叠特定的代码块,从而使界面更加清洁,便于编辑其他代码。折叠后的代码块不会影响编译。你也可以随时展开代码块以进行编辑等操作。如:

int main()
{
...
#pragma region demo_region        //这里前面会有折叠符,折叠时提示 demo_region所写内容
int a = 10;
int b = 20;
int c = ADD( a + b ); 
#pragma endregion demo_region        //需要跟#pragma region配套使用
....
}

折叠代码块的方法:如同Visual C++中折叠函数、类、命名空间,当代码被包含在如上所述的指令之间后,#pragma region这一行的左边会出现一个“-”号,单击以折叠内容,同时“-”号会变成“+”号,再次单击可以展开代码块。

6.#pragma optimize

首先,让我们来了解一下如何通过Pragma指令对代码进行优化。在C++编程中,我们经常需要追求代码的高效性和性能。Pragma提供了一系列指令,可以告诉编译器如何对代码进行优化。例如,#pragma optimize指令可以告诉编译器在编译过程中对代码进行优化,以达到更好的执行效果。如:

#pragma optimize(3, on)

#pragma optimize("", off)  
// 一些不需要优化的代码  
#pragma optimize("", on)

7.#pragma message (message string)

不中断编译的情况下,发送一个字符串文字量到标准输出。message编译指示的典型运用是在编译时显示信息,例如:

#if _M_IX86 >= 500          //查看定义的宏有没有大于500,或者有时用作检查有没有定义宏
#pragma message("_M_IX86 >= 500")  
#endif

message string参数可以是扩展到字符串的宏,您可以通过任意组合将此类宏与字符串串联起来,例如:

#pragma message( "Compiling " __FILE__ )           //显示被编译的文件
#pragma message( "Last modified on " __TIMESTAMP__ )     //文件最后一次修改的日期和时间

如果在message杂注中使用预定义的宏,则该宏应返回字符串,否则必须返回该宏的输出转换为字符串,例如:

#define STRING2(x) #x  
#define STRING(x) STRING2(x)  
  
#pragma message (__FILE__ "[" STRING(__LINE__) "]: test")   //注意把行号转成了字符串

8.#pragma omp parallel for

OpenMP并行指令,OpenMP(Open Multi-Processing)是一套用于并行编程的API,而#pragma omp指令就是用来指导编译器进行OpenMP并行化的。通过在循环、函数等代码块前加上#pragma omp,可以让编译器自动并行化该代码块,充分利用多核处理器的性能。

#pragma omp parallel for
for (int i = 0; i < n; ++i) {
    // 并行化的代码
}

9.#pragma pack([ show ] | [ push | pop ] [, identifier ] , n)

指定结构、联合和类成员的封装对齐。其实就是改变编译器的内存对齐方式。这个功能对于集合数据体使用,默认的数据的对齐方式占用内存比较大,可进行修改。

在没有参数的情况下调用pack会将n设置为编译器选项/zp中设置的值。如果未设置编译器选项,windows默认为8,linux默认为4。

具体的使用方法为,其中n的取值必须是2的幂次方,即1、2、4、8、16等:

  1. #pragma pack(show) 以警告信息的形式显示当前字节对齐的值.

  2. #pragma pack(n) 将当前字节对齐值设为 n .

  3. #pragma pack() 将当前字节对齐值设为默认值(通常是8) .

  4. #pragma pack(push) 将当前字节对齐值压入编译栈栈顶.

  5. #pragma pack(pop) 将编译栈栈顶的字节对齐值弹出并设为当前值.

  6. #pragma pack(push, n) 先将当前字节对齐值压入编译栈栈顶, 然后再将 n 设为当前值.

  7. #pragma pack(pop, n) 将编译栈栈顶的字节对齐值弹出, 然后丢弃, 再将 n 设为当前值.

  8. #pragma pack(push, identifier) 将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier .

  9. #pragma pack(pop, identifier) 将编译栈栈中标识为 identifier 位置的值弹出, 并将其设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃.

  10. #pragma pack(push, identifier, n) 将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier, 再将 n 设为当前值.

  11. #pragma pack(pop, identifier, n) 将编译栈栈中标识为 identifier 位置的值弹出, 然后丢弃, 再将 n 设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃.

注意: 如果在栈中没有找到 pop 中的标识符, 则编译器忽略该指令, 而且不会弹出任何值.

使用时最好是成对出现的,要不容易引起错误,设置之后记得用完给恢复,如:

#pragma pack(n)     //设置以n个字节为对齐长度
struct 
{
    int  ia;
     char cb;
}
#pragma pack ()      //弹出n个字节对齐长度,设置默认值为对齐长度

如果想给单独一个结构体设置对齐长度还可以使用C++ 11 标准中的alignas。

但是不是设置#pragma pack(n)就按照设置的字节进行对齐呢?其实并不是这样的,实际的对齐字节数要符合以下规则:
1、若设置了对齐长度n,实际对齐长度=Min(设置字节长度,结构体成员的最宽字节数)。若没有设置n,实际对齐长度 = Min(结构体成员的最宽字节数,默认对齐长度)。
2、每个成员相对于首地址的偏移量(offset)都是实际对齐长度的整数倍,若不满足编译器进行填充。
3、数据集合的总大小为实际对齐长度的整数倍,若不是编译器进行填充。

假设默认对齐为8时,看几个例子:

#pragma pack(n)
struct Stu1
{
    short      sa;    //2个字节
    char     cb;    //1个字节
    int        ic;    //4个字节
    char     cd;    //1个字节
}
#pragma pack()

cout << sizeof(Stu1) << endl;

若n为1:实际对齐长度为1 = Min(1,8)。这个就不用解释了,相当于各个元素相加,总长度为8。
若n为2:实际对齐长度为2 = Min(2,8)。sa占两个字节,不需要补齐。cb首地址偏移为2个字节,满足规则二。ic的首地址偏移3(2+1)个字节,不能满足规则二,填充一个字节到4。cd的首地址偏移为8个字节,满足规则。现在相加的8+1=9个字节,不满足规则三,填充一个字节,总长度为10;
若n为4:实际对齐长度为4 = Min(4,8)。这个就不用介绍了,默认时就用的这个。总长度为12。
若n>4:实际对齐长度为 4 = Min(设置字节长度,结构体成员的最宽字节数)。总长度为12。

10.#pragma alias(…)

指定 short_filename 将用作 long_filename 的别名,语法为:

#pragma include_alias( "long_filename ", "short_filename" )  
#pragma include_alias( <long_filename>, <short_filename> ) 

要搜索的别名必须完全符合规范,无论是大小写、拼写还是双引号或尖括号的使用。 include_alias 杂注对文件名执行简单的字符串匹配;将不执行任何其他文件名验证。 例如:

#pragma include_alias( "api.h", "c:\version1.0\api.h" )  
#pragma include_alias( <stdio.h>, <newstdio.h> )  
#include "api.h"  
#include <stdio.h>  

11.#pragma code_seg

语法:

#pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] )

该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序,函数在.obj文件中默认的存放节为.text节,如果code_seg没有带参数的话,则函数存放在.text节中。
        push (可选参数) 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名
        pop(可选参数) 将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名
        identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈
        "segment-name" (可选参数) 表示函数存放的节名
例如:

//默认情况下,函数被存放在.text节中
void func1() {                   // stored in .text
}
//将函数存放在.my_data1节中
#pragma code_seg(".my_data1")
void func2() {                   // stored in my_data1
}
//r1为标识符,将函数放入.my_data2节中
#pragma code_seg(push, r1, ".my_data2")
void func3() {                   // stored in my_data2
}
int main() {
}

12.总结

        总的来说,#pragma 是一个强大的工具,但应该谨慎使用,确保它不会导致代码在不同编译器或平台上的不可移植性。

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值