Chinaunix首页 | 论坛 | 博客
  • 博客访问: 119374
  • 博文数量: 19
  • 博客积分: 329
  • 博客等级: 一等列兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-10 09:58
文章分类

全部博文(19)

文章存档

2012年(3)

2011年(14)

2009年(2)

分类: C/C++

2011-09-03 15:42:21

Macro(宏)来源于希腊语,意思就是大,远.在计算机科学中指的是规则,这个规则指定了
输入和输出之间的关系(是不是优点类似于函数?).所有的宏都是一个概念,那就是输入
与输出之间的映射关系,这个和函数本质上是相同的.但是宏和函数处理方式基本上是不同
的,比如在C语言中,宏是在preprocess阶段被替换掉,而函数(讨论一般意义上的函数,不
讨论inline函数)在运行时调入栈中运行.而且不同的语言对宏的处理也是不同的,比如C
语言中,宏是无法区分作用域的(编译单元内的作用域),但是在metapost中,恰恰是可以

在宏体里定义局部变量的.参看


下面讨论C语言中的宏.
谈到宏,不得不提到preprocess directives(预处理指令).预处理指令是预处理器在
预处理阶段所要执行的操作.具体在宏中就是执行宏替换功能.基本的一个就是#define.

好,现在先谈谈宏的定义.
宏就是处理输入和输出的规则.所以更直观的一点就是带个输入参数.

  1. #define ADD(foo)  foo+10  

这样就定义了一个宏add,它的输入是foo,输出是foo+10,规则就是输入加上10.那么在
预处理阶段是如何处理的呢?比如,


  1. bar.c  
  2.   
  3. #define ADD(foo) foo+10  
  4.   
  5. int main()  
  6. {  
  7.     ADD(3);  
  8.     return 0;  
  9. }  

这个程序在预处理阶段会被替换成
  1. int main()  
  2. {  
  3.     3+10;  
  4.     return 0;  
  5. }  

可以查阅相关的编译器手册,指定相应的选项(options),来察看这个结果.在GCC中,使用
-E选项(其中会输出一些额外的信息,现在不必理会).这个宏,可以带参数,形式上和函数
很像,所以有的人会称它为宏函数,这是个非正式称呼.

更常见的定义宏的方式就是没有输入(即没有参数),比如,

  1. #define PI 3.14  

定义了宏PI, 没有输入,输出为3.14,规则就是得到3.14.处理方式同上.形式上,和常量很
相像,所以又有了宏常量的称呼.
#define FREEBSD
定义了宏FREEBSD,这个宏没有输入,没有输出,没有规则,仅仅是定义了这个宏.

但是请记住,宏和函数,在功能上是一样的,都是处理输入得到输出的过程.只不过语言或者
编译器对他们的处理方式不一样.

C语言中,一些内置的宏:
这些内置的宏,不需要自己再define了,直接拿来用就可以.
__LINE__   表示当前行号.
__FILE__   表示编译单元的文件名
__DATE__   表示编译时的日期字符串
__TIME__   表示编译时的时间字符串
__STDC__   是否是符合标准,如果是结果为1,否则为0,这个主要是针对编译器编译时的选项.
更多的宏参考在线文档

注意,很多人在调试的时候使用__FUNCTION__(gcc扩展)或者__func__(C99标准),这两个都
不是宏,应为在预处理阶段根本无法确认函数.

宏定义需要注意的问题
1.宏是预处理阶段的替换(expansion),不进行计算,所以要时刻把()加上.
拿上面定义的一个例子来看:

  1. bar.c  
  2.   
  3. #define ADD(foo) foo+10  
  4.   
  5. int i = 2 * ADD(3);  

语句会被替换成:

  1. int i = 2 * foo+10;  

这样就错误了.本意是要3+10,然后再乘以2,结果为26,现在变成了30了.
  1. #define ADD(foo) (foo+10)  

