C/C++的条件编译

来源:互联网 发布:访问数据是什么? 编辑:程序博客网 时间:2024/04/23 19:44

以为已经掌握条件编译,预编译的要领了,结果还是存在很多遗漏的地方,所以规整一下:


条件编译:

1
#ifdef _XXXX
...程序段1...
#else
...程序段2...
#endif
  这表明如果标识符_XXXX已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。


2
#ifndef _XXXX
...程序段1...
#else
...程序段2...
#endif
  这里使用了#ifndef,表示的是if not def。当然是和#ifdef相反的状况(如果没有定义了标识符_XXXX,那么执行程序段1,否则执行程序段2)。


3
#if 常量
...程序段1...
#elif<常量表达式2>
...程序段2...
#elif<常量表达式3>
...程序段3...
                ...    ...
#else
...程序段2...
#endif
  这里表示,如果常量为真(非0,随便什么数字,只要不是0),就执行程序段1,否则执行程序段2。

 

例如:
#if defined(_PC) && defined(_SSE)
# ifdef DEBUG
#  define __PC_VERIFY_ALIGNMENT__
# endif
# ifdef __PC_VERIFY_ALIGNMENT__
#  define PC_VERIFY_ALIGN_ASSERT( ptr ) /
  {/
   if( ( ( (INT) ptr ) % 16) != 0 ) /
   {/
    debugf( NAME_Critical, TEXT("Unaligned PC data (0x%X)"), ptr ); /
    DebugBreak(); /
   }/
  }
# else
#  define PC_VERIFY_ALIGN_ASSERT( ptr )
# endif
#else
# define PC_VERIFY_ALIGN_ASSERT( ptr )
#endif

 


再谈到预编译命令:

预处理器指示符  
  头文件通过include   预处理器指示符preprocessor   include   directive   而成为我们程序的  
  一部分预处理器指示符用#   号标识这个符号将放在程序中该行的最起始一列上处理  
  这些指示符的程序被称做预处理器preprocessor   通常捆绑在编译器中  
  #include   指示符读入指定文件的内容它有两种格式  
  #include   <some_file.h>  
  #include   "my_file.h"  
  如果文件名用尖括号<   和>   括起来表明这个文件是一个工程或标准头文件查  
  找过程会检查预定义的目录我们可以通过设置搜索路径环境变量或命令行选项来修改这些  
  目录在不同的平台上这些方法大不相同建议你请教同事或查阅编译器手册以获得更进  
  一步的信息如果文件名用一对引号括起来则表明该文件是用户提供的头文件查找该  
  文件时将从当前文件目录开始  
  被包含的文件还可以含有#include   指示符由于嵌套包含文件的原因一个头文件可能  
  会被多次包含在一个源文件中条件指示符可防止这种头文件的重复处理例如  
  #ifndef   BOOKSTORE_H  
  #define   BOOKSTORE_H  
  /*   Bookstore.h   的内容   */  
  #endif  
  条件指示符#ifndef   检查BOOKSTORE_H   在前面是否已经被定义这里BOOKSTORE_H  
  是一个预编译器常量习惯上预编译器常量往往被写成大写字母如果BOOKSTORE_H  
  在前面没有被定义则条件指示符的值为真于是从#ifndef   到#endif   之间的所有语句都被包  
  含进来进行处理相反如果#ifndef   指示符的值为假则它与#endif   指示符之间的行将被忽  
  略  
  为了保证头文件只被处理一次把如下#define   指示符  
  #define   BOOKSTORE_H  
  放在#ifndef   后面这样在头文件的内容第一次被处理时BOOKSTORE_H   将被定义  
  从而防止了在程序文本文件中以后#ifndef   指示符的值为真  
  只要不存在两个必须包含的头文件要检查一个同名的预处理器常量这样的情形这  
  个策略就能够很好地运作  
  #ifdef   指示符常被用来判断一个预处理器常量是否已被定义以便有条件地包含程序代  
  码例如  
  int   main()  
  {  
  #ifdef   DEBUG  
  cout   <<   "Beginning   execution   of   main()/n";  
  #endif  
  string   word;  
  vector<   string   >   text;  
  while   (   cin   >>   word   )  
  {  
  #ifdef   DEBUG  
  cout   <<   "word   read:   "   <<   word   <<   "/n";  
  #endif  
  text.push_back(   word   );  
  }  
  //   ...  
  }  
  本例中如果没有定义DEBUG   实际被编译的程序代码如下  
  int   main()  
  {  
  string   word;  
  vector<   string   >   text;  
  while   (   cin   >>   word   )  
  {  
  text.push_back(   word   );  
  }  
  //   ...  
  }  
  反之如果定义了DEBUG   则传给编译器的程序代码是  
  int   main()  
  {  
  cout   <<   "Beginning   execution   of   main()/n";  
  string   word;  
  vector<   string   >   text;  
  while   (   cin   >>   word   )  
  {  
  cout   <<   "word   read:   "   <<   word   <<   "/n";  
  text.push_back(   word   );  
  }  
  //   ...  
  }  
  我们在编译程序时可以使用-D   选项并且在后面写上预处理器常量的名字这样就能在  
  命令行中定义预处理器常量2  
  $   CC   -DDEBUG   main.C  
  也可以在程序中用#define   指示符定义预处理器常量  

