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 知识体系图

下面这张图帮你理清这两个宏在项目中的定位和作用:

__cplusplus 与 __STDC_VERSION__ 知识体系 __cplusplus __STDC_VERSION__ 检测是否为C++环境 获取C++标准版本号 检测C语言标准版本 C89不定义,C99起定义 应用场景:混合编译 / 版本兼容 / 跨平台

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" 没加对地方。


公众号:蓝海资料掘金营,微信 deep3321