【C/C++】详解 | #pragma预处理器

文章目录

  • § - 1 前言
  • § - 2 简介
  • § - 3 基本语法
  • 一、message参数
  • 二、once参数
  • 三、hdrstop参数
  • 四、code_seg参数
  • 五、warning参数
  • 六、pack参数
  • 八、intrinsic参数
  • 七、function参数
  • 八、init_seg参数
  • 九、inline_depth参数
  • 十、auto_inline参数
  • 十一、check_stack
  • 十二、const_seg参数
  • 十三、bss_seg参数
  • 十四、alloc_text参数
  • 结语


§ - 1 前言

在阅读本文前,您需要知道:

  • #define预处理器
  • 条件预处理器

§ - 2 简介

有一说一,所有预处理其中,#pragma应该最复杂了。

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

听着很绕对吧,我们慢慢来。

§ - 3 基本语法

#pragma的基本语法是:

#pragma PARA

其中PARA是命令参数,可以是以下值(仅列出常用命令):

P A R A
alloc_text comment init_seg1 optimize
auto_inline component inline_depth pack
bss_seg data_seg inline_recursion pointers_to_members
check_stack function intrinsic setlocale
code_seg hdrstop message vtordisp
const_seg include_alias once warning

以下,正文。


一、message参数

message可以在不中断编译的情况下发送 字面字符串常量标准输出
它通常用于编译时显示一些信息。
语法如下:

#pragma message(msg·string)
//注:【msg·string】意思即参数msg需要string类型
//forC:string即char*/char[],字符串

其中,msg即发送的信息。它必须是string(即char[]char*
【实例】以下代码将在编译时发送 hello world到标准输出并在运行时输出HELLO WORLD!!!2

#include
//forC: #include 
#pragma message("hello world\n")
int main(){
	std::cout<<"HELLO WORLD!!!";
	//forC: printf("HELLO WORLD!!!");
	return 0;
}

你能够用字符串文字量和宏(但必须指示为字符串形式)的任何组合来构造(中间要有空格):

#define MAXN 114514
#pragma message("MAXN:"MAXN)//**非法**!!!
#define MAXN "114514"
#pragma message("MAXN:"MAXN)//**警告**!!!
/*警告:
warning: invalid suffix on literal; C++11 requires a space between literal and string macro
*/
#define MAXN "114514"
#pragma message("MAXN:" MAXN)//合法
/*输出:
MAXN:114514
【不会记录二者间的空格】
*/
#pragma message("Hello" " " "World" "!" "\n")
//Hello World!




二、once参数

once参数在标头文件(*.h / *.c / *.cpp)的开头使用,目的是防止该文件被包含(#include)多次,效果同#ifndef - #define - ... - #endif




三、hdrstop参数

hdrstop(即HeaDeR STOP),表示仅编译这前的 头文件,后方不再编译。




四、code_seg参数

code_seg,网上各种说法不一,总结一下,

code_seg参数可以设置程序中函数代码存放的代码段

指定函数在.obj文件中存放的节,函数在.obj文件中默认的存放节为.text节,如果code_seg没有带参数的话,则函数存放在.text节中。

它的语法是:3

#pragmacode_seg(\
	[\
		[{ push | pop},]										\
		[ identifier·idt,]\
	]															\
	[\
		segment-name·string 									\
		[, segment-class·string ]\
	]															\
)

参数:

  • p u s h push push p o p pop pop 【可选】:
    push将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名;
    pop(可选参数)将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名。
  • i d e n t i f i e r identifier identifier:标识符(不能是C/C++关键字、字面常量、宏)【可选】,当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈。
  • s e g m e n t segment segment - n a m e name name :字符串【可选】,表示函数存放的节名4
  • s e g m e n t segment segment - c l a s s class class :字符串【可选】,表示函数存放类名。

实例:

//默认情况下,函数被存放在.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
}

/*------------------------------*/

//例如
#pragma  code_seg(“PAGE”)
//作用是将此部分代码放入分页内存中运行。

#pragma  code_seg()
//将代码段设置为默认的代码段

#pragma  code_seg("INIT")
//加载到INIT内存区域中,成功加载后,可以退出内存

关于分页内存,请参考分页内存与非分页内存。




五、warning参数

语法:

#pragma warning(														\
	warning-specifier·kyw : warning-order[list]·int						\
	[																	\
		;																\
		warning-specifier·kyw : warning-order[list]·int					\
		[...]															\
	]																	\
)
#pragma warning( push [, warning-lv] )
#pragma warning( pop )