........................................................................................................................

编译C++程序时编译器自动定义了一个预处理器名字__cplusplus   注意前面有两个下  
  划线因此我们可以根据它来判断该程序是否是C++程序以便有条件地包含一些代码  
  例如  
  #ifdef   __cplusplus  
  //   不错我们要编译C++  
  //   extern   "C"   到第7   章再解释  
  extern   "C"  
  #endif  
  int   min(   int,   int   );  
  在编译标准C   时编译器将自动定义名字__STDC__   当然__cplusplus   与__STDC__  
  不会同时被定义  
  另外两个比较有用的预定义名字是__LINE__和__FILE__   __LINE__记录文件已经被  
  编译的行数__FILE__包含正在被编译的文件的名字可以这样使用它们  
  if   (   element_count   ==   0   )  
  cerr   <<   "Error:   "   <<   __FILE__  
  <<   "   :   line   "   <<   __LINE__  
  <<   "element_count   must   be   non-zero./n";  
  另外两个预定义名字分别包含当前被编译文件的编译时间__TIME__   和日期  
  __DATE__   时间格式为hh:mm:ss   因此如果在上午8   点17   分编译一个文件则时间表  
  示为08:17:05   如果这一天是1996   年10   月31   日星期四则日期表示为  
  Oct   31   1996  
  若当前处理的行或文件发生变化则__LINE__和__FILE__的值将分别被改变其他四个  
  预定义名字在编译期间保持不变它们的值也不能被修改  
  assert()是C   语台标准库中提供的一个通用预处理器宏在代码中常利用assert()来判断一  
  个必需的前提条件以便程序能够正确执行例如假定我们要读入一个文本文件并对其  
  中的词进行排序必需的前提条件是文件名已经提供给我们了这样我们才能打开这个文件  
  为了使用assert()   必须包含与之相关联的头文件  
  #include   <assert.h>  
  下面是一个简单的使用示例  
  assert(   filename   !=   0   );  
  assert()将测试filename   不等于0   的条件是否满足这表示为了后面的程序能够正确执  
  行我们必须断言一个必需的前提条件如果这个条件为假即filename   等于0   断言  
  失败则程序将输出诊断消息然后终止  
  assert.h   是C   库头文件的C   名字C++程序可以通过C   库的C   名字或C++名字来使用它  
  这个头文件的C++名字是cassert   C   库头文件的C++名字总是以字母C   开头后面是去掉后  
  缀.h   的C   名字正如前面所解释的由于在各种C++实现中头文件的后缀各不相同因  
  此标准C++头文件没有指定后缀  
  使用头文件的C   名字或者C++名字两种情况下头文件的#include   预处理器指示符的  
  效果也会不同下面的#include   指示符  
  #include   <cassert>  
  将cassert   的内容被读入到我们的文本文件中但是由于所有的C++库名字是在名字空间  
  std   中被定义的因而在我们的程序文本文件中它们是不可见的除非用下面的using   指示  
  符显式地使其可见  
  using   namespace   std;  
  使用C   头文件的#include   指示符  
  #include   <assert.h>  
  就可以直接在程序文本文件中使用名字assert()   而无需使用using   指示符3   库文件厂  
  商用名字空间来控制全局名字空间污染即名字冲突问题以避免它们的库污染了用  
  户程序的名字空间8.5   节将讨论这些细节

 


