vc预处理

VC 编译命令开关   
vc能够能够经过Settings -->Project-->C/C++-->Customize来设置这个编译开关                                  

/C:在预处理输出中保留注释语句
/c:只编译,不链接,至关于在"Build"菜单下选择了"Compile"
/D:定义常量和宏,与源程序里的#define 有相同效果
/E:预处理C、C++源文件,将源文件中全部的预编译指令及宏展开,将注释去掉,而后将预处理器的输出拷贝至标准输出设备输出,而且在每一个文件的开头和末尾加入#line
/EH:指定编译器用何种异常处理模型
/EP:同/E,只是去掉了#line
/F:设置程序的堆栈大小
/FA:设置生成何种列表文件(汇编、汇编与机器码、汇编与源码、汇编与机器码以及源码)
/Fa:指定用/FA设置的列表文件的存放路径及(或)文件名
/FD:生成文件的相互依赖信息
/Fd:设置程序数据库文件(PDB)的存放路径及(或)文件名
/Fe:设置最终可执行文件的存放路径及(或)文件名
/FI:预处理指定的头文件,与源文件中的#include有相同效果
/Fm:建立map文件
/Fo:设置编译后Obj文件的存放路径及(或)文件名
/Fp:设置预编译文件(pch)的存放路径及(或)文件名
/FR:生成浏览信息(sbr)文件
/Fr:同/FR,不一样之处在于/Fr不包括局部变量信息
/G3:为80386处理器优化代码生成
/G4:为80486处理器优化代码生成
/G5:为Pentium处理器优化代码生成
/G6:为Pentium Pro处理器优化代码生成
/GA:为Windows应用程序做优化
/GB:为Pentium处理器优化代码生成,使用8038六、80486、Pentium、Pentium Pro的混合指令集,是代码生成的默认选项(程序属性选项中Processor对应Blend)
/GD:为Windows动态库(dll)做优化,此开关在VC6中没有实现
/Gd:指定使用__cdecl的函数调用规则
/Ge:激活堆栈检测
/GF:消除程序中的重复的字符串,并将她放到只读的缓冲区中
/Gf:消除程序中的重复字符串
/Gh:在每一个函数的开头调用钩子(hook)函数--penter
/Gi:容许渐进编译
/Gm:容许最小化rebuild
/GR:容许运行时类型信息(Run-Time Type Infomation)
/Gr:指定使用__fastcall的函数调用规则
/Gs:控制堆栈检测所用内存大小
/GT:支持用__declspec(thread)分配的数据的fier-safety
/GX:容许同步异常处理,与/EHsc开关等价
/Gy:容许编译器将每个函数封装成COMDATs的形式,供链接器调用
/GZ:容许在Debug build 的时候捕捉Release build的错误
/Gz:指定使用__stdcall的函数调用规则
/H:限制外部名字的长度
/HELP:列出编译器的全部的命令开关
/I:指定头文件的搜索路径
/J:将char的缺省类型从signed char改为unsigned char
/LD:建立一个动态链接库
/LDd:建立一个Debug版本的动态连接库
/link:将指定的选项传给链接器
/MD:选择多线程、DLL版本的C Run-Time库
/MDd:选择多线程、DLL、Debug版本的C Run-Time库
/ML:选择单线程版本的C Run—Time库
/MLd:选择单线程、Debug版本的C Run—Time库
/MT:选择多线程版本的C Run-Time库
/MTd:选择多线程、Debug版本的C Run—Time库
/nologo:不显示程序的版权信息
/O1:优化使产生的可执行代码最小
/O2:优化使产生的可执行代码速度最快
/Oa:指示编译器程序里没有使用别名,能够提升程序的执行速度
/Ob:控制内联(inline)函数的展开
/Od:禁止代码优化
/Og:使用全局优化
/Oi:用内部函数去代替程序里的函数调用,可使程序运行的更快,但程序的长度变长
/Op:提升浮点数比较运算的一致性
/Os:产生尽量小的可执行代码
/Ot:产生尽量块的可执行代码
/Ow:指示编译器在函数体内部没有使用别名
/Ox:组合了几个优化开关,达到尽量多的优化
/Oy:阻止调用堆栈里建立帧指针
/Q1f:对核心级的设备驱动程序生成单独的调试信息
/QI0f:对Pentium 0x0f错误指令做修正
/Qifdiv:对Pentium FDIV错误指令做修正
/P:将预处理输出写到指定文件里,文件的后缀名为I
/TC:将命令行上的全部文件都看成C源程序编译,无论后缀名是否为.c
/Tc:将指定的文件看成C源程序编译,无论后缀名是否为.c
/TP:将命令行上的全部文件都看成C++源程序编译,无论后缀名是否为.cpp
/Tp:将指定文件看成C++源程序编译,无论后缀名是否为.cpp
/U:去掉一个指定的前面定义的符号或常量
/u:去掉全部前面定义的符号或常量
/V:在编译的obj文件里嵌入版本号
/vd:禁止/容许构造函数置换
/vmb:选择指针的表示方法,使用这个开关,在声明指向某个类的成员的指针以前,必须先定义这个类
/vmg:选择指针的表示方法,使用这个开关,在声明指向某个类的成员的指针以前,没必要先定义这个类,但要首先指定这个类是使用何种继承方法
/vmm:设置指针的表示方法为Single Inheritance and Multiple Inheritance
/vms:设置指针的表示方法为Single Inheritance
/vmv:设置指针的表示方法为Any class
/W:设置警告等级
/w:禁止全部警告
/X:阻止编译器搜索标准的include 目录
/Yc:建立预编译头文件(pch)
/Yd:在全部的obj文件里写上彻底的调试信息
/Yu:在build过程当中使用指定的预编译头文件
/YX:指示编译器若预编译头文件存在,则使用它,若不存在,则建立一个
/Z7:生成MSC7.0兼容的调试信息
/Za:禁止语言扩展(Microsoft Extensions to C)
/Zd:调试信息只包含外部和全局的符号信息以及行号信息
/Ze:容许语言扩展(Microsoft Extensions to C)
/Zg:为源文件里面定义的每一个函数生成函数原型
/ZI:生成程序库文件(Pdb)并支持Edit and Continue调试特性
/Zi:生成程序库文件(pdb),包含类型信息和符号调试信息
/ZL:从obj文件里去掉缺省的库文件名
/Zm:设置编译器的内存分配xianzhi
/Zn:禁止浏览信息文件里面的封装
/Zp:设置结构成员在内存里面的封装格式
/Zs:快速检查语法错误
--------------------------
vc所支持的文件类型

