您当前的位置:首页 > 计算机 > 编程开发 > C语言

C语言预处理机制――学习笔记

时间:12-31来源:作者:点击数:

一、变量式宏定义(Oject-like Macro)

宏只是进行简单的字符串替换:
    #define N 20
    #define STR "hello, world\n"

 二、函数式宏定义(Function-like Macro)

宏可以类似函数一样使用:
    #define MAX(a, b) ((a)>(b)?(a):(b))

下面是他的几个特点特点:

  • 参数没有类型,只是形式上的替换,不过类型检查,传参数时要格外小心
  • 调用真正的函数和调用函数式宏定义的代码编译生成的指令不同
  • 定义时要注意每个参数要加括号,最外层也要加括号
  • 调用函数时先求实参表达式再传给形参,如果实参表达式有side effect,这些side effect只发生一次
  • 有可能重复求值,小心定义这类宏

以下是几个应用举例:

#define sdram_selfrefresh_disable(saved_lpr0) \
    do
    { \
        at91_ramc_write(0, AT91_DDRSDRC_LPR, saved_lpr0); \
        at91_ramc_write(1, AT91_DDRSDRC_LPR, saved_lpr1); \
    }
    while (0)

这里的宏用do---while括起来就是为了让这个宏替换的代码总是当作一个整体看待。

三、内联函数(inline function)

内联函数是为了减少因函数调用产生的开销,会在调用函数时进行代码展开,而不是函数调用的方式,弊端是会导致编译文件变大:
    static inline int rwsem_is_locked(struct rw_semaphore *sem) { return sem->count != 0; }

四、#和##运算符和可变参数

#和##是预处理运算符,在函数式宏定义中,#运算符后面应该跟一个形参,之间用空格或Tab分割,用于创建字符串字面值:
#define STR(s) # s STR(hello world)

调用STR宏产生字符串字面值“hello world”。

在宏定义中##运算符把前后两个预处理Token连接成一个预处理Token,并且变量式宏定义中也可以用##运算符:
    #define CONCAT(a, b) a##b
    CONCAT(con, cat)

预处理之后是concat,比如定义一个宏展开为两个#符号:
    #define HASH_HASH # ## # 

注:中间空格不能少,因为####根据最长匹配原则是看作两个##,报错,因为预处理Token不能出现在开头或末尾。

函数式宏定义也有可变参数:
    #define showlist(...) printf(#__VA_ARGS__)
    #define report(test,...) ((test)?printf(#test):\
    printf(__VA_ARGS__))
    showlist(The first ,second, and third items.);
    report(x>y, "x is %d but y is %d",x,y);

预处理展开,宏定义中可变参数部分用__VA_ARGS__表示:
    printf("The first ,second, and third items.");
    ((x>y)?printf("x>y"):printf("x is %d but y is %d",x,y));

当__VA_ARGS__是空参数时,##运算符会把前面的逗号吃掉。

注:真正的宏应该学习Common Lisp的宏,体会宏的强大!

五、#undef预处理指示

如果用#define重复定义宏,规定这些宏定义必须一模一样,否则报错。

如果用#undef取消宏定义,取消一个没有定义的宏不会报错。

六、宏展开的步骤。

举例:
#define sh(x)
printf("n" #x "=%d, or %d\n",n##x,alt[x])
#define sub_z 26 sh(sub_z)

展开过程:

1、x的实参是sub_z

2、#x替换为sub_z

3、n##x替换为nsub_z

4、sh(x)展开为:printf("n sub_z =%d, or %d\n",nsub_z,alt[sub_z]);

5、其实sub_z最先替换为26

6、第4步的展开真实是:printf("n 26 =%d, or %d\n",n26,alt[26]);

七、条件预处理指示

实例1:
#ifndef HEADER_FILENAME
#define HEADER_FILENAME
/*body of header*/
#endif

实例2:
#if MACHINE == 68000 int x;
#elif MACHINE == 8086 long x;
#else #error UNKOWN TARGET MACHINE
#endif

实例3:
#ifdef HEADER_FILENAME
#define HEADER_FILENAME
#undef HEADER_FILENUM
/*body of header*/
#endif

实例4:
    #if defined x
等于
    #ifdef x

    #if !defined x
等于
    #ifndef x

实例5:
#if 0
....
#endif

八、其它预处理特性

  • #pragma预处理指示供编译器实现一些扩展特性,C标准没有规定#pragma后面写什麽和起什麽作用,由编译器自己决定
  • 宏__FILE__代表当前源文件的文件名
  • 宏__LINE__代表展开成当前代码行的行号,是编译器内建的特殊宏定义
  • C标准规定C标准库的头文件是相互独立的
  • C99新特殊标识符__func__是一个变量名不是宏定义,因此不在预处理阶段求值,显示上一个调用的函数名称
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门