一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。    
          条件编译命令最常见的形式为:    
          #ifdef   标识符    
          程序段1    
          #else    
          程序段2    
          #endif    
             
          它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。    
          其中#else部分也可以没有,即:    
          #ifdef    
          程序段1    
          #denif    
             
          这里的“程序段”可以是语句组,也可以是命令行。这种条件编译可以提高C源程序的通用性。如果一个C源程序在不同计算机系统上系统上运行,而不同的计算机又有一定的差异。例如,我们有一个数据类型,在Windows平台中,应该使用long类型表示,而在其他平台应该使用float表示,这样往往需要对源程序作必要的修改,这就降低了程序的通用性。可以用以下的条件编译:    
          #ifdef   WINDOWS    
          #define   MYTYPE   long    
          #else    
          #define   MYTYPE   float    
          #endif    
             
          如果在Windows上编译程序,则可以在程序的开始加上    
          #define   WINDOWS    
             
          这样则编译下面的命令行:    
          #define   MYTYPE   long    
             
          如果在这组条件编译命令之前曾出现以下命令行:    
          #define   WINDOWS   0    
             
          则预编译后程序中的MYTYPE都用float代替。这样,源程序可以不必作任何修改就可以用于不同类型的计算机系统。当然以上介绍的只是一种简单的情况,可以根据此思路设计出其它的条件编译。    
          例如,在调试程序时,常常希望输出一些所需的信息,而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段:    
          #ifdef   DEBUG    
          print   ("device_open(%p)/n",   file);    
          #endif    
             
          如果在它的前面有以下命令行:    
          #define   DEBUG    
             
          则在程序运行时输出file指针的值,以便调试分析。调试完成后只需将这个define命令行删除即可。有人可能觉得不用条件编译也可达此目的,即在调试时加一批printf语句,调试后一一将printf语句删除去。的确,这是可以的。但是,当调试时加的printf语句比较多时,修改的工作量是很大的。用条件编译,则不必一一删改printf语句,只需删除前面的一条“#define   DEBUG”命令即可,这时所有的用DEBUG作标识符的条件编译段都使其中的printf语句不起作用,即起统一控制的作用,如同一个“开关”一样。    
          有时也采用下面的形式:    
          #ifndef   标识符    
          程序段1    
          #else    
          程序段2    
          #endif    
             
          只是第一行与第一种形式不同:将“ifdef”改为“ifndef”。它的作用是:若标识符未被定义则编译程序段1,否则编译程序段2。这种形式与第一种形式的作用相反。    
          以上两种形式用法差不多,根据需要任选一种,视方便而定。    
          还有一种形式,就是#if后面的是一个表达式,而不是一个简单的标识符:    
          #if   表达式    
          程序段1    
          #else    
          程序段2    
          #endif    
             
          它的作用是:当指定的表达式值为真(非零)时就编译程序段1,否则编译程序段2。可以事先给定一定条件,使程序在不同的条件下执行不同的功能。    
          例如:输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写输出,或全改为小写字母输出。    
          #define   LETTER   1    
          main()    
          {    
          char   str[20]="C   Language",c;    
          int   i=0;    
          while((c=str[i])!='/0'){    
          i++;    
          #if   LETTER    
          if(c>='a'&&c<='z')   c=c-32;    
          #else    
          if(c>='A'&&c<='Z')   c=c+32;    
          #endif    
          printf("%c",c);    
          }    
          }    
             
          运行结果为:C   LANGUAGE    
          现在先定义LETTER为1,这样在预处理条件编译命令时,由于LETTER为真(非零),则对第一个if语句进行编译,运行时使小写字母变大写。如果将程序第一行改为:    
          #define   LETTER   0    
             
          则在预处理时,对第二个if语句进行编译处理,使大写字母变成小写字母(大写字母与相应的小写字母的ASCII代码差32)。此时运行情况为:    
          c   language    
          有人会问:不用条件编译命令而直接用if语句也能达到要求,用条件编译命令有什么好处呢?的确,此问题完全可以不用条件编译处理,但那样做目标程序长(因为所有语句都编译),而采用条件编译,可以减少被编译的语句,从而减少目标的长度。当条件编译段比较多时,目标程序长度可以大大减少。

 


C++预处理(一)(整理)
  C++中有那么多灵活的特性,例如重载、类型安全的模板、const关键字等等,为什么程序员还要写“#define”这样的预处理指令? 
  典型的一个例子,大家都知道“const int a=100;”就比“#define a 100”要好,因为const提供类型安全、避免了预处理的意外修改等。 
  然而,还是有一些理由让我们去使用#define。

 