DSW:全称是Developer Studio Workspace,最高级别的配置文件,记录了整个工做空间的配置信息,她是一个纯文本的文件,在vc建立新项目的时候自动生成
DSP:全称是Developer Studio Project,也是一个配置文件,不过她记录的是一个项目的全部配置信息,纯文本文件
OPT:与DSW、DSP配合使用的配置文件,她记录了与机器硬件有关的信息,同一个项目在不一样的机器上的opt文件内容是不一样的
CLW:记录了跟ClassWizard相关的信息,若是丢失了clw文件,那么在Class View面板里就没有类信息
PLG:其实是一个超文本文件,能够用Internet Explorer打开,记录了Build的过程,是一个日志型文件
RC:资源描述文件,记录了全部的资源信息,在资源编辑器里做的修改,实际上都是对RC文件的修改
RC2:附加的资源描述文件,不能直接资源编辑器修改,只能手工添加,能够用来添加额外的资源
RES:通过资源编辑器编译以后的资源文件,以二进制方式存放
SBR:编译器生成的浏览信息文件,在代码导航的时候很是有用,她须要在编译时指定/FR或者/Fr开关
BSC:BSCMAKE.EXE将全部的SBR文件做为输入,通过处理以后输出一个BSC文件,在代码导航的时候实际用到的是BSC文件
ILK:当选定渐增型编译链接时,链接器自动生成ILK文件,记录链接信息
PDB:全称是Program DataBase,即程序数据库文件,用来记录调试信息,是一个至关重要的文件,没有他,程序没法正常调试
LIB:若是项目输出是Dll的话,通常会输出一个跟项目同名的Lib文件,记录输出的函数信息
EXP:同Lib,是跟Dll一块儿生成的输出文件
PCH:全称是PreCompiled Header,就是预先编译好的头文件,在编译时指定/Yu开关时编译器自动生成




