C Programming: A Modern Approach

第14章 预处理器

2020-03-09  本文已影响0人  橡树人

英文原版:P315

诸如#define#include预处理指令都是由预处理指令是由预处理器来处理的。

预处理器是一个在编译前对C程序进行编辑的小型软件。

C语言对预处理器的依赖使其在主流编程语言中显得与众不同。

预处理器虽然是一个功能强大的工具,但也可能产生许多难发现的错误。而且,预处理器很容易被误用来编写几乎读不懂的程序。因此,建议:不要过分依赖预处理器,请适度地使用

本章的主要内容

14.1 预处理器是如何工作的?

什么是预处理指令?

#开头的指令,末尾不包含分号``;

预处理指令的工作原理

注意事项:

例1 celsius.c

/* Converting a Fahrenheit temperature to Celsius */

#include <stdio.h>

#define FREEZING_PT 32.0f
#define SCALE_FACTOR (5.0f/9.0f)


int main(void)
{
    float fahrenheit, celsius;

    printf("Enter Fahrenheit temperature: ");
    scanf("%f", &fahrenheit);

    celsius = (fahrenheit - FREEZING_PT) * SCALE_FACTOR;

    printf("Celsius equivalent: %.1f\n", celsius);
    
    return 0;
}

生成.i 文件

gcc -E celsius.c -o celsius.i

文件celsius.i

空行
空行
从stdio.h中引入的行
空行
空行
空行
空行
int main(void)
{
 float fahrenheit, celsius;

 printf("Enter Fahrenheit temperature: ");
 scanf("%f", &fahrenheit);

 celsius = (fahrenheit - 32.0f) * (5.0f/9.0f);

 printf("Celsius equivalent: %.1f\n", celsius);

 return 0;
}

解释:
在这个例子中,预处理器都做了哪些事?

  1. 预处理器执行#include指令:
    • 引入stdio.h的内容
  2. 预处理器执行#define指令:
    • 移除#define指令;
    • 将源文件中出现FREEZING_PTSCALE_FACTOR的地方替换为宏定义的值;
  3. 用空格符来替换注释

14.2 预处理指令都遵循哪些规则?

常见的预处理指令有哪几类?

  1. 宏定义
    可使用#define来定义一个宏;
    可使用#undefine来移除一个宏的定义;
  2. 文件包含
    可使用#include来让一个程序包含某个具体文件的内容;
  3. 条件编译
    基于预处理器测试的条件,可使用#if#ifdef#ifndef#elif#else#endif来将一个文本块包含到程序中,或者从程序中移除一个文本块。

所有的预处理指令都遵循的一些规则:

例1 需要多行的单条指令

#define DISK_CAPACITY (SIDES *                     \
                       TRACKS_PER_SIDE *          \
             SECTORS_PER_TRACK *          \
                       BYTES_PER _SECTORS)

例2 在宏的定义后面加上注释

#define FREZZING_PT 32.0f //水的凝固点

14.3 宏的定义

如何定义宏?

宏定义注意事项:
不要在宏定义里添加额外的字符

错误示例1 在宏定义里放等号=

#define N = 100
...
int a[N];//等价于int a[=100]

错误示例2 在宏定义里放分号;

#define N 100;
...
int a[N];//等价于int a[100;]

不带参数的宏

格式:

#define indentifier replace-list

常用情形:
给数值常量、字符常量、字符串字面量起个别名

解释:

例1 无参数宏定义

#define STR_LEN 80
#define TRUE 1
#define FALSE 0
#define PI 3.14159
#define CR '\r'
#define EOS '\0'
#define MEM_ERR "Error: not enough memeory"

使用宏来给常量起别名有若干个好处:

带参数的宏

格式:

宏定义宏调用

预处理器是如何处理带参数宏的?

带参数的宏的用法:

例1 宏定义

#define MAX(x,y) ((x)>(y):(x),(y))

解释:

例1 宏定义

#include <stdio.h>

#define MAX(x,y) ((x)>(y):(x),(y))
#define IS_EVEN(n) ((n)%2==0)

int main(void)
{
    int i;

    i = 98;
    if(IS_EVEN(i)){
        i++;
    }

    printf("%d\n", i);

    MAX(100,34);
    return 0;
}

例2 复杂点的带参数宏定义

#define TOUPPER(c) ('a'<(c)&&(c)<='z'?(c)-'a'+'A':(c))

例3 空参数列表的带参数宏定义

#define getchar() getc(stdin)

函数调用都有哪些运行时开销?

如何避免函数调用的运行时开销?

带参数的宏 VS 真正的函数
优点:

函数调用在程序执行过程中是有开销的,比如保存上下文信息、拷贝参数等。
宏调用没有运行时开销。

宏支持泛型
宏的形参跟函数形参不一样,没有特定的类型。
宏可接受任意类型的实参

带参数宏的缺点有哪些?

导致编译后的代码过长。
每个宏调用都会导致在源代码中插入宏的替换列表,因此会增加源代码的长度。
宏使用的次数越多,这种效果越明显。特别是当嵌套进行宏调用时,效果会加倍。

编译器不对宏调用实参进行类型检查,也不会发生类型转换。
当函数调用发生时,编译器会挨个检查实参来看其是否有合适的类型。如果存在某个实参不是合适的类型,则该实参就会被转换成合适的类型,或者发出一条错误消息。

不存在指向宏的指针
存在指向函数的指针
因为宏在预处理后就被移除了。

宏调用可能对实参求值多次
函数调用只会对实参求值一次
如果实参有副作用的话,则对一个参数求值多次会出现不在期望内的行为。

带参数的宏

带参数的宏注意事项:

对宏实参多次求值导致的错误是很难被发现的,因为宏调用跟函数调用看起来是一样的。
更糟的是,一个宏在大部分时候是正常工作的,仅对某些有副作用的参数会有错误。
最好不要使用有副作用的实参。

14.4 条件编译指令

知识补充

什么是条件编译?
根据其执行的测试的结果,预处理器可包含或者移除一段程序文本。

#if指令和#endif指令

格式:

#if 常量表达式
....
#endif 

预处理器是如何处理#if指令和#endif指令的?
当遇见#if指令时,预处理器会对常量表达式求值。

注意事项:
如果在#if中出现未定义的宏,则#if指令会默认该未定义的宏的值是0。

例1 保留测试代码,且让编译器忽略这些代码

#include <stdio.h>

#define DEBUG 1

int main(void)
{
    int i, j;

    i = 100;
    j = 98;

    #if DEBUG
    printf("Value of i : %d\n", i);
    printf("Value of j : %d\n", j);
    #endif  /** DEBUG*/

    return 0;
}

defined运算符

defined运算符是预处理器特有的运算符。

功能:

例2 defined运算符

#include <stdio.h>

//注意这里没有给DEBUG定义一个值,但是预处理后的文件中仍包含了printf语句
#define DEBUG

int main(void)
{
    int i, j;

    i = 100;
    j = 98;
        
    // defined只是测试DEBUG是否已被定义过
    #if defined(DEBUG)
    printf("Value of i : %d\n", i);
    printf("Value of j : %d\n", j);
    #endif  /** DEBUG*/

    return 0;
}

ifdef指令和ifndef指令

#ifdef

#ifndef

例1 等价关系

#ifdef 标识符

等价于

#if defined(标识符)

例2 等价关系2

#ifndef 标识符

等价于

#if !defined(标识符)

#elif指令和#else指令

为了提供多一点便利,预处理器还支持:#elif指令和#else指令

格式:

#elif 常量表达式

#else
上一篇 下一篇

猜你喜欢

热点阅读