预处理

预处理也称为预编译,它为编译做预备工作,主要进行代码文本的替换工作,用于处理#开头的指令,其中预处理器产生编译器的输出。

经过预处理器处理的源程序与之前的源程序会有所不同,在预处理阶段所进行的工作只是纯粹地替换与展开,没有任何计算功能,所以在学习#define命令时只有真正地理解这一点,才不会对此命令引起误解并误用。

define的缺陷

ifndef/define/endif的作用

如果一个项目中存在两个C文件,而这两个C文件都include (包含)了同一个头文件,当编译时,这两个C文件要一同编译成一个可运行文件,可能会产生大量的声明冲突。而解决的办法是把头文件的内容都放在#ifhdef和#endif中,一般格式如下:

#ifhdef〈标识〉
#define〈标识〉
#endif

上述代码的作用是当“当标识没有由#defme定义过时,则定义标识。<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下画线,并把文件名中的也变成下画线,如stdio.h。

1
2
3
#ifhdef_STDIO_H_
#define STDIO H
#endif

在#ifndef中定义变量出现的问题(一般不定义在#ifndef中)如下所示:

1
2
3
4
#ifndef AAA
#define AAA
int i;
#endif

里面有一个变量定义,在VC中链接时就出现了 i重复定义的错误,而在c语言中成功编译。

define 示例

由于宏定义是预处理指令,而非语句,所以在进行宏定义时,不能以分号结束。

预处理只会执行简单的替换,不会计算表达式的值,所以需要注意括号的使用.

计算一年的秒数

#define SECOND_PER_YEAR (60 * 60 * 24 * 365)UL

比较两个数a和b的大小

如果只是进行简单的比较,则返回比较结果即可,宏定义可以写为如下形式:

#define check(a,b) (((a)>(b))==fabs((a)-(b)))? “greater”: “smaller”

求int占用的字节数

#define MySizeof(Value) (char* )(&Value + 1) - (char* )&Value

上例中,(char* )& Value返回Value的地址的第一个字节,(char* Value +1)返回Value的地址的下一个地址的第一个字节,所以它们之差为它所占的字节数。

求结构体的内存偏移

#define OffSet(type, field) ((size_t)&((type *)0 -> field))

在C语言中,ANSI C标准允许值为0的常量被强制转换成任何一种类型的指针,而且转换结果是一个空指针,即NULL指针,因此对0取指针的操作((type *)0)的结果就是一个类型为type*的NULL指针。

但如果利用这个NULL指针来访问type的成员当然是非法的,因为&(((type *)0)->field)的意图只不过是计算field字段的地址。

C语言编译器根本就不生成访问type的代码,而仅仅是根据type的内容布局和结构体实 例首址在编译期计算这个(常量)地址,这样就完全避免了通过NULL指针访问内存可能出 现的问题。同时又因为地址为0,所以这个地址的值就是字段相对于结构体基址的偏移。

判断数组中有多少个元素

#define Count (sizeof(array)/sizeof(array[0]))

只需要用整个数组的sizeof去除以一个元素的sizeof即可求出数组中元素的个数

得到一个字的高位字节和低位字节

#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte) ((word)(xxx) >>8))

字符串连接

在#define中,标准只定义了#和##两种操作。#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串。

补充: 在用#define 定义时 ,可以用斜杠(“\”) 续行.与vb中的下划线(“ _”)作用同. 比如:

#define add1( x, y ) ( x + y)

也可以表示成 :

#define add1(x,y) \
(x + y )
#define Conn(x,y) x##y

记号粘贴操作符(token paste operator): ##

简单的说,“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。

其中,分隔的作用类似于空格。我们知道在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些##来替代空格。

另外一些分隔标志是,包括操作符,比如 +, -, *, /, [,], …,所以尽管下面的宏定义没有空格,但是依然表达有意义的定义: define add(a, b) a+b,而其强制连接的作用是,去掉和前面的字符串之间的空格,而把两者连接起来。

连接符##用来将两个token连接为一个token,但它不可以位于第一个token之前or最后一个token之后。注意这里连接的对象只要是token就行,而不一定是宏参数。

举列 – 试比较下述几个宏定义的区别

1
2
3
4
#define A1(name, type)  type name_##type##_type 或
#define A2(name, type)  type name##_##type##_type
A1(a1, int);  /* 等价于: int name_int_type; */
A2(a1, int);  /* 等价于: int a1_int_type;   */

解释:

  1. 在第一个宏定义中,”name”和第一个””之间,以及第2个””和第二个”type”之间没有被分隔,所以预处理器会把name_##type##type解释成3段:

    “name”、“type”、以及“_type”,这中间只有“type”是在宏前面出现过的,所以它可以被宏替换。

  2. 而在第二个宏定义中,“name”和第一个“”之间也被分隔了,所以 预处理器会把name####type##type解释成4段:“name”、“”、“type”以及“_type”,这其间,就有两个可以被宏替换了。

  3. A1和A2的定义也可以如下:

     #define A1(name, type)  type name_  ##type ##\_type    
                           <##前面随意加上一些空格>  
     #define A2(name, type)  type name ##_ ##type ##_type  
    

    结果是## 会把前面的空格去掉完成强连接,得到和上面结果相同的宏定义

#define ToChar(x) #@x

就是给x加上单引号,结果返回是一个const char。 举例说:

char a = ToChar(1);

结果就是a=’1’;做个越界试验char a = ToChar(123);结果是a=’3’;

但是如果你的参数超过四个字符,编译器就给给你报错了!

#define ToString(x) #x

#的功能是将其后面的宏参数进行字符串化操作,意思就是对它所应用的宏变量通过替换后在其左右各加上一个双引号。

char* str = ToString(123132);就成了str="123132";

#示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#define TRACE(S) (printf("%s\n", #S), S) /*注意用逗号而不是分号*/ 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
int main() 
{ 
    int a=5; 
    int b=TRACE(a); 
    const char *str="hello"; 
    char des[50]; 
    strcpy(des,TRACE(str)); 
    puts(des); 
    system("pause"); 
    return 0; 
} 

输出:

a
str
hello

解析:

TRACE(a),宏定义中的S为a,所以输出a,然后因为printf后面有逗号操作符,所以返回a,赋值到b。

做TRACE(a)、TRACE(str)的时候输出的并非是5和hello,而是它们本身,a和str。

#示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>
#define M 1
#define N 2
#define A(a,b) a##b, a+b
#define B(a)   #a,a
int main()
{
char *MN = "here";
    printf("%s, %d, %d\n", B(A(1,2))); 
    printf("%s, %d\n", A(M,N)); 
    return 0;
}

输出:

A(1,2), 12, 3
here, 3

解析:

宏定义无## 和 #, 参数继续展开;否则就不继续展开

B(a)宏定义有#,带#的a A(1,2)不会继续展开,不带#的展开成12, 1+2;

MN被强制转化为指向字符串首地址的指针。输出“here”