vc一些预处理
1#if defined XXX_XXX 
#endif 
是条件编译,是根据你是否认义了XXX_XXX这个宏,而使用不一样的代码。 

通常.h文件里最外层的 
#if !defined XXX_XXX 
#define XXX_XXX 
#endif 
是为了防止这个.h头文件被重复include。 
#undef为解除定义,#ifndef是if not defined的缩写,即若是没有定义。
 
2#error XXXX 
是用来产生编译时错误信息XXXX的,通常用在预处理过程当中; 
例子: 
#if !defined(__cplusplus) 
#error C++ compiler required. 
#endif 

3extern 全局变量 至关于C#中的public 

4) __cdecl和__stdcall是两种C++函数调用规则的系统约定。 
__cdecl的: 
Argument-passing order 
Right to left 

Stack-maintenance responsibility 
Calling function pops the arguments from the stack 

Name-decoration convention 
Underscore character (_) is prefixed to names, except when exporting __cdecl functions that use C linkage. 

Case-translation convention 
No case translation 

__stdcall的: 
Argument-passing order 
Right to left. 

Argument-passing convention 
By value, unless a pointer or reference type is passed. 

Stack-maintenance responsibility 
Called function pops its own arguments from the stack. 

Name-decoration convention 
An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list. Therefore, the function declared as int func( int a, double b ) is decorated as follows: _func@12 

Case-translation convention 
None 
5#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n ) 
这个是用来指定类、结构的内存对齐的; 
这个提及来有点多,你上网查内存对齐能查到; 

6) 
__declspec( dllimport ) declarator 
__declspec( dllexport ) declarator 
这两个是与Dll有关的,声明能够从dll文件中输出和输入的函数、类或数据; 

7#pragma once 
是规定当编译时这个文件只能被include一次; 
相似
#if define 

8) disable message 
才疏学浅,没见过。。。。 

9) __int64 
是64位的整数类型,是为了防止32位的int不够用时用的; 

10#pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] ) 
是用来定义函数被放置在obj文件的哪一个段里。 

--------------------------------------------------- 

#if defined MACRO_NAME 
与 
#ifdef MACRO_NAME 仍是稍微有点区别的。 
譬如咱们公司就鼓励写第一种,由于它能够这么写: 
#if defined(MACRO_NAME) && defined(MACRO_NAME) 

这个#error是可让用户在编译时手动产生一个编译错误,更准确的说是在预处理的时候。 
#if !defined(__cplusplus) 
#error C++ compiler required. 
#endif 
这个就是说若是你的程序不是C++的,譬如C的,就会报错。里面的错误信息是用户本身决定的。 

#pragma once基本和前面那个选择编译同样的。











VC中预处理指令与宏定义的妙用
妙用一
刚接触到MFC编程的人每每会被MFC 向导生成的各类宏定义和预处理指令所吓倒,可是预处理和宏定义又是C语言的一个强大工具。使用它们能够进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能。

一个经典的例子

使用预处理与宏定义最经典的例子莫过于加在一个头文件中以免头文件被两次编译。试想这种的状况,有一个文件headerfile.h 它被包含在headerfile1.h中,同时在headerfile2.h 中也被包含了,如今有一个CPP文件,implement.cpp 包含了headerfile1.h 和headerfile2.h:

#include “headerfile1.h”
#include “headerfile2.h”

假设headerfile.h 中定义了一个全局变量 iglobal 。

int iglobal;

