pragma -- 预处理指令应用详解

#Pragma是预处理指令,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#Pragma指令对每个编译器给出了一个方法,在保持与CC++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。
其格式一般为 : #Pragma Para
其中 Para  为参数,下面来看一些常用的参数。
(1)message  参数。  Message  参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#Pragma message( “消息文本” )
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了 _X86 这个宏可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 macro activated!”)
#endif
当我们定义了 _X86 这个宏以后,应用程序在编译时就会在编译输出窗口里显示“ _X86 macro activated! ”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。
 
(2) 另一个使用得比较多的 pragma 参数是 code_seg 。格式如:
#pragma code_seg( [/section-name/[,/section-class/] ] )
它能够设置程序中函数代码存放的代码段,使用没有 section-name 字符串的 #pragmacode_seg 可在编译开始时将其复位,当我们开发驱动程序的时候就会使用到它。
 
(3)#pragma once ( 比较常用)
只要在头文件的最开始加入这条指令就能够 保证头文件被编译一次 ,这条指令实际上在 VC6 中就已经有了,但是考虑到兼容性并没有太多的使用它。
 
(4)#pragma hdrstop 表示预编译头文件到此为止,后面的头文件不进行预编译。 BCB 可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。有时单元之间有依赖关系,比如单元 A 依赖单元 B ,所以单元 B 要先于单元 A 编译。你可以用 #pragma startup 指定编译优先级,如果使用了 #pragma package(smart_init)  BCB 就会根据优先级的大小先后编译。
 
(5)#pragma resource /*.dfm/ 表示把 *.dfm 文件中的资源加入工程。 *.dfm 中包括窗体外观的定义。
 
(6 #pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于:
#pragma warning(disable:4507 34) //  不显示 4507 34 号警告信息
#pragma warning(once:4385) // 4385 号警告信息仅报告一次
#pragma warning(error:164) //  164 号警告信息作为一个错误。
同时这个 pragma warning  也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
这里 n 代表一个警告等级 (1---4)
#pragma warning( push ) 保存所有警告信息的现有的警告状态。
#pragma warning( push, n) 保存所有警告信息的现有的警告状态,并且把全局警告
等级设定为 n
#pragma warning( pop ) 向栈中弹出最后一个警告信息,在入栈和出栈之间所作的
一切改动取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
//.......
#pragma warning( pop )
在这段代码的最后,重新保存所有的警告信息 ( 包括 4705 4706 4707)
 
7 pragma comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的 lib 关键字,可以帮我们连入一个库文件。
 
8 )•通过 #pragma pack(n) 改变 C 编译器的字节对齐方式
C 语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如 int long float 等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然对界( alignment )条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
例如,下面的结构各成员空间分配情况:
struct test
{
        char x1; //  偏移地址为 0
        short x2;//  偏移地址为 [2 3]
        float x3;//  偏移地址为 [4 7]
        char x4; //  偏移地址为 8
};
      结构的第一个成员 x1 ,其偏移地址为 0 ,占据了第 1 个字节。第二个成员 x2 short 类型,其起始地址必须 2 字节对界,因此,编译器在 x2 x1 之间填充了一个空字节。结构的第三个成员 x3 和第四个成员 x4 恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在 test 结构中,成员 x3 要求 4 字节对界,是该结构所有成员中要求的最大对界单元,因而 test 结构的自然对界条件为 4 字节,编译器在成员 x4 后面填充了 3 个空字节。整个结构所占据空间为 12 字节。更改 C 编译器的
缺省字节对齐方式在缺省情况下, C 编译器为每一个变量或是数据单元按其自然对界条件分配
空间。一般地,可以通过下面的方法来改变缺省的对界条件:
  • 使用伪指令 #pragma pack (n) C 编译器将按照 n 个字节对齐。
     使用伪指令 #pragma pack () ,取消自定义字节对齐方式。
         另外,还有如下的一种方式:
      __attribute((aligned (n))) ,让所作用的结构成员对齐在 n 字节自然边界上。如果结构中有成员的长度大于 n ,则按照最大成员的长度来对齐。
      __attribute__ ((packed)) ,取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
以上的 n = 1, 2, 4, 8, 16...  第一种方式较为常见。
应用实例
  在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的
方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修改起来
也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这
一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,
不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,
其它程序无需修改,省时省力。下面以 TCP 协议首部为例,说明如何定义协议结构。
其协议结构定义如下:
#pragma pack(1) //  按照 1 字节方式进行对齐
struct TCPHEADER
{
        short SrcPort; // 16 位源端口号
        short DstPort; // 16 位目的端口号
        int SerialNo; // 32 位序列号
        int AckNo; // 32 位确认号
        unsigned char HaderLen : 4; // 4 位首部长度
        unsigned char Reserved1 : 4; //  保留 6 位中的 4
        unsigned char Reserved2 : 2; //  保留 6 位中的 2
        unsigned char URG : 1;
        unsigned char ACK : 1;
        unsigned char PSH : 1;
        unsigned char RST : 1;
        unsigned char SYN : 1;
        unsigned char FIN : 1;
        short WindowSize; // 16 位窗口大小
        short TcpChkSum; // 16 TCP 检验和
        short UrgentPointer; // 16 位紧急指针
};
#pragma pack() //  取消 1 字节对齐方式
#pragma pack 规定的对齐长度,实际使用的规则是:
结构,联合,或者类的数据成员,第一个放在偏移为 0 的地方,以后每个数据成员的对齐,按照 #pragma pack 指定的数值和这个数据成员自身长度中,比较小的那个进行。
也就是说,当 #pragma pack 的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。
而结构整体的对齐,则按照结构体中最大的数据成员  #pragma pack 指定值 之间,较小的那个进行。
指定连接要使用的库
比如我们连接的时候用到了  WSock32.lib ,你当然可以不辞辛苦地把它加入到你的工程中。但是我觉得更方便的方法是使用  #pragma  指示符,指定要连接的库 :
#pragma comment(lib, "WSock32.lib")
 
 
 