参数:

  • w a r n i n g warning warning - s p e c i f i e r specifier specifier · 标识符,可以为:
    • o n c e once once —— 只显示指定信息一次。
    • d e f a u l t default default —— 对指定信息应用默认的编译程序选项。
    • 1 | 2 | 3 | 4 —— 对指定信息引用给定的警告等级。
    • d i s a b l e disable disable —— 不显示指定信息。
    • e r r o r error error —— 将指定信息作为错误提出。
  • w a r n i n g warning warning - o r d e r order order · int,可以是任意警告编号。

你可以同时对多组编号操作,如下:

#pragma warning( 		\
	disable : 4507 34; 	\
	once : 4385; 		\
	error : 164 		\
)
/* 请注意,行末的【\】是必须的,
 * 如果没有它则意味着#pragma的结束,编译器无法理解后面的参数
 */
//因此,除非必须,请尽量将它们写在一行

这相当于:

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

对于那些关于代码生成的,大于4699的警告标号,warning编译指示仅在函数定义外时有效。如果指定的警告编号大于4699并且用于函数内时被忽略。

【实例】- 使用warning参数禁止,再解除警告。

int a;
#pragma warning( disable : 4705 )
void func(){
	cout<<"HELLO WORLD!!!";
	//forC: printf("HELLO WORLD!!!");
    a;
}
#pragma warning( default : 4705 )

int main(){
	cout<<"hello world!\n";
	//forC: printf("hello world!\n");
	func();
	return 0;
}

输出:

hello world!
HELLO WORLD!!!

可以看出,对于被禁止的警告,编译器将跳过警告语句。


关于warning的另外两种方式:

#pragma warning( push [, warning-lv·int1,4] )
#pragma warning( pop )

其中, w a r n i n g warning warning - l v lv lv 是警告等级,为1 | 2 | 3 | 4
warning(push)表示保存当前所有警告方法,warning(push,lv)表示保存警告状态并将 全局警告等级 设置为lv
warning(pop)将上一个设置的警告状态取消(可以看做是一个堆栈,将顶元素弹出),任何在pushpop间设定的状态状态将被取消,因此,在设定状态后使用warning(push)是明智的选择。
考虑以下代码:

#pragma warning( push )
#pragma warning(disable : 4507 34)
#pragma warning(once : 4385)
#pragma warning(error : 164)
/* Some Code */
#pragma warning( pop )

运行完后,所有的警告状态都将被取消而非仅取消error:164
以下:

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

//***
#pragma warning( push )
//***

/* Some Code */
#pragma warning( pop )

此时仅error:164会被取消。


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

#pragma warning( push , 3 )
//Body of hdr
#pragma warning( pop )




六、pack参数

参考:#pragma pack

该参数指定结构和联合成员的紧缩对齐。尽管用/Zp选项设定整个翻译单元的结构和联合成员的紧缩对齐,可以用pack编译指示在数据说明层次设定紧缩对齐。从出现该编译指示后的第一个结构或者联合说明开始生效。这个编译指示不影响定义。

当你使用#pragma pack(n),其中n是1,2,4,8或者16,第一个以后的每个结构成员保存在较小的成员类型或者n字节边界上。如果你使用没有参数的#pragma pack,结构成员将被紧缩到由/Zp指定的值。默认的/Zp紧缩的大小是/Zp8。

语法:

//一般使用
#pragma pack( [ n ] )
//完整
#pragma pack( [ [ { push | pop } ] [ , identifier ] ] [ n ] )

该语法允许你将使用不同紧缩编译指示的组件合并到同一个翻译单元内。

每次出现有push参数的pack编译指示将保存当前的紧缩对齐值到一个内部的编译程序堆栈。编译指示的参数列表从左向右读取。如果你使用了push,当前紧缩值被保存。如果你提供了一个n值,这个值将成为新的紧缩值。如果你指定了一个你选定的标示符,这个标示符将和新的紧缩值关联。

每次出现有pop参数的pack编译指示从内部编译程序堆栈顶部取出一个值并将那个值作为新的紧缩对齐。如果你用了pop,而内部编译程序堆栈是空的,对齐值将从命令行得到,同时给出一个警告。如果你用了pop并指定了n的值,那个值将成为新的紧缩值。如果你用了pop并指定了一个标示符,将移去所有保存在堆栈中的的值直到匹配的找到匹配的标示符,和该标示符关联的紧缩值也被从堆栈中移出来成为新的紧缩值。如果没有找到匹配的标示符,将从命令行获取紧缩值并产生一个1级警告。默认的紧缩对齐是8。