在编译的时候编译器两次编译headerfile,也就会发现iglobal被定义了两次,这时就会发生变量重定义的编译错误。

传统的解决办法是使用#ifdef 以及#endif 来避免头文件的重复编译,在上面的例子中,只须要加上这么几行:

#ifndef smartnose_2002_6_21_headerfile_h
#define smartnose_2002_6_21_headerfile_h

int iglobal;

#endif

仔细的考虑上面的宏定义,会发现当编译器编译过一次headerfile.h之后,smartnose_2002_6_21_headerfile_h 这个宏就被定义了,之后对headerfile.h的编译都会跳过int iglobal 这一行。固然smartnose_2002_6_21_headerfile_h 这个宏是能够任意定义的,可是这个宏自己不能和其它文件中定义的宏重复,因此MFC在自动生成的文件中老是使用一个随机产生的长度很是长的宏,但我以为这没有必要,我建议在这个宏中加入一些有意义的信息,比方做者,文件名,文件建立时间等等,由于咱们有时候会忘记在注释中加入这些信息。

在VC.Net 中咱们不会再看见这些宏定义了,由于在这里会广泛使用一个预处理指令:

#pragma once

只要在头文件的最开始加入这条指令就可以保证头文件被编译一次,这条指令实际上在VC6中就已经有了,可是考虑到兼容性并无太多的使用它。

源代码版本控制

当咱们为许多平台开发多个版本的时候预编译指令和宏定义也可以帮咱们的忙。假设咱们如今为WINDOWS 和LINUX开发了一套软件,因为这两种系统的不一样,咱们不得不在程序控制源代码的版本。比方内存的分配,咱们能够在LINUX上使用标准C的malloc 函数,可是咱们但愿在 WINDOWS上使用HeapAlloc API。下面的代码演示了这种状况:

main()
{
………………..
#ifdef _WINDOWS_PLATFORM
HeapAlloc(5);
#else
malloc(5);
#endif
………………..
}

当咱们在WINDOWS 平台上编译此程序的时候,只须要定义_WINDOWS_PLATFORM这个宏,那么HeapAlloc这条语句就可以起做用了。这样就可以让咱们在同一个文件中为不一样的平台实现不一样版本的代码,同时保持程序的良好结构。在许多状况下,咱们还能够为一个方法使用不一样的算法,而后用宏定义来针对不一样的状况选择其中的一个进行编译。这在MFC应用程序中是使用得最多的。最明显的就是文件中常常存在的

#ifdef _DEBUG

…………………….some code………..

#endif

这样的代码,这些代码在应用程序的调试版(DEBUG)中会发挥其做用。

#Pragma 指令

在全部的预处理指令中,#Pragma 指令多是最复杂的了,它的做用是设定编译器的状态或者是指示编译器完成一些特定的动做。其格式通常为

#Pragma Para

其中Para 为参数,下面来看一些经常使用的参数。

message 参数。 Message 参数是我最喜欢的一个参数,它可以在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是很是重要的。其使用方法为:

#Pragma message(“消息文本”)

当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。

当咱们在程序中定义了许多宏来控制源代码版本的时候,咱们本身有可能都会忘记有没有正确的设置这些宏,此时咱们能够用这条指令在编译的时候就进行检查。假设咱们但愿判断本身有没有在源代码的什么地方定义了_X86这个宏能够用下面的方法

#ifdef _X86

#Pragma message(“_X86 macro activated!”)

#endif

当咱们定义了_X86这个宏之后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated!”。咱们就不会由于不记得本身定义的一些特定的宏而抓耳挠腮了。

另外一个使用得比较多的pragma参数是code_seg。格式如:

#pragma code_seg( ["section-name"[,"section-class"] ] )

它可以设置程序中函数代码存放的代码段,当咱们开发驱动程序的时候就会使用到它。

最后一个比较经常使用的就是上面所说的#pragma once 指令了。

VC预约义的宏

在VC中有一类宏并非由用户用#define语句定义的,而是编译器自己就可以识别它们。这些宏的做用也是至关大的。让咱们来看第一个,也是MFC中使用得最频繁的一个:__FILE__ 。

