preprocess

预处理

l  #error tokens :将打印出与系统实现相关的信息,信息中包括#error预处理命令中给定的标记。标记是一些用空格分割的字符。

l  #pragma tokens:执行一个系统实现中已经定义好的操作,不能被系统实现识别出来的pragma将被忽略掉。

l  #:将会把替换文本中的一个标记转换成一个用引号括起来的字符串,比如#define HELLO(x) printf(“Hello, ” #x “\n”);,当程序中出现HELLO(leo)时,将被展开为:printf(“Hello leo\n”);

l  ##:用来将两个标记拼接在一起,例如#define TOKENCONCAT(x,y)  x##y,当程序中出现TOKENCONCAT(O,K)将会被OK替换。And##运算符必须有两个操作数;

l  #line:使得在它之后的后继程序代码行,按照命令中给定的整型常数值,重新编排序号。

l  __LINE__:源程序文件中当前代码行的行号

l  __FILE__:假定的文件名

l  __DATE__:源代码被编译的日期,格式为月日年

l  __TIME__:源代码被编译的时间,格式为时分秒

l  __STDC__

 

 

Mark:可以在源程序多处使用断言assert,在发行版本中只要定义宏#define NDEBUG就可以取消断言,而不用逐条删除。

预处理器

预处理器

       编译一个C程序涉及很多步骤,其中第一个步骤被称为预处理preprocessing阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作,它的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换由#define指令定义的符号以及确定代码的部分内容时候应该根据一些条件编译指令进行编译

预定义符号

符号

含义

__FILE__

进行编译的原文件名

__LINE__

文件当前行的行号

__DATE__

文件被编译的日期

__TIME__

文件被编译的时间

__STDC__

如果编译器遵循ANSI C,其值就为1,否则未定义

 

#define

       对于多行的宏定义,可以使用反斜杠来换行。

clip_image002

       我们也可以使用#define指令把一序列语句插入到程序中,例如:

clip_image004

关于宏的使用,有诸多注意的事项。比如,下面定义一个宏:

clip_image006

       所以对于上面的例子,如果我们定义如下就没有问题了:

clip_image008

 

这里再说另外一种情况:

clip_image010

对于上面的情况,我们可以用如下的方法定义(多加一个括号):

clip_image012

#define替换

       宏参数和#define定义可以包含其他#define定义的符号,但是,宏不可以出现递归

clip_image014式中的#xxx会把xxx转换为一个字符串;而##结构则执行一种不同的任务,它把位于它两边的符号链接成一个符号,比如value=5;那么#define test(x)  sum##value += x;中的sum##value会改为sum5

宏与函数

       宏非常频繁地用于执行简单的计算,比如在两个表达式中寻找其中较大或较小的一个:

clip_image016

       从上式中,我们看到通篇的括号,哈哈clip_image017,就是为了方式出现上面曾说过的错误。

       为什么不用函数来完成上面的任务呢

l  首先,用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹

l  再者,函数的参数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用,反之,上面的这个宏可以用于整型、长整型、单浮点型、双浮点型以及其他任何可以使用>操作符比较值大小的类型,也就是说,宏是与类型无关的

l  还有一些任务根本无法用函数实现,比如,下面的type参数类型:

clip_image019

 

和使用函数相比,使用宏的不利之处在于:一份宏定义代码的拷贝都将插入到程序中,除非宏比较短,否则使用宏可能会大幅度增加程序的长度

宏和函数的不同之处

属性

#define

函数

代码长度

每次使用时,宏代码都会插入到程序中,除了非常小的宏外,程序的长度将大幅度增长

函数代码值出现在一个地方;每次使用这个函数时,都调用那个地方的同一份代码

执行速度

更快

存在函数调用/返回的额外开销

操作符优先级

宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果

函数参数只在函数调用时求值一次,它的结果值传递给函数,表达式的求值结果更容易预测

参数求值

参数每次用于宏定义时,它们都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预测的结果

参数在函数被调用前置求值一次,在函数中多次使用参数并不会导致多种求职过程,参数的副作用并不会造成任何特殊的问题

参数类型

宏与类型无关,只要对参数的操作是合法的,它可以使用于任何参数类型

函数的参数是与类型有关的,如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的。

命令行定义

如果函数中有int array[ARRAY_SIZE];可以在编译时使用-D选项来指定该值的大小,比如

gcc –DARRAY_SIZE=1000   test.c就可以将程序中的该值设置为1000

文件包含

       对于#include指令,预处理会删除这条指令,并用包含文件的内容取而代之,这样,一个头文件如果被包含到10个源文件中,它实际上被编译了10次。

       #include有两种方式<>””,其实对于标准的类似stdio.h也可以使用””来引用,只是会浪费一些时间。

       多重包含多出现在大型程序中,此时如果多重复制同一份头文件会导致程序变大变慢,解决多重包含头文件的方法为:

clip_image021

       以后每个头文件都应该加上这个条件语句。

总结

l  宏与类型无关,这是一个优点;宏的执行速度快于函数,因为宏不存在函数调用/返回的开销,但是,使用宏会增加程序的长度,函数不会;同样,具有副作用的参数可能在宏的使用过程中产生不可预料的结果,而函数参数的行为更容易预测。

l  注意在宏定义中使用的参数,要在它们周围和整个宏定义的两边加上括号;