• C/C++条件编译:#ifdef、#else、#endif等


    程序员可能要为不同的工作环境准备C程序和C库包。不同的环境可能
    使用不同的代码类型。预处理器提供一些指令,程序员通过修改#define的值
    即可生成可移植的代码。#undef指令取消之前的#define定义。#if、#ifdef、
    #ifndef、#else、#elif和#endif指令用于指定什么情况下编写哪些代码。#line
    指令用于重置行和文件信息,#error指令用于给出错误消息,#pragma指令用
    于向编译器发出指令。

    #undef指令

    #undef指令用于“取消”已定义的#define指令。也就是说,假设有如下定义:

    #define LIMIT 400
    
    • 1

    然后,下面的指令:

    #undef LIMIT
    
    • 1

    将移除上面的定义。现在就可以把LIMIT重新定义为一个新值。即使原
    来没有定义LIMIT,取消LIMIT的定义仍然有效。如果想使用一个名称,又
    不确定之前是否已经用过,为安全起见,可以用#undef 指令取消该名字的定
    义。

    从C预处理器角度看已定义

    处理器在识别标识符时,遵循与C相同的规则:标识符可以由大写字
    母、小写字母、数字和下划线字符组成
    且首字符不能是数字。当预处理器
    在预处理器指令中发现一个标识符时,它会把该标识符当作已定义的或未定
    义的。这里的已定义表示由预处理器定义。如果标识符是同一个文件中由前
    面的#define指令创建的宏名,而且没有用#undef 指令关闭,那么该标识符是
    已定义的。如果标识符不是宏,假设是一个文件作用域的C变量,那么该标
    识符对预处理器而言就是未定义的。

    已定义宏可以是对象宏,包括空宏或类函数宏:

    #define LIMIT 1000 // LIMIT是已定义的
    #define GOOD // GOOD 是已定义的
    #define A(X) ((-(X))*(X)) // A 是已定义的
    int q; // q 不是宏,因此是未定义的
    #undef GOOD // GOOD 取消定义,是未定义的
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意,#define宏的作用域从它在文件中的声明处开始,直到用#undef指
    令取消宏为止,或延伸至文件尾(以二者中先满足的条件作为宏作用域的结
    束)。

    另外还要注意,如果宏通过头文件引入,那么#define在文件中的位置
    取决于#include指令的位置。

    稍后将介绍几个预定义宏,如__DATE__和__FILE__。这些宏一定是已
    定义的,而且不能取消定义。

    条件编译

    可以使用其他指令创建条件编译(conditinal compilation)。也就是说,
    可以使用这些指令告诉编译器根据编译时的条件执行或忽略信息(或代码)
    块。

    1.#ifdef、#else和#endif指令

    我们用一个简短的示例来演示条件编译的情况。考虑下面的代码:

    #ifdef MAVIS
    	#include "horse.h"// 如果已经用#define定义了 MAVIS,则执行下面的指令
    	#define STABLES 5
    #else
    	#include "cow.h" //如果没有用#define定义 MAVIS,则执行下面的指令
    	#define STABLES 15
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里使用的较新的编译器和 ANSI 标准支持的缩进格式。如果使用旧的
    编译器,必须左对齐所有的指令或至少左对齐#号,如下所示:

    #ifdef MAVIS
    #	include "horse.h" // 如果已经用#define定义了 MAVIS,则执行下面的指令
    #	define STABLES 5
    #else
    #	include "cow.h" //如果没有用#define定义 MAVIS,则执行下面的指令
    #	define STABLES 15
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    #ifdef指令说明,如果预处理器已定义了后面的标识符(MAVIS),则
    执行#else或#endif指令之前的所有指令并编译所有C代码(先出现哪个指令
    就执行到哪里)。如果预处理器未定义MAVIS,且有 #else指令,则执行
    #else和#endif指令之间的所有代码。

    #ifdef #else很像C的if else。两者的主要区别是,预处理器不识别用于标
    记块的花括号({}),因此它使用#else(如果需要)和#endif(必须存在)
    来标记指令块。这些指令结构可以嵌套。也可以用这些指令标记C语句块

    ifdef.c程序

    /* ifdef.c -- 使用条件编译 */
    #include 
    #define JUST_CHECKING
    #define LIMIT 4
    int main(void){
    	int i;
    	int total = 0;
    	for (i = 1; i <= LIMIT; i++){
    		total += 2 * i*i + 1;
    		#ifdef JUST_CHECKING
    		printf("i=%d, running total = %d\n", i, total);
    		#endif
    	}
    	printf("Grand total = %d\n", total);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    编译并运行该程序后,输出如下:

    i=1, running total = 3
    i=2, running total = 12
    i=3, running total = 31
    i=4, running total = 64
    Grand total = 64
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果省略JUST_CHECKING定义(把它放在C注释中,或者使用#undef指
    令取消它的定义)并重新编译该程序,只会输出最后一行。可以用这种方法
    在调试程序。

    定义JUST_CHECKING并合理使用#ifdef,编译器将执行用于
    调试的程序代码,打印中间值。调试结束后,可移除JUST_CHECKING定义
    并重新编译。如果以后还需要使用这些信息,重新插入定义即可。这样做省
    去了再次输入额外打印语句的麻烦。

    #ifdef还可用于根据不同的C实现选择合适的代码块。

    #ifndef指令

    #ifndef指令与#ifdef指令的用法类似,也可以和#else、#endif一起使用,
    但是它们的逻辑相反。#ifndef指令判断后面的标识符是否是未定义的,常用
    于定义之前未定义的常量。如下所示:

    /* arrays.h */
    #ifndef SIZE
    #define SIZE 100
    #endif
    
    • 1
    • 2
    • 3
    • 4

    (旧的实现可能不允许使用缩进的#define)

    通常,包含多个头文件时,其中的文件可能包含了相同宏定义。#ifndef
    指令可以防止相同的宏被重复定义。在首次定义一个宏的头文件中用#ifndef
    指令激活定义,随后在其他头文件中的定义都被忽略。
    #ifndef指令还有另一种用法。假设有上面的arrays.h头文件,然后把下面
    一行代码放入一个头文件中:

    #include "arrays.h"
    
    • 1

    SIZE被定义为100。
    但是,如果把下面的代码放入该头文件:

    #define SIZE 10
    #include "arrays.h"
    
    • 1
    • 2

    SIZE则被设置为10。这里,当执行到#include "arrays.h"这行,处理
    array.h中的代码时,由于SIZE是已定义的,所以跳过了#define SIZE 100这行
    代码。鉴于此,可以利用这种方法,用一个较小的数组测试程序。测试完毕
    后,移除#define SIZE 10并重新编译。这样,就不用修改头文件数组本身
    了。

    #ifndef指令通常用于防止多次包含一个文件

    #ifndef指令通常用于防止多次包含一个文件。也就是说,应该像下面这
    样设置头文件:

    /* things.h */
    #ifndef THINGS_H_
    #define THINGS_H_
    /* 省略了头文件中的其他内容*/
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5

    假设该文件被包含了多次。当预处理器首次发现该文件被包含时,
    THINGS_H_是未定义的,所以定义了THINGS_H_,并接着处理该文件的其
    他部分。当预处理器第2次发现该文件被包含时,THINGS_H_是已定义的,
    所以预处理器跳过了该文件的其他部分。

    为何要多次包含一个文件?

    最常见的原因是,许多被包含的文件中都包含着其他文件,所以显式包含的文
    件中可能包含着已经包含的其他文件。

    这有什么问题?

    在被包含的文件中有某些项(如,一些结构类型的声明)只能
    在一个文件中出现一次。C标准头文件使用#ifndef技巧避免重复包含。

    但是,这存在一个问题:
    如何确保待测试的标识符没有在别处定义。通常使用这些方法
    解决这个问题:

    用文件名作为标识符、使用大写字母、用下划线字符代替文件名中的点字符、
    用下划线字符做前缀或后缀(可能使用两条下划线)。

    例如,查看stdio.h头文件,可以发现许多类似的代码:

    #ifndef _STDIO_H
    #define _STDIO_H
    // 省略了文件的内容
    #endif
    
    • 1
    • 2
    • 3
    • 4

    你也可以这样做。但是,由于系统标准库使用下划线作为前缀,所以在自
    己的代码中不要这样写,避免与标准头文件中的宏发生冲突。

    程序使用#ifndef避免文件被重复包含

    names.c程序

    // names.h --修订后的 names_st 头文件,避免重复包含
    #ifndef NAMES_H_
    #define NAMES_H_
    // 明示常量
    #define SLEN 32
    // 结构声明
    struct names_st
    {
    	char first[SLEN];
    	char last[SLEN];
    };
    // 类型定义
    typedef struct names_st names;
    // 函数原型
    void get_names(names *);
    void show_names(const names *);
    char * s_gets(char * st, int n);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    下面程序测试该头文件没问题,但是如果把#ifndef保护删除后,程序就无法通过编译。
    doubincl.c程序:

    // doubincl.c -- 包含头文件两次
    #include 
    #include "names.h"
    #include "names.h" // 不小心第2次包含头文件
    int main(){
    	names winner = { "Less", "Ismoor" };
    	printf("The winner is %s %s.\n", winner.first,
    	winner.last);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    #if和#elif指令

    #if指令很像C语言中的if#if后面跟整型常量表达式,如果表达式为非
    零,则表达式为真。可以在指令中使用C的关系运算符和逻辑运算符:

    #if SYS == 1
    #include "ibm.h"
    #endif
    
    • 1
    • 2
    • 3

    可以按照if else的形式使用#elif(早期的实现不支持#elif)。例如,可
    以这样写:

    #if SYS == 1
    #include "ibmpc.h"
    #elif SYS == 2
    #include "vax.h"
    #elif SYS == 3
    #include "mac.h"
    #else
    #include "general.h"
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    较新的编译器提供另一种方法测试名称是否已定义,即用

    #if defined(VAX)
    
    • 1

    代替

    #ifdef VAX
    
    • 1

    这里,defined是一个预处理运算符,如果它的参数是用#defined定义
    过,则返回1;否则返回0。
    这种新方法的优点是,它可以和#elif一起使用。

    下面用这种形式重写前面的示例:

    #if defined (IBMPC)
    #include "ibmpc.h"
    #elif defined (VAX)
    #include "vax.h"
    #elif defined (MAC)
    #include "mac.h"
    #else
    #include "general.h"
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果在VAX机上运行这几行代码,那么应该在文件前面用下面的代码定
    义VAX:

    #define VAX
    
    • 1

    条件编译还有一个用途是让程序更容易移植

    条件编译还有一个用途是让程序更容易移植。改变文件开头部分的几个
    关键的定义,即可根据不同的系统设置不同的值和包含不同的文件。

    参考

    《C Primer Plus》

  • 相关阅读:
    Vue11 计算属性
    Flir Blackfly S工业相机:颜色校正讲解及配置与代码设置方法
    HarmonyOS应用开发—资源分类与访问
    Rust常见集合
    SpringMVC-18-异常机制
    SpringCloud微服务技术栈-什么是Docker?怎么安装Docker?
    【计算机网络】什么是socket编程?以及相关接口详解
    基于STM32结合CubeMX学习Free-RT-OS的源码之深入学习软件定时器实现过程
    elasticsearch安装部署详细教程
    什么是软件?什么是瀑布模型?
  • 原文地址:https://blog.csdn.net/Shujie_L/article/details/134442276