当编译器遇到这个宏时就把它展开成当前被编译文件的文件名。好了,咱们立刻就能够想到能够用它来作什么,当应用程序发生错误时,咱们能够报告这个错误发生的程序代码在哪一个文件里,比方在文件test.cpp中有这样的代码:

try
{
char * p=new(char[10]);
}
catch(CException *e )
{
TRACE(“ there is an error in file: %s\n”,__FILE__);
}

在程序运行的时候,若是内存分配出现了错误,那么在调试窗口中会出现there is an error in file: test.cpp 这句话,固然,咱们还能够把这个错误信息显示在别的地方。

若是咱们还可以记录错误发生在哪一行就行了,幸运的是,与__FILE__宏定义同样,还有一个宏记录了当前代码所在的行数,这个宏是__LINE__。使用上面的两个宏,咱们能够写出一个相似于VC提供的ASSERT语句。下面是方法

#define MyAssert(x) \
if(!(x)) \
MessageBox(__FILE__,__LINE__,NULL,MB_OK);

咱们在应用程序中能够象使用ASSERT语句同样使用它,在错误发生时,它会弹出一个对话框,其标题和内容告诉了咱们错误发生的文件和代码行号,方便咱们的调试,这对于不能使用ASSERT语句的项目来讲是很是有用的。

除了这两个宏之外,还有记录编译时间的__TIME__,记录日期的__DATE__,以及记录文件修改时间的__TIMESTAMP__宏。

使用这些预约义的宏,咱们几乎能够生成和VC可以生成的同样完整的源代码信息报表。

结论

翻开MFC和Linux的源代码,宏定义几乎占据了半边天,消息映射,队列操做,平台移植,版本管理,甚至内核模块的拆卸安装都用宏定义完成。绝不夸张的说,有些文件甚至就只能看见宏定义。因此学习宏定义,熟练的使用宏定义对于学习C语言乃至VC都是很是关键的。


妙用二

在上一篇文章中,我演示了几个经常使用的宏定义和预处理指令,但能够说这些都是至关常规的技巧。下面要介绍的宏定义与预处理指令的用法也是ATL,MFC以及LINUX中使用得比较多的很是重要的技巧。 

## 链接符与# 符

## 链接符号由两个井号组成,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而造成一个新的子串。但它不能够是第一个或者最后一个子串。所谓的子串(token)就是指编译器可以识别的最小语法单元。具体的定义在编译原理里有详尽的解释,但不知道也无所谓。同时值得注意的是#符是把传递过来的参数当成字符串进行替代。下面来看看它们是怎样工做的。这是MSDN上的一个例子。

假设程序中已经定义了这样一个带参数的宏:

#define paster( n ) printf( "token" #n " = %d", token##n )

同时又定义了一个整形变量:

int token9 = 9;

如今在主程序中如下面的方式调用这个宏:

paster( 9 );

那么在编译时,上面的这句话被扩展为:

printf( "token" "9" " = %d", token9 );

注意到在这个例子中,paster(9);中的这个”9”被原封不动的当成了一个字符串,与”token”链接在了一块儿,从而成为了token9。而#n也被”9”所替代。

可想而知,上面程序运行的结果就是在屏幕上打印出token9=9

在ATL的编程中,咱们查看它的源代码就会常常看见这样的一段:

#define IMPLEMENTS_INTERFACE(Itf) \
{&IID_##Itf, ENTRY_IS_OFFSET,BASE_OFFSET(_ITCls, Itf) },

咱们常常不假思索的这样使用它:

……
IMPLEMENTS_INTERFACE(ICat)
……

实际上IID_ICat 已经在别的地方由ATL向导定义了。当没有向导的时候,你只要遵循把IID_加在你的接口名前面来定义GUID的规则就也可使用这个宏。在实际的开发过程当中可能不多用到这种技巧,可是ATL使用得如此普遍,而其中又出现了很多这样的源代码,因此明白它是怎么一回事也是至关重要的。个人一个朋友就是由于不知道IMPLEMENTS_INTERFACE宏是怎么定义的,而又不当心改动了IID_ICat的定义而忙活了一成天。

Linux的怪圈

在刚开始阅读Linux的时候有一个小小的宏让我百思不得其解:

#define wait_event(wq,condition) \
do{ \
if(condition) \
break; \
__wait_event(wq,condition); \
}while(0)

这是一个奇怪的循环,它根本就只会运行一次,为何不去掉外面的do{..}while结构呢?我曾一度在内心把它叫作“怪圈”。原来这也是很是巧妙的技巧。在工程中可能常常会引发麻烦,而上面的定义可以保证这些麻烦不会出现。下面是解释:

假设有这样一个宏定义

#define macro(condition) \

if(condition) dosomething();

如今在程序中这样使用这个宏:

if(temp)
macro(i);
else
doanotherthing();

一切看起来很正常,可是仔细想一想。这个宏会展开成:

if(temp)
if(condition) dosomething();
else
doanotherthing();

这时的else不是与第一个if语句匹配,而是错误的与第二个if语句进行了匹配,编译经过了,可是运行的结果必定是错误的。

为了不这个错误,咱们使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时由于绝大多数的编译器都可以识别do{…}while(0)这种无用的循环并进行优化,因此使用这种方法也不会致使程序的性能下降。

几个小小的警告

正如微软声称的同样,宏定义与预编译器指令是强大的,可是它又使得程序难以调试。因此在定义宏的时候不要节省你的字符串,必定要力争完整的描述这个宏的功能。同时在定义宏的时候若有必要(比方使用了if语句)就要使用do{…}while(0)将它封闭起来。在宏定义的时候必定要注意各个宏之间的相互依赖关系,尽可能避免这种依赖关系的存在。下面就有这样一个例子。

设有一个静态数组组成的整型队列,在定义中使用了这样的方法: int array[]={5, 6, 7, 8};

咱们还须要在程序中遍历这个数组。一般的作法是使用一个宏定义

#define ELE_NUM 4
…………………………..
……………………………..

for(int I=0;I<ELE_NUM;I++)
{
cout<<array[I];
}

因为某种偶然的缘由,咱们删除了定义中的一个元素,使它变成:

array[]={5,6,7}

而却忘了修改ELE_NUM的值。那么在上面的代码中立刻就会发生访问异常,程序崩溃。而后是彻夜不眠的调试,最后发现问题出在这个宏定义上。解决这个问题的方法是不使用

array[]={….}这样的定义,而显式的申明数组的大小:

array[ELE_NUM]={….}

这样在改动数组定义的时候,咱们就不会不记得去改宏定义了。总之,就是在使用宏定义的时候可以用宏定义的地方通通都用上。

我发现的另外一个有趣的现象是这样的:

假设如今有一个课程管理系统,学生的人数用宏定义为:

#define STU_NUM 50

而老师的人数刚好也是50人,因而不少人把全部涉及到老师人数的地方统统用上STU_NUM这个宏。另外一个学期过去,学生中的一个被开除了,系统须要改变。怎么办呢?简单的使用#define STU_NUM 49 么?若是是这样,一个老师也就被开除了,咱们不得不手工在程序中去找那些STU_NUM宏而后判断它是不是表示学生的数目,若是是,就把它改为49。天哪,这个宏定义制造的麻烦比使用它带来的方便还多。正确的方法应该是为老师的数目另外定义一个宏:

#define TEA_NUM 50

当学生的数目改变之后只要把STU_NUM 定义为49就完成了系统的更改。因此,当程序中的两个量之间没有必然联系的时候必定不要用其中的一个宏去替代另外一个,那只会让你的程序根本没法改动。

最后,建议C/C++语言的初学者尽量多的在你的程序中使用宏定义和预编译指令。多看看MFC,ATL或者LINUX的源代码,你会发现C语言强大的缘由所在。
相关文章
相关标签/搜索