20、__cplusplus 与 __STDC_VERSION__:C/C++ 混合编译、标准版本检测
说实话,做嵌入式开发这么多年,我遇到过最头疼的问题之一,就是C和C++代码混在一起编译。你想想看,一个项目里既有老工程师留下的C驱动库,又有新同事用C++写的上层逻辑,编译器到底该按哪个标准来?
这时候,__cplusplus 和 __STDC_VERSION__ 这两个预定义宏就派上大用场了。它们就像编译器自带的身份证,能告诉你当前用的是C还是C++,以及用的是哪个版本的标准。
20.1 这两个宏到底是什么?
先看个简单的例子。你在代码里写个条件编译:
#ifdef __cplusplus
printf("这是C++代码\n");
#else
printf("这是C代码\n");
#endif
编译器看到这段代码,会自动判断。如果是C++编译器,__cplusplus 就被定义了;如果是C编译器,它就不存在。就这么简单。
那 __STDC_VERSION__ 呢?它是C语言专用的版本标识宏。我记得在C89标准里,这个宏还没出现。到了C99,它被定义为 199901L。C11是 201112L,C17是 201710L。
我个人的习惯是,在写跨平台库时,一定会用这两个宏做一层保护。不然哪天换个编译器,代码就炸了。
核心区别:
__cplusplus—— 检测是否为C++环境,以及C++标准版本__STDC_VERSION__—— 检测C语言标准版本(仅C编译器定义)
20.2 标准版本对应的宏值
我整理了一张表,方便你对照。这些值在不同编译器上基本一致,但个别嵌入式编译器可能会有差异。
| 语言标准 | 宏名称 | 宏值 |
|---|---|---|
| C89/ANSI C | __STDC_VERSION__ | 未定义 |
| C99 | __STDC_VERSION__ | 199901L |
| C11 | __STDC_VERSION__ | 201112L |
| C17 | __STDC_VERSION__ | 201710L |
| C++98 | __cplusplus | 199711L |
| C++11 | __cplusplus | 201103L |
| C++14 | __cplusplus | 201402L |
| C++17 | __cplusplus | 201703L |
| C++20 | __cplusplus | 202002L |
小提示: 有些老旧的嵌入式编译器(比如某些ARMCC版本)可能不定义 __STDC_VERSION__,即使它支持C99。这时候你得额外加个编译器检测宏做补充。
20.3 混合编译中的经典用法
在C/C++混合项目中,最常用的就是 extern "C" 配合 __cplusplus。说白了,就是告诉C++编译器:这段代码是C写的,别用C++的名字修饰规则。
看一个典型的头文件写法:
#ifndef __MY_LIB_H__
#define __MY_LIB_H__
#ifdef __cplusplus
extern "C" {
#endif
void my_c_function(int x);
int my_c_calculate(float a, float b);
#ifdef __cplusplus
}
#endif
#endif /* __MY_LIB_H__ */
这个模式我用了十几年。你想想看,如果没有这层保护,C++编译器会把函数名修饰成 _Z13my_c_functioni 这种样子,而C编译器只生成 my_c_function。链接时肯定找不到符号,报一堆未定义错误。
注意: extern "C" 是C++的语法,C编译器不认识。所以必须用 #ifdef __cplusplus 包起来,否则C编译器编译时会报语法错误。
20.4 版本检测的实际应用
除了混合编译,这两个宏还能帮你做版本兼容。比如你想用C11的 _Generic 泛型表达式,但又怕编译器太老不支持:
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
/* 支持C11,可以用 _Generic */
#define TYPE_SWITCH(x) _Generic((x), \
int: "int", \
float: "float", \
double: "double", \
default: "unknown" \
)
#else
/* 老版本C,只能用老办法 */
#define TYPE_SWITCH(x) "unknown"
#endif
我曾经在一个项目里吃过亏。当时用了C99的指定初始化器,结果客户那边用的编译器只支持C89。代码一编译,满屏的warning和error。从那以后,我写库代码都会加上版本检测。
C++那边也一样。比如你想用C++17的 std::optional:
#if __cplusplus >= 201703L
#include <optional>
using my_optional = std::optional<int>;
#else
/* 没有optional,自己实现一个简陋版 */
struct my_optional {
int value;
int has_value;
};
#endif
20.5 知识体系图
下面这张图帮你理清这两个宏在项目中的定位和作用:
20.6 避坑指南
最后说几个我踩过的坑,你遇到了能少走弯路。
坑1: 有些编译器在纯C模式下也会定义 __cplusplus?不会。但有些编译器(比如某些版本的IAR)在C++模式下不定义 __cplusplus,而是定义自己的宏。这时候你得查编译器手册,别死磕这两个宏。
坑2: __STDC_VERSION__ 在C++编译器里通常不定义。所以如果你写了一个 .cpp 文件,想检测C标准版本,直接用 __STDC_VERSION__ 是拿不到的。得先判断是不是C环境。
我的建议: 写头文件时,养成好习惯。所有对外接口都加上 extern "C" 保护。不管项目现在是不是纯C,保不齐哪天就要跟C++代码对接。提前做好,省得后面改得头疼。
嗯,这两个宏虽然简单,但用好了能解决大问题。下次你遇到C/C++混合编译报错,先看看是不是 extern "C" 没加对地方。