预编译和宏定义的相关知识
来自: http://www.neu.edu.cn/cxsj/online/c11/ch11_1.html
第十一章 预编译和宏定义
11.1 概述
软件工程中一个非常重要的问题是软件的可移植和可重用问题,例如在微机平台上开发的程序需要顺利的移植到大型计算机上去运行,同一套代码不加修改或经过少量的修改即可适应多种计算机系统。C语言作为软件工程中广泛使用的一门程序设计语言,需要很好的解决此类问题。为此ANSI C引入了预编译处理命令,主要规范和统一不同编译器的指令集合。通过这些指令,控制编译器对不同的代码段进行编译处理,从而生成针对不同条件的计算机程序。
ANSI C中主要定义如下三类预编译指令:
1) #define与#undef指令
2) #include 指令
3) #if #endif 和#if #else #endif指令
11.2 #define命令
#define命令主要可以实现两种功能:
1) 符号常量定义
2) 宏定义
11.2.1 符号常量定义
在介绍符号常量定义之前,首先分析如下的程序。
例11-1 输入全班20个同学的C语言成绩,并计算出最高分同学的姓名和成绩,并计算出全班C语言成绩的平均分。
#include "stdio.h"
void main()
{
float fAvg; /*平均成绩 */
float fMax; /*最高成绩 */
int iMax;
float fSum;
int i;
/*C语言成绩(20人) */
float fCScore[20]={78.5,65,89,65,45,62,89,99,85,85, 100,58,98,86,68,66,85.5,89.5,75,76};
/*全班同学的姓名(20人) */
char chNames[][20]= { "Maofen","LiuTao","Wangyu","Songbou", "Liyang", "Wangming","Renhon", "Wuyong","Luming","Sunce","Zhangjie","Yuanquan", "Yangjing","Yubing","XingWen","Huangxing","Dingwen","Quyang","Liufeng","Hanhan"
};
/*计算总分*/
fSum=0;
for(i=0;i<20;i++)
fSum=fSum+fCScore[i];
/*计算平均分*/
fAvg =fSum/20;
/*计算最高分*/
fMax = fCScore[0];
iMax=0;
for(i=0;i<20;i++)
{
if(fMax<fCScore[i])
{
iMax=i;
fMax= fCScore[i];
}
}
/*输出信息*/
printf("\nThe Score of C Programming Language\n");
printf("The first is %10s \n",chNames[iMax]);
printf("The Score is %10.2f \n",fCScore[iMax]);
printf("The Average is %10.2f \n",fAvg);
}
在上面的程序中,20是一整型常量,代表全班同学的总人数。根据求解问题的需要,20出现了多次。如果将此程序修改,以便处理一个具有25个同学的班级,则需要进行多处的改动。在实际的软件开发中,一个简单的常量可能在整个软件的不同部分出现上百次或上千次,并可能代表不同的信息。在移植过程中,采用手工修改,十分容易出现错误。因此C语言中引入了符号常量,形式如下:
#define 符号常量名称 替换文本
其中#define 为预编译命令,“替换文本”为“符号常量”所代表的字符序列集合,可以为任意字符的组合。在程序代码被编译之前,编译系统自动将所有符号常量出现的位置,被其代表的“替换文本”无条件的替换,然后再进行语法检查和编译。因此利用符号常量,例11-1可改写为
#include "stdio.h"
#define STUDENT_COUNT 20
void main()
{
……
/*C语言成绩(20人) */
float fCScore[STUDENT_COUNT ]={…};
char chNames[][STUDENT_COUNT ]={ …};
fSum=0;
for(i=0;i<STUDENT_COUNT;i++)
fSum=fSum+fCScore[i];
fAvg =fSum/STUDENT_COUNT ;
…
for(i=0;i<STUDENT_COUNT ;i++)
{
……
}
}
为了节省空间,上述程序仅仅显示了修改后的部分。结合例11-1,将所有20用STUDENT_COUNT替换即可。其中
#define STUDENT_COUNT 20
是定义符号常量STUDENT_COUNT,其替换文本为20,代表20这样一个字符序列集合,在程序编译时,所有STUDENT_COUNT出现的位置均被20 无条件的替换。
如果将上述程序修改为处理具有30位同学的班级,只需修改定义
#define STUDENT_COUNT 30
即可。从上面的分析可以看出,恰当的使用符号常量可以增强程序的可移植能力。
提示 符号常量出现的位置,最终将无条件替换为其代表的字符序列集合,并不做语法检查。在编译程序时再进行语法检查。另外,符号常量定义的末尾不能有分号,与普通的程序语句不同。
符号常量的所带代表字符序列集合,可以为空,形式如下:
#define 编译标志
此命令的主要用途是引入编译标志,根据不同的编译标志,编译系统可以完成对不同代码段的编译。例如下面的程序
例11-2 在Windows系统中int 为32位数,在Dos中long为32位数。为了程序在两个系统中移植,可以定义INT32,保证在两种系统中利用INT32声明的变量为32位整型数。
#include "stdio.h"
#ifdef MY_DOS
#define INT16 int
#define INT32 long
#endif
#ifdef MY_WIN32
#define INT16 short
#define INT32 int
#endif
void main()
{
INT32 a;
a=32;
printf("%ld",a);
}
其中MY_WIN32和MY_DOS为编译标志,本段程序的主要目的是保证INT32类型数总是32位的整型数,在DOS系统中编译的时候增加代码
#define MY_DOS
此时INT32代表long。
在Windows系统下编译的时候,修改为
#define MY_WIN32
此时INT32代表int。
提示 符号常量与变量常常混淆。符号常量是一个有名字,因此其与变量相似,但是其值在程序运行过程中不能更改,与常量类似。
一般符号常量的名称采用大写字母,用于区分变量。
将常用的常量定义为符号常量是一种良好的设计习惯。
11.2.2 #undef命令
取消符号常量定义命令,语法形式如下:
#undef符号常量名称(或编译标志)
其作用取消最近一次#define符号常量名称(或编译标志)命令,使定义的符号常量或编译标志失效。
例11-3 分析如下的代码
#include "stdio.h"
void Test();
int main(int argc, char* argv[])
{
#define CONST_NAME1 "CONST_NAME1"
printf("%s\n",CONST_NAME1);
#undef CONST_NAME1
printf("%s\n",CONST_NAME1); /*错误,CONST_NAME1的定义已经取消*/
{
#define CONST_NAME2 "CONST_NAME2"
printf("%s\n",CONST_NAME2);
}
printf("%s\n",CONST_NAME2);
return 0;
}
void Test()
{
printf("%s\n",CONST_NAME2);
}
在程序的编译的时候,系统提示如下信息
error C2065: 'CONST_NAME1' : undeclared identifier
出现上述编译错误的原因是,在第二次应用符号常量CONST_NAME1时,此符号常量已经被取消定义。
符号常量的有效范围是从第一次出现的位置开始,到#undef结束。如果没有对应的#undef指令,则到文件末尾结束。因此在本例中void Test函数可以直接使用CONST_NAME2,而无须定义。
提示 符号常量与变量的有效范围不同。变量根据其所在位置,例如某函数或文件决定其有效性,在函数内部定义的变量不能够为其他的函数所使用。但是符号常量仅仅与其出现先后位置,以及对应的#undef命令相关,与是否出现在具体函数无关。
11.2.3 宏定义
C语言还提供另外一种带参数的#define命令,其英文为macro,一般翻译为宏。其形式如下:
#define 宏(参数列表) 替换文本
其中一般称“替换文本”为宏定义。例如
#define Sum(a,b) a+b
中Sum为宏,a,b为参数列表,a+b为宏定义。在下面的代码中
a=Sum(2,3);
在编译阶段,被替换为
a=2+3;
然后,再编译成可执行代码。
例10-3 定义宏FAILED用于检测数据的正确性。
#define FAILED(Status) ((Status)<0)
#include "stdio.h"
void main()
{
int d;
printf ("Please input a integer number(n>0)\n");
do
{
scanf("%d" ,&d);
}while(FAILED(d));
}
其中while(FAILED(d))在编译之前被无条件替换为while(d<0)。
宏定义和调用在形式与函数比较相似,但是原理是不同。
例10-4 定义Add函数实现两个数的乘法。
double Add(int a,int b)
{
return a*b;
}
void main()
{
double a,b;
double e,f;
a=3;
b=2;
e=Add(a,2);
f=Add(b+1,2);
printf("e=%ff=%f",e,f);
}
程序的运行结果如下
e=6.000000f=6.000000
在下面代码与上面的代码类似,但是运行结果不同。
例10-5 定义Add宏实现两个数的乘法。
#define Add(a,b)? a*b
void main()
{
double a,b;
double e,f;
a=3;
b=2;
e=Add(a,2);
f=Add(b+1,2);
printf("e=%f,f=%f",e,f);
}
程序的运行结果如下
e=6.000000,f=4.000000
在编译之前,系统做如下的处理:
e=Add(a,2)替换为e=a*2;
f=Add(b+1,2)替换为f=b+1*2;
因此在使用宏的时候,一定要注意与函数区分。首先宏是一种预编译指令,函数为程序指令。其次宏的作用是定义一种带参数的代码替换方法,在编译之后不会形成独立的代码段;而函数在编译之后会形成相对独立的代码段。
提示 函数定义是将一块公共的代码封装成一个函数,系统编译之后,此部分的机器指令依然作为一个相对对立的代码段。宏的定义仅仅是定义一个带参数的文本替换编译指令,在编译的时候所有的宏将被其代表的信息替换,而在编译之后,对应的机器指令不独立存在。
宏的使用与函数的调用不同。宏的使用发生在程序代码编译成为机器指令之前,而函数的调用发生在程序运行的时候。函数调用会产生额外系统开销,而宏的使用会加大程序代码量,函数的使用会减少程序的代码量。使用宏可以获得更高的代码运行效率,因为其不存在函数调用。
11.3 #include命令
在实际的软件开发的时候,需要多个人构成的小组共同完成代码的编写与测试,因此需要借鉴和应用其他的结果,或者要借鉴前人的成果。例如在C语言中,经常使用的标准输入输出函数均为前人的工作成果。在使用时,只需将包含函数声明的头文件stdio.h,用#include指令包含当前的程序文件即可。
#include 命令的形式如下
#include <文件名>
或
#include “文件名”
其作用是在系统编译之前,将包含文件中的内容拷贝到当前文件的当前位置之后,再进行编译。
例11-6 文件file_1.c和file_2.c的程序分别如下:
/*file_1.c*/
double Add(int a,int b)
{
return a*b;
}
/*file_2.c*/
#include "file_1.c"
void main()
{
double a,b;
double e,f;
a=3;
b=2;
e=Add(a,2);
f=Add(b+1,2);
printf("e=%ff=%f",e,f);
}
在编译file_2.c的时候,系统根据#include "file_1.c"指令将file_1.c的内容拷贝当前文件的当前位置,所以在file_2.c可以直接使用double Add(int a,int b)函数。
在软件开发中一般将符号常量、全局变量、函数声明包含在头文件(.h文件)中,并将其定义放在.c文件中。然后在使用的时候,包含对应的头文件即可。下面是常用数学库函数头文件“math.h”中的部分代码,其中_Cdecl为Turbo C系统的中保留字。
/* math.h
Definitions for the math floating point package.
Copyright (c) Borland International 1987,1988
All Rights Reserved.
*/
int _Cdecl abs (int x);
double _Cdecl acos (double x);
double _Cdecl asin (double x);
double _Cdecl atan (double x);
double _Cdecl atan2 (double y, double x);
double _Cdecl atof (const char *s);
double _Cdecl ceil (double x);
double _Cdecl cos (double x);
如果在程序中需要使用数学库函数,在文件中加入如下的代码即可。
#include <math.h>
或
#include “math.h”
#include <math.h>与#include “math.h”的区别在于遇到#include <math.h>命令时系统从缺省的头文件目录中查找文件math.h文件;遇到#include “math.h” 时系统首先从当前的目录中搜索,如果没有找到再在缺省的头文件目录中查找文件math.h文件。因此包含系统提供的库函数使用#include <math.h>方式搜索速度比较快;如果包含用户自定义的.h文件使用#include “math.h”方式,,搜索速度比较快。
提示 在使用#include指令的时候,对系统文件,使用#include <math.h>形式;对用户自定义文件,则使用#include “math.h”形式。
11.4 条件编译
为了实现在编译程序的时候,控制哪些代码参与编译,哪些代码不参与编译,C语言中引入了条件编译指令。条件编译的引入,可以将针对于不同硬件平台或软件平台的代码,编写在于同一程序文件中,从而方便程序的维护和移植。在进行软件移植的时候,可以针对不同的情况,控制不同的代码段被编译。
#ifdef … #else …#endif
#if defined… #else …#endif
#ifndef … #else …#endif
#if !defined … #else …#endif
#ifdef …#elif … #elif …#else … #endif
11.4.1 #ifdef … #else …#endif
此编译指令类似于C语言中的if else 语句,是一种典型的条件编译指令。其语法形式如下:
#ifdef 常量表达式
代码段1
#else
代码段2
#endif
其中常量表达式可以仅仅为一个编译标志。
如果常量表达式的值为真(非零值),编译代码段1部分的代码,否则编译代码段2部分的代码。
当常量表达式为简单的编译标志时,如果此编译标志在前面的代码中,已经使用#define指令定义过,且在当前的代码段中有效,则编译代码段1部分的代码,否则编译代码段2部分的代码。
例11-7 定义符号常量CONST_TRUE 和TAG_TRUE,根据不同的条件进行不同操作。
#define CONST_TRUE 1
#define TAG_TRUE
void main()
{
#ifdef CONST_TRUE
printf("The CONST_TRUE is true\n");
#else
printf("The CONST_TRUE is false\n");
#endif
#ifdef TAG_TRUE
printf("The TAG_TRUE is defined\n");
#else
printf("The TAG_TRUE is not defined\n");
#endif
}
程序的运行结果如下
The CONST_TRUE is true
The TAG_TRUE is defined
因为CONST_TRUE代表1,所以系统编译printf("The CONST_TRUE is true\n")部分代码,因此程序运行输出“The CONST_TRUE is true”的结果。如果CONST_TRUE代表0,则系统编译printf("The CONST_TRUE is false\n")部分代码。由于TAG_TRUE已经定义,所系统编译printf("The TAG_TRUE is defined\n")部分代码。
此编译指令的简单形式为单分支的条件编译指令,其语法格式如下:
#ifdef常量表达式
代码段
#endif
如果常量表达式的值为真,则编译“代码段”部分的代码,否则跳过此部分的代码。当常量表达式为编译标志时,如果此编译标志有效,则编译“代码段”部分的代码,否则跳过此部分的代码。
下面程序中,只有定义了TAG_TRUE 之后,才可以编译printf("The TAG_TRUE is defined\n")语句。
#ifdef TAG_TRUE
printf("The TAG_TRUE is defined\n");
#endif
11.4.2 #if defined… #else …#endif
此编译指令为#ifdef … #else …#endif的等价编译指令。其语法格式如下:
#if defined 常量表达式
代码段1
#else
代码段2
#endif
或
#if defined (常量表达式)
代码段1
#else
代码段2
#endif
此编译指令的简单形式为单分支的条件编译指令,其语法形式如下:
#if defined常量表达式
代码段1
#endif
11.4.3 #ifndef … #else …#endif
语法形式如下:
#ifndef 常量表达式
代码段1
#else
代码段2
#endif
如果常量表达式的值为假(零值)时,编译代码段部分的代码,否则编译代码段2部分的代码。当常量表达式为编译标志时,如果此编译标志无效则编译代码段1部分的代码,否则编译代码段2部分的代码。
此编译指令的简单形式为单分支的选择编译指令,其语法格式如下:
#ifndef常量表达式
代码段1
…
#endif
11.4.4 #if !defined … #else …#endif
此编译指令与#ifndef … #else …#endif等价,其语法形式如下:
# if !defined 常量表达式
代码段1
#else
代码段2
#endif
或
# if !defined(常量表达式)
代码段1
#else
代码段2
#endif
提示 头文件的重复包含,造成变量重复定义或函数的重复声明,此错误是初学者经常遇到的一个问题。主要的原因是在编辑自己的头文件的时候,并没有加入有效的预防措施。解决此类问题,在文件开始与结尾加入类似代码
#if !defined(MY__INCLUDED_) /*此头文件对应的编译标志*/
#define MY__INCLUDED /*此头文件没有被包含,所以编译此部分的代码*/
……
#endif // !defined(MY__INCLUDED)
11.4.5 #ifdef …#elif … #elif …#else … #endif
此条件编译指令为多分支的条件编译指令,类似于多分支的选择结构if …else if .. else ,其语法形式为
# ifdef 常量表达式1
代码段1
#elif 常量表达式2
代码段2
#elif 常量表达式3
代码段3
#endif
其中elif为else if的意思。常量表达式1、常量表达式2、常量表达式3、……为一个常量表达式或编译标志。当常量表达式1为常量表达式时,如果其值为真(非零值)则编译代码段1部分的代码,否则如果常量表达式2值为真(非零值)则编译代码段2部分的代码,依次类推。
其他等价的多分支条件编译指令如下:
形式1
# ifndef 常量表达式1
代码段1
#elif 常量表达式2
代码段2
#elif 常量表达式3
代码段3
#endif
形式2
# if defined 常量表达式1
代码段1
#elif 常量表达式2
代码段2
#elif 常量表达式3
代码段3
#endif
形式3
# if !defined ( 常量表达式1)
代码段1
#elif 常量表达式2
代码段2
#elif 常量表达式3
代码段3
#endif
提示 条件编译指令的作用是控制不同的代码被编译形成机器指令;而条件语句是控制哪些机器指令可以执行。条件编译中不同分支的代码不会同时编译并存在同一可执行文件中;而条件语句所有分支中的语句均会编译机器指令,并存放在同一可执行文件中。
11.5 其他指令
在ANSI C中除了定义上述常用的预编译指令之外,还定义了其他一些预编译指令。
1. #error
2. # pragma
3. #line
4. 运算符#和##
11.5.1 #error
语法格式如下:
#error token-sequence
其主要的作用是在编译的时候输出编译错误信息token-sequence,从方便程序员检查程序中出现的错误。例如下面的程序
#include "stdio.h"
int main(int argc, char* argv[])
{
#define CONST_NAME1 "CONST_NAME1"
printf("%s\n",CONST_NAME1);
#undef CONST_NAME1
#ifndef CONST_NAME1
#error No defined Constant Symbol CONST_NAME1
#endif
{
#define CONST_NAME2 "CONST_NAME2"
printf("%s\n",CONST_NAME2);
}
printf("%s\n",CONST_NAME2);
return 0;
}
在编译的时候输出如编译信息
fatal error C1189: #error : No defined Constant Symbol CONST_NAME1
11.5.2 # pragma
其语法格式如下:
# pragma token-sequence
此指令的作用是触发所定义的动作。如果token-sequence存在,则触发相应的动作,否则忽略。此指令一般为编译系统所使用。例如在Visual C++.Net 中利用# pragma once 防止同一代码被包含多次。
11.5.3 #line
此命令主要是为强制编译器按指定的行号,开始对源程序的代码重新编号,在调试的时候,可以按此规定输出错误代码的准确位置。
形式1
语法格式如下:
# line constant “filename”
其作用是使得其后的源代码从指定的行号constant重新开始编号,并将当前文件的名命名为filename。例如下面的程序如下:
#include "stdio.h"
void Test();
#line 10 "Hello.c"
int main(int argc, char* argv[])
{
#define CONST_NAME1 "CONST_NAME1"
printf("%s\n",CONST_NAME1);
#undef CONST_NAME1
printf("%s\n",CONST_NAME1);
{
#define CONST_NAME2 "CONST_NAME2"
printf("%s\n",CONST_NAME2);
}
printf("%s\n",CONST_NAME2);
return 0;
}
void Test()
{
printf("%s\n",CONST_NAME2);
}
提示如下的编译信息:
Hello.c(15) : error C2065: 'CONST_NAME1' : undeclared identifier
表示当前文件的名称被认为是Hello.c, #line 10 "Hello.c"所在的行被认为是第10行,因此提示第15行出错。
形式2
语法格式如下:
# line constant
其作用在于编译的时候,准确输出出错代码所在的位置(行号),而在源程序中并不出现行号,从而方便程序员准确定位。
11.5.4 运算符#和##
在ANSI C中为预编译指令定义了两个运算符——#和##。
# 的作用是实现文本替换,例如
#define HI(x) printf("Hi,"#x"\n");
void main()
{
HI(John);
}
程序的运行结果
Hi,John
在预编译处理的时候, "#x"的作用是将x替换为所代表的字符序列。在本程序中x为John,所以构建新串“Hi,John”。
##的作用是串连接。
例如
#define CONNECT(x,y) x##y
void main()
{
int a1,a2,a3;
CONNECT(a,1)=0;
CONNECT(a,2)=12;
a3=4;
printf("a1=%d\ta2=%d\ta3=%d",a1,a2,a3);
}
程序的运行结果为
a1=0 a2=12 a3=4
在编译之前, CONNECT(a,1)被翻译为a1, CONNECT(a,2)被翻译为a2。
11.6 预定义常量
在ANSI C中预先定义了如下几个符号常量(表11-1),在定义符号常量的时候,不能定义与预定义符号常量同名的常量。
表11-1 预定义符号常量
11.7 VC++中的预编译指令
1. 预编译头文件说明
2. VC的预编译功能
11.7.1 预编译头文件说明
所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、 Afxwin.H)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。
预编译头文件通过编译stdafx.cpp生成,以工程名命名,由于预编译的头文件的后缀是“pch”,所以编译结果文件是projectname.pch。
编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的。编译器认为,所有在指令#include "stdafx.h"前的代码都是预编译的,它跳过#include "stdafx. h"指令,使用projectname.pch编译这条指令之后的所有代码。
因此,所有的CPP实现文件第一条语句都是:#include "stdafx.h"。
另外,每一个实现文件CPP都包含了如下语句:
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
这是表示,如果生成调试版本,要指示当前文件的名称。__FILE__是一个宏,在编译器编译过程中给它赋值为当前正在编译的文件名称。
VC默认情况下使用预编译头(/Yu),不明白的在加入新.h文件后编译时总出现fatal errorC1010: 在查找预编译头指令时遇到意外的文件结尾的错误。解决方法是在include头文件的地方加上#include"stdafx.h",或者打项目属性,找到“C/C++”文件夹,单击“预编译头”属性页。修改“创建/使用预编译头”属性为“不使用预编译头”。
11.7.2 VC的预编译功能
这里介绍VC6的预编译功能的使用,由于预编译详细使用比较的复杂,这里只介绍几个最重要的预编译指令: /Yu, /Yc,/Yx,/Fp。其它的详细资料可以参考: MSDN -> Visual Studio 6.0 Document -> Visual C++ 6.0 Document -> VC++ Programmer Guider ->Compiler and Linker -> Details -> Creating Precompiled Header files
预编译头的概念:
所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的C/C++代码,甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。注意生成预编译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6-7M大。注意及时清理那些没有用的预编译头文件。
也许你会问:现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西(.eg Macro, Preprocessor )都要重新处理一遍。 VC的预编译头文件保存的正是这部分信息。以避免每次都要重新处理这些头文件。
根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次都编译那些不需要经常改变的代码。编译性能当然就提高了。
要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件)想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个“系统级别”的,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们会发现这个头文件里包含了以下的头文件:
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <afxdisp.h> // MFC Automation classes
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
#include <afxcmn.h>
这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文件的,所以说他们是稳定的。
那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件里只有一句代码就是:#include“Stdafx.h”。原因是理所当然的,我们仅仅是要它能够编译而已―――也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指定StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。打开project ->Setting->C/C++ 对话框。把Category指向Precompiled Header。在左边的树形视图里选择整个工程,Project Options(右下角的那个白的地方)可以看到 /Fp “debug/PCH.pch”,这就是指定生成的.pch文件的名字,默认的通常是 <工程名>.pch。然后,在左边的树形视图里选择StdAfx.cpp,这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文件的可以有YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件。
这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以下是注意事项:
1)如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,包含你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end.
2)如果你把pch文件不小心丢了,根据以上分析,你只要让编译器生成一个pch文件就可以了。也就是说把 stdafx.cpp(即指定/Yc的那个cpp文件)重新编译一遍就可以了。
【结束】
来自:http://www.cppblog.com/BlueSky/archive/2007/11/20/37000.html
今天在网上突然发现了下面几个关于c代码中的宏定义的说明,回想下,好像在系统的代码中也见过这些零散的定义,但没有注意,看到别人总结了下,发现果然很有用,虽然不知有的道可用与否,但也不失为一种手段,所以就先把它摘抄下来,增加一点见识:
1,防止一个头文件被重复包含
#ifndef BODYDEF_H
#define BODYDEF_H
//头文件内容
#endif
2,得到指定地址上的一个字节或字
#define MEM_B( x ) ( *( (byte *) (x) ) )
#define MEM_W( x ) ( *( (word *) (x) ) )
3,得到一个field在结构体(struct)中的偏移量
#define FPOS( type, field ) ( (dword) &(( type *) 0)-> field )
4,得到一个结构体中field所占用的字节数
#define FSIZ( type, field ) sizeof( ((type *) 0)->field )
5,得到一个变量的地址(word宽度)
#define B_PTR( var ) ( (byte *) (void *) &(var) )
#define W_PTR( var ) ( (word *) (void *) &(var) )
6,将一个字母转换为大写
#define UPCASE( c ) ( ((c) >= ''a'' && (c) <= ''z'') ? ((c) - 0x20) : (c) )
7,判断字符是不是10进值的数字
#define DECCHK( c ) ((c) >= ''0'' && (c) <= ''9'')
8,判断字符是不是16进值的数字
#define HEXCHK( c ) ( ((c) >= ''0'' && (c) <= ''9'') ||((c) >= ''A'' && (c) <= ''F'') ||((c) >= ''a'' && (c) <= ''f'') )
9,防止溢出的一个方法
#define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val))
10,返回数组元素的个数
#define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
11,使用一些宏跟踪调试
ANSI标准说明了五个预定义的宏名。它们是:
_LINE_ (两个下划线),对应%d
_FILE_ 对应%s
_DATE_ 对应%s
_TIME_ 对应%s
_STDC_
宏中"#"和"##"的用法
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
Printf(STR(vck)); // 输出字符串"vck"
printf("%d\n", CONS(2,3)); // 2e3 输出:2000
当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用"#"或"##"的地方宏参数是不会再展开.
#define A (2)
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
printf("%s\n", CONS(A, A)); // compile error
这一行则是:
printf("%s\n", int(AeA));
INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏.
加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数
#define STR(s) _STR(s) // 转换宏
#define CONS(a,b) _CONS(a,b) // 转换宏
printf("int max: %s\n", STR(INT_MAX)); // INT_MAX,int型的最大值,为一个变量 #include<climits>
输出为: int max: 0x7fffffff
STR(INT_MAX) --> _STR(0x7fffffff) 然后再转换成字符串;
printf("%d\n", CONS(A, A));
输出为:200
CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))
"#"和"##"的一些应用特例
1、合并匿名变量名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)
#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int); 即: static int _anonymous70; 70表示该行行号;
第一层:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__);
第二层: --> ___ANONYMOUS1(static int, _anonymous, 70);
第三层: --> static int _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;
2、填充结构
#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
IDD id;
const char * msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相当于:
MSG _msg[] = {{OPEN, "OPEN"},
{CLOSE, "CLOSE"}};
3、记录文件名
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
4、得到一个数值类型所对应的字符串缓冲大小
#define _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
--> char buf[_TYPE_BUF_SIZE(0x7fffffff)];
--> char buf[sizeof "0x7fffffff"];
这里相当于:
char buf[11];
来自 :http://blog.21ic.com/user1/8499/archives/2012/92721.html
C语言宏定义技巧
作者 仙帝将王 日期 2012-6-13 7:25:00
1,防止一个头文件被重复包含
#ifndef COMDEF_H #define COMDEF_H //头文件内容 #endif 2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。 typedef unsigned char boolean; /* Boolean value type. */ typedef unsigned long int uint32; /* Unsigned 32 bit value */ typedef unsigned short uint16; /* Unsigned 16 bit value */ typedef unsigned char uint8; /* Unsigned 8 bit value */ typedef signed long int int32; /* Signed 32 bit value */ typedef signed short int16; /* Signed 16 bit value */ typedef signed char int8; /* Signed 8 bit value */ //下面的不建议使用 typedef unsigned char byte; /* Unsigned 8 bit value type. */ typedef unsigned short word; /* Unsinged 16 bit value type. */ typedef unsigned long dword; /* Unsigned 32 bit value type. */ typedef unsigned char uint1; /* Unsigned 8 bit value type. */ typedef unsigned short uint2; /* Unsigned 16 bit value type. */ typedef unsigned long uint4; /* Unsigned 32 bit value type. */ typedef signed char int1; /* Signed 8 bit value type. */ typedef signed short int2; /* Signed 16 bit value type. */ typedef long int int4; /* Signed 32 bit value type. */ typedef signed long sint31; /* Signed 32 bit value */ typedef signed short sint15; /* Signed 16 bit value */ typedef signed char sint7; /* Signed 8 bit value */ 3,得到指定地址上的一个字节或字 #define MEM_B( x ) ( *( (byte *) (x) ) ) #define MEM_W( x ) ( *( (word *) (x) ) ) 4,求最大值和最小值 #define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) ) #define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) ) 5,得到一个field在结构体(struct)中的偏移量 #define FPOS( type, field ) \ /*lint -e545 */ ( (dword) &(( type *) 0)-> field ) /*lint +e545 */ 6,得到一个结构体中field所占用的字节数 #define FSIZ( type, field ) sizeof( ((type *) 0)->field ) 7,按照LSB格式把两个字节转化为一个Word #define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] ) 8,按照LSB格式把一个Word转化为两个字节 #define FLOPW( ray, val ) \ (ray)[0] = ((val) / 256); \ (ray)[1] = ((val) & 0xFF) 9,得到一个变量的地址(word宽度) #define B_PTR( var ) ( (byte *) (void *) &(var) ) #define W_PTR( var ) ( (word *) (void *) &(var) ) 10,得到一个字的高位和低位字节 #define WORD_LO(xxx) ((byte) ((word)(xxx) & 255)) #define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8)) 11,返回一个比X大的最接近的8的倍数 #define RND8( x ) ((((x) + 7) / 8 ) * 8 ) 12,将一个字母转换为大写 #define UPCASE( c ) ( ((c) >= ’a’ && (c) <= ’z’) ? ((c) - 0x20) : (c) ) 13,判断字符是不是10进值的数字 #define DECCHK( c ) ((c) >= ’0’ && (c) <= ’9’) 14,判断字符是不是16进值的数字 #define HEXCHK( c ) ( ((c) >= ’0’ && (c) <= ’9’) ||\ ((c) >= ’A’ && (c) <= ’F’) ||\ ((c) >= ’a’ && (c) <= ’f’) ) 15,防止溢出的一个方法 #define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val)) 16,返回数组元素的个数 #define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) ) 17,返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n) #define MOD_BY_POWER_OF_TWO( val, mod_by ) \ ( (dword)(val) & (dword)((mod_by)-1) ) 18,对于IO空间映射在存储空间的结构,输入输出处理 #define inp(port) (*((volatile byte *) (port))) #define inpw(port) (*((volatile word *) (port))) #define inpdw(port) (*((volatile dword *)(port))) #define outp(port, val) (*((volatile byte *) (port)) = ((byte) (val))) #define outpw(port, val) (*((volatile word *) (port)) = ((word) (val))) #define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val))) [2005-9-9添加] 19,使用一些宏跟踪调试 A N S I标准说明了五个预定义的宏名。它们是: _ L I N E _ _ F I L E _ _ D A T E _ _ T I M E _ _ S T D C _ 如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序 也许还提供其它预定义的宏名。 _ L I N E _及_ F I L E _宏指令在有关# l i n e的部分中已讨论,这里讨论其余的宏名。 _ D AT E _宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。 源代码翻译到目标代码的时间作为串包含在_ T I M E _中。串形式为时:分:秒。 如果实现是标准的,则宏_ S T D C _含有十进制常量1。如果它含有任何其它数,则实现是 非标准的。 可以定义宏,例如: 当定义了_DEBUG,输出数据信息和所在文件所在行 #ifdef _DEBUG #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_) #else #define DEBUGMSG(msg,date) #endif 20,宏定义防止使用是错误 用小括号包含。 例如:#define ADD(a,b) (a+b) 用do{}while(0)语句包含多语句防止错误 例如:#difne DO(a,b) a+b;\ a++; |