![C语言最佳实践](https://wfqqreader-1252317822.image.myqcloud.com/cover/885/53286885/b_53286885.jpg)
1.4.7 优雅编写条件编译代码
条件编译在C代码中十分常见。条件编译的常见用途有如下3种。
(1)当我们要将自己开发的软件运行在不同的操作系统中时,由于底层标准库在不同操作系统中的实现存在差异,我们需要使用条件编译来处理这种差异。
(2)类似地,当我们要针对不同的处理器或处理器架构编写特定的代码实现时,也需要使用条件编译。
(3)当我们要通过编译时配置选项来控制软件的功能(比如通过配置选项控制包含哪些功能模块)时,会经常使用条件编译。
如下代码来自MiniGUI的头文件,用于判断如何确定64位的整数数据类型,其中给出了条件编译的常见用法:
/* Figure out how to support 64-bit datatypes */ #if !defined(__STRICT_ANSI__) # if defined(__GNUC__) # define MGUI_HAS_64BIT_TYPE long long # endif # if defined(__CC_ARM) # define MGUI_HAS_64BIT_TYPE long long # endif # if defined(_MSC_VER) # define MGUI_HAS_64BIT_TYPE __int64 # endif #endif /* !__STRICT_ANSI__ */ /* The 64-bit datatype isn't supported on all platforms */ #ifdef MGUI_HAS_64BIT_TYPE typedef unsigned MGUI_HAS_64BIT_TYPE Uint64; typedef signed MGUI_HAS_64BIT_TYPE Sint64; #else /* This is really just a hack to prevent the compiler from complaining */ typedef struct { Uint32 hi; Uint32 lo; } Uint64, Sint64; #endif
如上述代码所示,条件编译会严重割裂代码的连续性,从而极大破坏代码的可读性。因此,我们应该尽量避免使用条件编译。当然,由于C语言主要用来开发底层基础软件,因此C程序员难免会因为上面所说的3种用途而使用条件编译。为此,下面是4条可供C程序员参考的处理原则。
第一,使用恰当的注释说明条件编译代码块的作用,如下所示:
#ifdef foo ... #else /* foo */ ... #endif /* not foo */ #ifdef foo ... #endif /* foo */ #ifndef bar ... #else /* not bar */ ... #endif /* bar */ #ifndef bar ... #endif /* not bar */
上述代码在#else
和#endif
代码行的末尾,使用了恰当的注释来说明条件编译代码块所对应的条件。
第二,在嵌套条件编译时,恰当地使用缩进来表示嵌套关系,如下所示:
#ifndef NULL # ifdef __cplusplus # define NULL (0) # else # define NULL ((void *)0) # endif #endif /* not defined NULL */
第三,避免使用过长的条件编译代码块,确保一个条件编译代码块不超过常见编辑器的最大行数(25~50行)。
第四,使用构建系统生成器提供的方法实现对软件功能的控制,避免使用条件编译。比如,在实现一个跨平台的文件系统操作接口时,需要考虑到不同操作系统之间(尤其是Windows操作系统和类UNIX操作系统之间)的巨大差异。这时,我们可以借助构建系统生成器,根据当前所要构建的目标系统,生成对应的构建文件,从而针对不同的目标平台编译不同的源文件。为此,我们可以将针对类UNIX操作系统(如Linux和macOS)的代码组织到一个源文件中,如filesystem-unix-like.c
;而将针对Windows操作系统的代码组织到另一个源文件中,如filesystem-windows.c
。这样就可以避免在源文件中使用大段的条件编译。
知识点:构建系统生成器
构建系统(build system)生成器(generator)是用于生成构建C、C++项目等所使用的构建系统的工具。这里的构建系统通常由一组Makefile组成。因此,我们可以将构建系统生成器理解成Makefile生成器。常见的构建系统生成器有GNU Autotools、CMake、Meson等。成熟的构建系统生成器通常具有跨平台的特征,可以帮助我们针对不同的平台组织我们的源文件,并按照目标构建系统的特性自动生成一些宏,从而提升代码的可移植性。
有关构建系统生成器的内容,我们将在第5章做详细阐述。