一、使用预处理宏
1)  守护头文件
为了防止头文件被多次包含,这是一种常用技巧。
#ifndef MYPROG_X_H
#define MYPROG_X_H
// … 头文件x.h的其余部分
#endif
2)  使用预处理特性
在调试代码中,插入行号或编译时间这类信息通常很有用,可以使用预定义的标准宏,例如__FILE__、__LINE__、__DATE__和__TIME__。
3)  编译时期选择代码
A.  调试代码
选择性的输出一些调试信息:
void f()
{
#ifdef _DEBUG 
  cerr < <”调试信息” < <endl;
#endif
// .. f()的其他部分
}

通常我们也可以用条件判断来代替:
void f()

  if(_DEBUG) 
  { 
  cerr < <”调试信息” < <endl;
}
// .. f()的其他部分
}

B.  特定平台代码
同一函数同一功能在不同的编译平台上可能有不同的表现形式,我们可以通过定义宏来区分不同的平台。
C.  不同的数据表示方式
< <深入浅出MFC>>这本书对MFC框架中宏的使用解析的很透彻,也让我们领略到宏的强大功能。可以参看DECLARE_MESSAGE_MAP(),
BEGIN_MESSAGE_MAP, END_MESSAGE_MAP的实现。
4)  #pragma的使用,例如用#pragma禁止掉无伤大雅的警告,用于可移植性的条件编译中。例如,
包含winsock2 lib文件:
#pragma comment(lib,”ws2_32”)
用如下预处理宏,可以使结构按1字结对齐:
#pragma pack(push)
#pragma pack(1)
// … 结构定义
#pragma pack(pop) 
      禁止掉某些警告信息:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( error : 164 )// 把164号警告作为错误报出
// Some code
#pragma warning( pop )

二、宏的常见陷阱
  下面示范如何写一个简单的预处理宏max();这个宏有两个参数,比较并返回其中较大的一个值。在写这样一个宏时,容易犯哪些错误?有四大易犯错误。
1)  不要忘记为参数加上括号
// 例1:括号陷阱一:参数
//
#define max(a, b) a < b ? b : a
例如:
max(i += 2, j)
展开后:
i += 2 < j ? j : i += 2
考虑运算符优先级和语言规则,实际上是:
i += ((2 < j) ? j : i += 2)
这种错误可能需要长时间的调试才可以发现。
2)  不要忘记为整个展开式加上括号
// 例2:括号陷阱二:展开式
//
#define max(a, b) (a) < (b) ? (b) : (a) 
  例如: 
  m = max(j, k) + 42; 
  展开后为: 
  m = (j) < (k) ? (j) : (k) + 42;
考虑运算符优先级和语言规则,实际上是: 
  m = ((j) < (k)) ? (j) : ((k) + 42); 
  如果j >= k, m被赋值k+42,正确;如果j < k, m被赋值j,是错误的。如果给展开式加上括号,就解决了这个问题。
3)  当心多参数运算
// 例3:多参数运算
//
#define max(a, b) ((a) < (b) ? (b) : (a))
max(++j, k); 
  如果++j的结果大于k,j会递增两次,这可能不是程序员想要的:
((++j) < (k) ? (k) : (++j)) 
  类似的:
max(f(), pi)
展开后:
((f()) < (pi) ? (pi) : (f()))
如果f()的结果大于等于pi,f()会执行两次,这绝对缺乏效率,而且可能是错误的。
4)  名字冲突
宏只是执行文本替换,而不管文本在哪儿,这意味着只要使用宏,就要小心对这些宏命名。具体来说,这个max宏最大的问题是,极有可能会和标准的max()函数模板冲突:
// 例4:名字冲突
//
#define max(a,b) ((a) < (b) ? (b) : (a))
#include <algorithm> // 冲突!
在 <algorithm>中,有如下:
template <typename T> const T&
max(const T& a, const T& b);
宏将它替换为如下,将无法编译:
template <typename T> const T&
((const T& a) < (const T& b) ? (const T& b) : (const T& a));
所以,我们尽量避免命名的冲突,想出一个不平常的,难以拼写的名字,这样才能最大可能地避免与其他名字空间冲突。

宏的其他缺陷:
5)  宏不能递归 
  容易理解。
6)  宏没有地址
你可能得到任何自由函数或成员函数的指针,但不可能得到一个宏的指针,因为宏没有地址。宏之所以没有地址,原因很显然===宏不是代码,宏不会以自身的形势存在,因为它是一种被美化了的文本替换规则。
7)  宏有碍调试
在编译器看到代码之前,宏就会修改相应的代码,因而,他会严重改变变量名称和其他名称;此外,在调试阶段,无法跟踪到宏的内部。 

 ——《现代C++中的预处理宏》徐东来

原创粉丝点击