#pragma C++
2007-06-16 17:00
解析 #pragma 指令  
在所有的预处理指令中, #Pragma  指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。 #pragma 指令对每个编译器给出了一个方法 , 在保持与 C C++ 语言完全兼容的情况下 , 给出主机或操作系统专有的特征。依据定义 , 编译指示是机器或操作系统专有的 , 且对于每个编译器都是不同的。  
其格式一般为 : #Pragma Para 
其中 Para  为参数,下面来看一些常用的参数。  

(1)message 
参数。  Message  参数是我最喜欢的一个参数,它能够在编译信息输出窗  
口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:  
#Pragma message(“
消息文本 ”) 
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。  
当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了 _X86 这个宏可以用下面的方法  
#ifdef _X86 
#Pragma message(“_X86 macro activated!”) 
#endif 
当我们定义了 _X86 这个宏以后,应用程序在编译时就会在编译输出窗口里显示 “_ 
X86 macro activated!”
。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了  
 

(2)
另一个使用得比较多的 pragma 参数是 code_seg 。格式如:  
#pragma code_seg( ["section-name"[,"section-class"] ] ) 
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。  

(3)#pragma once (
比较常用)  
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在 VC6 中就已经有了,但是考虑到兼容性并没有太多的使用它。  

(4)#pragma hdrstop
表示预编译头文件到此为止,后面的头文件不进行预编译。 BCB 可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。  
有时单元之间有依赖关系,比如单元 A 依赖单元 B ,所以单元 B 要先于单元 A 编译。你可以用 #pragma startup 指定编译优先级,如果使用了 #pragma package(smart_init)  BCB 就会根据优先级的大小先后编译。  

(5)#pragma resource "*.dfm"
表示把 *.dfm 文件中的资源加入工程。 *.dfm 中包括窗体  
外观的定义。  

(6)#pragma warning( disable : 4507 34; once : 4385; error : 164 ) 
等价于:  
#pragma warning(disable:4507 34) // 
不显示 4507 34 号警告信息  
#pragma warning(once:4385) // 4385
号警告信息仅报告一次  
#pragma warning(error:164) // 
164 号警告信息作为一个错误。  
同时这个 pragma warning  也支持如下格式:  
#pragma warning( push [ ,n ] ) 
#pragma warning( pop ) 
这里 n 代表一个警告等级 (1---4)  
#pragma warning( push )
保存所有警告信息的现有的警告状态。  
#pragma warning( push, n)
保存所有警告信息的现有的警告状态,并且把全局警告  
等级设定为 n  
#pragma warning( pop )
向栈中弹出最后一个警告信息,在入栈和出栈之间所作的  
一切改动取消。例如:  
#pragma warning( push ) 
#pragma warning( disable : 4705 ) 
#pragma warning( disable : 4706 ) 
#pragma warning( disable : 4707 ) 
//....... 
#pragma warning( pop ) 
在这段代码的最后,重新保存所有的警告信息 ( 包括 4705 4706 4707)  
7 pragma comment(...) 
该指令将一个注释记录放入一个对象文件或可执行文件中。  
常用的 lib 关键字,可以帮我们连入一个库文件。

(8)#pragma pack() 
我们知道在 VC 中,对于想结构体 Struct 这样的类型, VC 采用 8 字节对齐的方式,如果我们不想使用 8 字节对齐(在网络变成中经常需要这样),我们可以在结构体前面加上  
#pragma pack(1) 
struct 

...... 

