第3.12章: #ifdef / #ifndef

English

条件编译

预处理器可以根据某个宏是否定义,来包含或排除代码中的某些部分。 这被称为条件编译。

它被用于:

#ifdef 与 #ifndef

#ifdef 检查某个宏是否已定义。 #ifndef 检查某个宏是否未定义。


#ifdef DEBUG
print("debug mode\n");
#endif

#ifndef RELEASE
print("not release mode\n");
#endif

多分支条件编译

你可以用 #elif 串联多个条件。


#if defined(FAST_IO)
print("fast_io enabled\n");
#elif defined(USE_SIMD)
print("SIMD enabled\n");
#else
print("default configuration\n");
#endif

只有第一个匹配的分支会被包含。

包含防卫

包含防卫(include guard)防止一个头文件被包含多次。 它们是 #pragma once 的传统替代方案。


// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

void f();

#endif

当头文件被再次包含时,MYHEADER_H 已经被 定义,因此其中的内容会被跳过。

平台专属包含

你可以根据平台有条件地包含头文件。


#if defined(_WIN32)
#include <windows.h>
#undef min
#undef max
#endif

windows.h 定义了宏 min 与 max, 它们会与同名的 C++ 函数冲突。 #undef 移除这些宏,这样你就能使用真正的函数。

#undef

#undef 指令移除一个宏定义。


#define PI 3.14
#undef PI   // PI no longer defined

这常用于撤销系统头文件带来的宏。

条件包含

部分编译器支持 __has_include,用于在包含之前检查头文件 是否存在。


#if __has_include(<optional>)
#include <optional>
#endif

这让代码可以可移植地适配可用的头文件。

特性检测:__has_embed

__has_embed 检查编译器是否支持 #embed 指令。


#if __has_embed("resource.bin")
static constexpr auto data =
#embed "resource.bin";
#else
print("embed not supported\n");
#endif

这让 #embed 在可用时能被可移植地使用。

断言与 -DNDEBUG

头文件 <cassert> 提供了 assert 宏。它在运行时检查一个条件,如果条件为假就 终止程序。


#include <cassert>

void f(int x) {
    assert(x != 0);
    println(100 / x);
}

在调试构建中,如果 x == 0,这会打印一条错误信息并 终止程序。

用 -DNDEBUG 禁用断言

如果宏 NDEBUG 已定义,所有 assert 调用 都会被预处理器移除。


// compile with:
// g++ -DNDEBUG main.cpp

assert(x != 0);   // expands to nothing

这就是发布构建通常会定义 NDEBUG 的原因。

警告:不要在 assert 内产生副作用

因为当定义了 NDEBUG 时 assert 会消失, 所以它内部的表达式不能有副作用。


assert(++i != 0);   // BAD: increments only in debug builds

这会导致调试构建与发布构建行为不一致。

要点