最好把参数全部加上(),对于这个例子,没有必要,但是对其他的例子就很有必要了:
  1. #define MUL(foo, egg) foo*egg  
  2. MUL(2+3,4+5);  

期望的结果是(2+3)*(4+5)=45,结果却成了2+3*4+5=19.
合理的定义方式是:
  1. #define MUL(foo, egg) ((foo) * (egg))  

2.宏是否可以用来定义注释.

  1. #define HEAD_COMMENT /*  
  2. #define END_COMMENT */  

答案是否定的.注释的处理在预处理指令处理之前,也就是说在define指令被处理之前注释
已经不存在,如果在预处理指定处理过后又出来了定义的话,编译器一定会不认识.



Preprocessor(预处理器),在计算机科学中指的是一个处理代码输入并且把输出做为另外
一个程序输入的程序,它也是一个程序.参看:

GNU的预处理器可以处理C/C++/Objective-c,因为都和C沾边(或者就是从开始遗留下来的
习惯)所以就叫C PreProcessor(cpp 不是c plus plus:-) )

CPP处理预处理指令,有#include,#define(undef),#if(ifdef,ifndef,else if,elif,endif),#error
,#line, #pragma,#,##,当然预处理也处理注释(comment),而且优先于预处理指令.

1.文件包含(file inclusion)

#include指令

  1. #include       从标准路径搜索stdio.h  
  2. #include "nids.h"  
  3.     从当前目录开始搜索nids.h如果找不到再到命令行参数指定的目录寻找  
  4.     最后是标准目录下寻找.  
  5.       

2.宏定义(macros defination)

前述.


3.条件编译(conditinal compilation)

  1. #if __STDC__ == 1  
  2. #if defined FREEBSD  
  3. #if !defined FREEBSD  
  4. #endif  
  5.   
  6. #ifdef FREEBSD  
  7. #else if  
  8. #define LINUX  
  9. #endif  

4.错误产生(error generation)
#error this line shouldnt be reached

5.行号控制(line control)

  1. #line 299 "ip.c"  下一行是ip.c的299行  
  2. #line 299         下一行是本文件的299行  

6 #,##
#和##常常用于宏定义里,这两个是预处理运算符,不是指令,因为预处理指令不能用于宏定义.就像C99添加的_Pragma操作符.

  1. #define tempfile(dir) #dir "/%s"  
  2. tempfile(/usr/tmp)被扩展成  "/usr/tmp" "/%s",也就是 "/usr/tmp/%s"  

#把宏参数字符串化(C语言表示的字符串),而且这个的"优先级"比较高.
看一个来自cpp手册的例子:
  1. #define xstr(s) str(s)  
  2. #define str(s) #s  
  3. #define foo 4  
  4. str (foo)       由于有#,表示参数首先被字符串化,所以不再有任何扩展.  
  5.     → "foo"  
  6. xstr (foo)      没有#,所以参数被扩展.  
  7.     → xstr (4)  
  8.     → str (4)  
  9.     → "4"  

解释:
s is stringified when it is used in str, so it is not macro-expanded first. But
s is
an ordinary argument to xstr, so it is completely macro-expanded before xstr
itself is
expanded . Therefore, by the time str gets to its argument, it has already been macro-expanded.

  1. #define cat(x,y) x##y  
  2. cat(2,3)   扩展为23  
  3. cat(wolf,python) 扩展为wlfpython  

##把tokens连接起来(tokens concatenation)
通常##两边的tokens可以是标识符,数字或者运算符,不可以是C语言表示的字符串.
比如cat("2","3")是错误的.

7.#pragma
#pragma用于向编译器提供信息.(由于是指令,所以无法用于宏定义,C99定义了
_Pragma运算符)

#pragma once 可以用于防止头文件被重复包含(另外一种方式是使用条件编译)

8.空指令 #
#后面仅跟着一个换行符,就是一个空指令,什么也不做,在预处理阶段没有动它.


阅读(2236) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~