#pragma pack( )
以下是另一个转载:
vc6 的时代头文件一般使用 ifndef define endif
vc7 的时代头文件一般成了 pragma once
不知道有没有人深究其中的意义
为什么有这样的代码,是为了头文件不被重复引用,那样编译器抱错的,这两种方法都是同样的目的,有没有区别呢?
还是举例来说明,可能有好几个库,每个库内部可能都有 public.h 这个文件,如果使用
ifndef public_h
define public_h
...
endif
那么当一个文件同时引用两个这样的库时,后一个库里的文件就不被编译了,而 pragma once 可以保证文件只被编译一次
看起来 pragma once ifndef define endif 要好,那么 ifndef define endif
的地方都 pragma once 好了。今天碰到了又一个例子,比如你有一个 zlib.h 在几个库都用到,而为了方便,把 zlib 每个目录下 copy 了一分,因为这个文件不会作修改,已经很完整了,这个时候如果使用 pragma once ,就会重复定义,看来 ifndef define endif 还是又派上用场的地方。
所以对于公有或者接口的文件,使用 ifndef define endif ,对于内部的文件使用 pragma once.
#pragma once 与  #ifndef #define #endif  的区别
  1. 对于#pragma once,根据MSDN解说,能够防止一个文件被多次包含。与#ifndef #define #endif形式的文件保护相比,前者是平台相关的,可移植性比较差,但是它效率更高,因为它不需要去打开包含的文件,就可以判断这个文件有没有被包含。当然这个工作是系统帮我们完成的。
  2. 后者的优点在于它是语言相关的特性,所以可移植性好。但是在包含一个文件的时候,只有打开这个文件,根据文件的保护宏是否已经被定义来判断此文件是否已经被包含过。效率相对较低。当然在#i nclude的时候,程序员也可以自己判断所要包含的文件的保护宏是否已经被定义,来决定是否要包含这个文件。类似下面的代码:
#ifndef FILE_H_
#include "file.h"
#endif
这样作可以得到较高的效率,而且保证可移植性。但是文件之间的依赖性较高,如果一个文件的保护宏改变的话,所有使用如上形式包含这个文件的文件都要修改。有悖于模块化的思想。
 
 
 
 
五. #pragma warning指令
该指令允许有选择性的修改编译器的警告消息的行为
指令格式如下:
#pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...]
#pragma warning( push[ ,n ] )
#pragma warning( pop )
主要用到的警告表示有如下几个:
once:只显示一次(警告/错误等)消息
default:重置编译器的警告行为到默认状态
1,2,3,4:四个警告级别
disable:禁止指定的警告信息
error:将指定的警告信息作为错误报告
如果大家对上面的解释不是很理解,可以参考一下下面的例子及说明

#pragma warning( disable : 4507 34; once : 4385; error : 164 ) 
等价于:
 
#pragma warning(disable:4507 34) // 
不显示4507和34号警告信息 
#pragma warning(once:4385)        // 4385号警告信息仅报告一次 
#pragma warning(error:164)        // 把164号警告信息作为一个错误。 
同时这个pragma warning 也支持如下格式:
 
#pragma warning( push [ ,n ] ) 
#pragma warning( pop ) 
这里n代表一个警告等级(1---4)。 
#pragma warning( push )保存所有警告信息的现有的警告状态。 
#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n。   
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的一切改动取消。例如:
 
#pragma warning( push ) 
#pragma warning( disable : 4705 ) 
#pragma warning( disable : 4706 ) 
#pragma warning( disable : 4707 ) 
#pragma warning( pop )
在这段代码的最后,重新保存所有的警告信息 ( 包括 4705 4706 4707)
在使用标准 C++ 进行编程的时候经常会得到很多的警告信息 , 而这些警告信息都是不必要的提示 , 所以我们可以使用 #pragma warning(disable:4786) 来禁止该类型的警告在 vc 中使用 ADO 的时候也会得到不必要的警告信息 , 这个时候我们可以通过 #pragma warning(disable:4146) 来消除该类型的警告信息
. pragma comment(...)
该指令的格式为:
 #pragma comment( "comment-type" [, commentstring] )
     
该指令将一个注释记录放入一个对象文件或可执行文件中 ,comment-type( 注释类型 ): 可以指定为五种预定义的标识符的其中一种
    
五种预定义的标识符为 :
     compiler:将编译器的版本号和名称放入目标文件中 , 本条注释记录将被编译器忽略
                 
如果你为该记录类型提供了 commentstring 参数 , 编译器将会产生一个警告
                  
例如 :#pragma comment( compiler )
     exestr:将 commentstring  参数放入目标文件中 , 在链接的时候这个字符串将被放入到可执行文件中 , 当操作系统加载可执行文件的时候 , 该参数字符串不会被加载到内存中 . 但是 , 该字符串可以被 dumpbin 之类的程序查找出并打印出来 , 你可以用这个标识符将版本号码之类的信息嵌入到可        执行文件中 !
     lib:这是一个非常常用的关键字 , 用来将一个库文件链接到目标文件中常用的 lib 关键字,可以帮我们连入一个库文件。  
 
例如: #pragma comment(lib, "user32.lib") 
该指令用来将user32.lib库文件加入到本工程中

     linker:将一个链接选项放入目标文件中
, 你可以使用这个指令来代替由命令行传入的或者在开发环境中设置的链接选项 , 你可以指定 /include 选项来强制包含某个对象 , 例如 :       #pragma comment(linker, "/include:__mySymbol")
               你可以在程序中设置下列链接选项
                          /DEFAULTLIB 
                          /EXPORT 
                          /INCLUDE 
                          /MERGE 
                         /SECTION

              这些选项在这里就不一一说明了
, 详细信息请看 msdn!
      user:将一般的注释信息放入目标文件中 commentstring 参数包含注释的文本信息 , 这个注释记录将被链接器忽略
      
例如 :     #pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )

你可能感兴趣的:(pragma -- 预处理指令应用详解)