使用后者的语法允许你编写头文件保证在使用头文件之前和其后的紧缩值是一样的:

//文件:hdr.h
#pragma pack(push,enter_hdr)
//BODY
#pragma pack(pop,enter_hdr)
//结束

上述代码使得进入头文件时将当前紧缩值和标示符enter_hdr关联并推入,被记住。在头文件尾部的pack编译选项移去所有在头文件中可能遇到的紧缩值并移去和enter_hdr关联的紧缩值。这样头文件保证了在使用头文件之前和其后的紧缩值是一样的。

类似地,你可以在包含不同头文件时使用不同紧缩值(如果文件内部没有定义):

#pragma pack(push,ent_hdr_)
#include"hdr.h"
#pragma pack(pop,ent_hdr_)

上述代码保护了hdr.h中的紧缩值。



八、intrinsic参数

语法:

#pragma intrinsic( [ func1 [ , func2 [...] ] ] )

该预处理将参数列表中的函数名(无需带【()】)声明为内含函数(内联),见内含函数

intrinsic编译提示指定对在编译指示参数表中函数调用是内含的。 编译程序像嵌入代码一样生成内含函数,而不是函数调用。 下面列出了 具有内含形式的库函数。 一旦遇到 intrinsic 编译指示, 它从第一个包含指定内含函数的函数定义开始起作用。 作用持续到源文件尾部或者出现包含相同内含函数的 function 编译指示。

以下函数具有内含形式:

_disable _enable _inp _inpw _lrotl _lrotr
_outp _outpw _rotl _rotr _strset abs
fabs labs memcmp memcpy memset strcat
strcmp strcpy strlen - - -

请注意,

下列浮点函数没有内含形式:( 然而它们具有直接将参数通过浮点芯片传送而不是推入程序堆栈的版本。)

acos asin cosh fmod pow sinh tanh

当你同时指定/Oi 和/Og 编译程序选项(或者任何包含/Og, /Ox, /O1 和/O2 的选项) 时下列浮点函数具有真正的内含形式:

atan exp log10 sqrt atan2 log sin tan cos

你可以用编译程序选项/Op 或/Za 来覆盖真内含浮点选项的生成。 在这种情况下, 函数会像一般库函数一样被生成, 同时直接将参数通过浮点芯片传送而不是推入程序堆栈。




七、function参数

语法:

#pragma function( func1 [ , func2 [...] ] )

内含函数的执行是将函数代码嵌入调用处(并非直接调用),使用function显示地强制调用该函数。其持续作用到源文件的尾部或者出现对同一个内含函数指定intrinsic编译指示。function编译指示只能用于函数外——在全局层次。




八、init_seg参数

【注:此预编译提示仅C++特有】
语法:

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

该编译提示是指定影响启动代码执行的关键字或代码段。

因为全局静态对象的初始化可以包含执行代码,所以你必须指定一个关键字来定义什么时候构造对象。在使用需要初始化的动态连接库(DLL)或程序库时使用init_seg编译指示是尤其重要的。

参数选项:

  • c o m p i l e r compiler compiler —— 由Microsoft C运行时间库保留。在这个组中的对象将第一个构造。
  • l i b lib lib —— 用于第三方类库开发者的初始化。在这个组中的对象将在标记为构造compiler的对象之后,其它对象之前构造。
  • u s e r user user —— 用于任何其它用户。在这个组中的对象将最后构造。
  • s e l e c t i o n selection selection - n a m e name name · 字符串 —— 允许显式地指定初始化段。在用户指定的section-name中的对象将不会隐式地构造,而它们的地址将会被放置在由section-name命名的段中。
  • f u n c func func - n a m e name name · 字符串 【可选】—— 指定当程序退出时,作为atexit函数调用的函数。这个函数必须具有和atexit函数相同的形式:(如果你需要延迟初始化,你能够选择指定显式的段名。随后你必须调用每个静态对象的构造函数。)
    int func_name(void (__cdecl *)(void));
    




九、inline_depth参数

语法:

#pragma inline_depth( dep·int[0,255])

