第3.12章: #ifdef / #ifndef
条件编译
预处理器可以根据某个宏是否定义,来包含或排除代码中的某些部分。 这被称为条件编译。
它被用于:
- 平台专属代码
- 编译器专属代码
- 条件包含
- 包含防卫
- 特性检测(
__has_include、__has_embed)
#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
这会导致调试构建与发布构建行为不一致。
要点
- #ifdef / #ifndef:根据宏定义包含或排除代码。
- #elif:多分支条件编译。
- 包含防卫:防止头文件被多次包含。
- 平台专属包含:只在有效的地方包含头文件。
- #undef:移除宏(例如
windows.h中的min、max)。 - __has_include:在包含之前检查头文件是否存在。
- __has_embed:检查是否支持
#embed。 - assert + NDEBUG:断言在发布构建中会消失。
- assert 中不要有副作用:否则调试与发布行为不同。
- 条件编译发生在 C++ 之前:预处理器只看词法单元。