该编译提示通过控制能够被扩展的一系列函数调用(从0到255次)来控制嵌入函数扩展的发生次数,这个编译指示控制用inline,__inline标记的或在/Ob2选项下能自动嵌入的嵌入函数。

inline_depth编译指示控制能够被扩展的一系列函数调用。例如,如果嵌入深度是4,并且如果A调用B然后调用C,所有的3次调用都将做嵌入扩展。然而,如果设置的最近一次嵌入深度是2,则只有A和B被扩展,而C仍然作为函数调用。

为了使用这个编译指示,你必须设置编译程序选项/Ob为1或者2。用这个编译指示指定的深度设定在该指示后面的第一个函数开始生效。如果你在括号内不指定一个值,inline_depth设置嵌入深度到默认值8。

在扩展时,嵌入深度可以被减少而不能被增加。如果嵌入深度是6,同时在扩展过程中预处理程序遇到一个inline_depth编译指示设置为8,则深度保持为6。

嵌入深度0将拒绝嵌入扩展,深度255将设置在嵌入扩展时没有限制。如果用一个没有指定值的编译指示,则使用为默认值。




十、auto_inline参数

语法:

#pragma auto_inline( { on | off } )

当指定off时将任何一个可以被考虑为作为自动嵌入扩展候选的函数排除出该范围。为了使用auto_inline编译指示,将其紧接着写在一个函数定义之前或之后(不是在其内部)。该编译指示将在其出现以后的第一个函数定义开始起作用。auto_inline编译指示对显式的inline函数不起作用。



十一、check_stack

语法:

#pragma check_stack( [ { on | off } ] )
#pragma check_stack( [ { + | - } ] )

当指定为off|-时编译程序关闭堆栈探测,若是on|+则打开堆栈探测,对于未给出参数的,以默认设置决定。
如果你没有给出check­_stack编译指示的参数,堆栈检查将恢复到在命令行指定的行为:

使用/Gs编译? 结果(前提是未给出参数,否则按参数)
后续的函数关闭堆栈检查
后续的函数开启堆栈检查




十二、const_seg参数

语法:

#pragmaconst_seg(\
	[\
		[{ push | pop},]										\
		[ identifier·idt,]\
	]															\
	[\
		segment-name·string 									\
		[, segment-class·string ]\
	]															\
)

类似于code_seg,它指定用于常量数据的默认段。

用const_seg编译指示分配的数据不包含任何关于其位置的信息。

第二个参数segment-class是用于兼容2.0版本以前的Visual C++的,现在将忽略它。




十三、bss_seg参数

语法:

#pragmabss_seg(\
	[\
		[{ push | pop},]										\
		[ identifier·idt,]\
	]															\
	[\
		segment-name·string 									\
		[, segment-class·string ]\
	]															\
)

它为未初始化数据指定缺省段。在一些情况下,你能使用bss_seg将所有未初始化数据安排在一个段中来加速你的装载时间。

用bss_seg编译指示分配的数据不包含任何关于其位置的信息。

第二个参数segment-class是用于兼容2.0版本以前的Visual C++的,现在将忽略它。




十四、alloc_text参数

语法:

#pragma alloc_text( txtforfunc·string , func1 [ , func2... ] )

命名特别定义的函数驻留的代码段。该编译指示必须出现在函数说明符和函数定义之间。(即在函数说明后,定义前)
注意alloc_text不接受C++中方法(类成员函数)及重载函数的处理,仅能以C的方式说明(extern "C")。如果你试图将这个编译指示应用于一个具有C++连接方式的函数时,将出现一个编译程序错误。
另外,该编译提示不能用在函数内部


结语

精力有限,只能先写到这了,下期见!


@HaohaoCppDebuger |寻兰 
2021/12/2 

-----THE END-----
THANK YOU !

































  1. 加粗 命令为仅C++,下同。 ↩︎

  2. 代码段中以//forC: ...注释的语句为C语言写法。下同。 ↩︎

  3. 代码中 [ ] 括住的部分为可选。下同。
    { XX | YY } 表示XXYY任选。下同。
    语法代码段的换行实际不合法,注意行末【\】。下同。 ↩︎

  4. 该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序,函数在.obj文件中默认的存放节为.text节,如果code_seg没有带参数的话,则函数存放在.text节中。

    ↩︎

你可能感兴趣的:(C++干货系列,#,C++干货,预处理器,c++,开发语言,后端)