RIOT 中 C++ 与 C 混合编程实战指南:以 riot_and_cpp 示例为例
【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT
本篇技术指南围绕 RIOT 官方语言支持示例中的 riot_and_cpp 展开,系统讲解如何在以 C 为核心的 RIOT 操作系统上编写同时包含 C 与 C++ 源文件的应用。你将掌握:通过CXXEXFLAGS为 C++ 文件注入编译选项、用FEATURES_REQUIRED开启 C++ 运行时支持、用extern "C"打通 C/C++ 符号链接(name mangling)、在嵌入式多线程环境下使用 C++ 类、函数重载、命名空间与std::vector容器等完整实战方案,并可直接在native模拟目标上编译运行验证。
示例工程概览
riot_and_cpp是 RIOT 官方提供的“C++ 与 C 混合编程”最小演示工程,位于仓库 examples/lang_support/official/riot_and_cpp/ 目录,其 README 开宗明义地指出:
This project demonstrates how user can use both C++ and C in their application with RIOT.
整个工程由 6 个文件构成,结构非常清晰:
examples/lang_support/official/riot_and_cpp/ ├── Makefile # 构建配置:APPLICATION、BOARD、RIOTBASE、FEATURES_REQUIRED、CXXEXFLAGS ├── Makefile.ci # CI 内存不足板卡清单 ├── README.md # 使用说明 ├── c_functions.c # 纯 C 函数实现(hello、day_of_week) ├── c_functions.h # C 函数声明,含 extern "C" 保护 ├── cpp_class.cpp # C++ 类实现(构造/析构、say_hello 重载、私有 greet) ├── cpp_class.hpp # C++ 类声明(public/private 成员划分) └── main.cpp # C++ 主程序:创建线程、实例化类、使用 std::vector它演示了 RIOT 应用中 C 与 C++ 共存的全部关键点,源码头注释(main.cpp)将其概括为:
- 混合编译 C 与 C++ 源文件,验证名称修饰(name mangling)处理;
- 引入命名空间,避免每次调用都写
std::前缀; - 使用 private/public 成员函数(如
cpp_obj.greet()无法从main.cpp访问); - 对
cpp_obj.say_hello(...)针对none、int、float进行函数重载; - 演示 C++ 模板容器
std::vector; - 使用迭代器访问容器元素。
Makefile 配置要点
Makefile 是本示例的核心配置,其中与 C++ 直接相关的部分如下:
APPLICATION = riot_and_cpp # If no BOARD is found in the environment, use this default: BOARD ?= native # This has to be the absolute path to the RIOT base directory: RIOTBASE ?= $(CURDIR)/../../../.. # Features required FEATURES_REQUIRED += cpp # basic C++ support FEATURES_REQUIRED += libstdcpp # libstdc++ support (for #include <cstdio>) # If you want to add some extra flags when compile c++ files, add these flags # to CXXEXFLAGS variable CXXEXFLAGS += include $(RIOTBASE)/Makefile.includeFEATURES_REQUIRED:显式声明 C++ 能力
RIOT 采用“特性(features)声明”机制。示例通过两行FEATURES_REQUIRED明确要求目标平台必须具备的能力:
cpp:基础 C++ 支持(C++ 编译器与运行时);libstdcpp:libstdc++ 支持,示例注释专门指出这是#include <cstdio>等 C++ 标准库头文件所必需的。
若目标板卡不支持这些特性,构建会在早期阶段报错,而不是等到链接时才失败,这保证了混合语言工程的可移植性。
CXXEXFLAGS:C++ 文件的专属编译选项
这是 README 中唯一单列的 Makefile 选项,原文描述为:
CXXEXFLAGS : user's extra flags used to build c++ files should be defined here (e.g -std=gnu++11).
即:用户针对 C++ 源文件的额外编译标志必须定义在CXXEXFLAGS中。例如需要指定 C++ 标准版本时,可以这样写:
CXXEXFLAGS += -std=gnu++17从源码结构看,RIOT 的构建系统在编译.cpp/.cc文件时使用独立的 C++ 编译规则,CXXEXFLAGS正是该规则下专用于 C++ 文件的用户扩展标志位——它与 C 文件的CFLAGS/EXFLAGS通道相互独立,避免 C++ 专属标志(如-std、-fno-exceptions等)污染 C 代码的编译单元。
其他通用配置
BOARD ?= native:默认使用native(宿主机模拟)目标,无需任何硬件即可编译运行;RIOTBASE ?= $(CURDIR)/../../../..:指向 RIOT 仓库根目录,此相对路径正是从examples/lang_support/official/riot_and_cpp/向上回溯 4 级得到;DEVELHELP ?= 1:启用 RIOT 的开发期安全检查代码,生产环境可注释关闭;QUIET ?= 1:默认隐藏完整编译命令,置 0 可查看编译器的具体调用行。
C 与 C++ 互操作的核心:extern "C"
C 与 C++ 混合编程最大的技术障碍是名称修饰(name mangling):C++ 编译器会对函数符号进行重载相关的名字改编,而 C 编译器不做。若 C++ 代码直接#includeC 头文件并调用其中函数,链接阶段会因符号不匹配而失败。
c_functions.h给出了标准解法——用__cplusplus宏加extern "C"包裹全部 C 函数声明:
#include <stdio.h> /* * all headers defining c functions must be marked as extern "C" when included * into c++ sources */ #ifdef __cplusplus extern "C" { #endif int hello(void); int day_of_week(int day, int month, int year); #ifdef __cplusplus } #endif要点说明:
- 该头文件同时被
c_functions.c(C 编译器)和main.cpp(C++ 编译器)包含; - 在 C++ 编译单元中,
__cplusplus宏已定义,声明被extern "C"包裹,从而禁止 C++ 编译器对这些符号做名称修饰,保证与c_functions.c中编译出的 C 符号一致; - 在纯 C 编译单元中
__cplusplus未定义,extern "C"不生效,代码等价于普通 C 头文件。
这是所有“C++ 调用 C 函数”场景的通用模式,也是本示例验证 name mangling 处理的关键。
C 侧的实现
c_functions.c提供了两个纯 C 函数:
int hello(void):打印"Hello world in a C function";int day_of_week(int day, int month, int year):以 2000-01-01(星期六,first_day = 6)为基准计算任意日期是星期几,返回 0 表示 Sunday、6 表示 Saturday,非法日期或year < 2000返回-1。
day_of_week内部使用宏IS_LEAP_YEAR(year)判断闰年,并用取模 7 的方式累计天数差,是一个不依赖任何 RIOT 模块的纯算法函数,非常适合作为 C 侧代码的代表。
C++ 侧:类、重载、命名空间与容器
类的声明与私有成员
cpp_class.hpp定义了一个带 public/private 划分的类:
#include <cstdio> class cpp_class { public: cpp_class(); // 构造函数 ~cpp_class(); // 析构函数 void say_hello(void); void say_hello(int n); // 重载:int 参数 void say_hello(float f);// 重载:float 参数 private: void greet(void); // 私有成员,外部不可访问 };对应实现 cpp_class.cpp 中:
- 构造函数调用
printf打印"Instantiating Object [constructor called]"并调用私有方法greet(); - 析构函数打印
"Destroying Object [destructor called]"与"Im shutting down!"; say_hello三个重载版本分别输出Hello!、Hello![int: %d]、Hello![float: %f]。
函数重载的实际效果
在 main.cpp 中:
cpp_class cpp_obj; printf("\n-= Test overloading functions =-\n"); cpp_obj.say_hello(); cpp_obj.say_hello(42); cpp_obj.say_hello(3.141592f);编译器根据实参类型(无参 /int/float)自动选择对应重载版本。注意3.141592f的f后缀必不可少——若写成字面量3.141592,其类型为double,而类中只重载了float版本,编译将失败。这恰好演示了 C++ 重载决议的精确匹配规则。
命名空间与 std::vector
示例展示了嵌入式环境下使用 C++ 标准库容器的可行性:
using namespace std; // 引入命名空间,之后可省略 std:: 前缀 vector<int> vInts; vInts.push_back(1); vInts.push_back(3); vInts.push_back(2); printf("The vector vInts has been filled with %" PRIuSIZE " numbers.\n", vInts.size());using namespace std;之后std::vector<int>可直接写作vector<int>。随后用迭代器遍历并打印容器内容:
for (vector<int>::iterator it = vInts.begin(); it != vInts.end(); ++it) { printf("%d ", *(it)); }这里有两个值得注意的 RIOT 相关细节:
printf的%格式串使用了 RIOT 提供的 PRI 宏(PRIkernel_pid、PRIuSIZE),用于保证打印kernel_pid_t、size_t类型时在不同架构(如 native 的 64 位与 32 位 MCU)上格式正确;- 本工程能使用
std::vector,正是因为 Makefile 中声明了FEATURES_REQUIRED += libstdcpp,使 C++ 标准模板库可用。
在 C++ 程序中创建 RIOT 线程
混合编程不仅要打通语言边界,还要让 C++ 代码与 RIOT 内核 API 协作。main.cpp 展示了标准的线程创建流程:
char threadA_stack [THREAD_STACKSIZE_MAIN]; void *threadA_func(void *arg); thread_create(threadA_stack, sizeof(threadA_stack), 0, THREAD_CREATE_WOUT_YIELD, threadA_func, NULL, "thread A");- 栈空间使用 RIOT 提供的
THREAD_STACKSIZE_MAIN宏(thread.h 中定义的标准线程栈大小),由用户态静态数组提供; THREAD_CREATE_WOUT_YIELD标志表示创建后不立即让出 CPU;- 线程函数
threadA_func以 C 函数形式实现,内部调用 C 侧函数hello()与day_of_week(...); - 主线程通过
thread_getpid()打印自己的kernel_pid。
运行时两个线程并行执行:线程 A 演示“C++ 代码调用 C 函数”,主线程演示“C++ 类与 STL 容器”,完整覆盖了单进程中 C/C++/RIOT 三者的协作场景。
编译、运行与验证
在示例目录下执行(需要本机已安装 RIOT 工具链,native目标依赖宿主机 gcc/g++ 与pkg-config等):
cd examples/lang_support/official/riot_and_cpp make ./bin/native/riot_and_cpp.elf预期输出包含以下关键片段:
************ RIOT and C++ demo program *********** ******** Hello, you're in thread #1 ******** We'll test C++ class and methods here! -= Test overloading functions =- Instantiating Object [constructor called] Im starting! Hello! Hello![int: 42] Hello![float: 3.141593] ... ******** Hello, you're in thread #2 ******** We'll test some C functions here! -= hello function =- Hello world in a C function -= day_of_week function =- day 13, month 6, year 2014 is Friday2014-06-13确实是星期五,可据此验证day_of_week的 C 算法正确性。程序结束后(native下按Ctrl-C)可观察到析构函数输出Destroying Object [destructor called]。
换用真实板卡编译时,例如:
make BOARD=nrf52840dkRIOT 会先检查该板卡的 features 是否满足cpp与libstdcpp要求。对于 Flash/RAM 较小的板卡,请参考 Makefile.ci 中列出的BOARD_INSUFFICIENT_MEMORY清单(如nucleo-l011k4、samd10-xmini、stm32f0discovery等),这些板卡因内存不足以运行本示例而不参与 CI 构建。
总结与扩展
通过riot_and_cpp这一官方示例可以得出 RIOT 中 C/C++ 混合编程的四条核心经验:
- 特性声明先行:在 Makefile 中用
FEATURES_REQUIRED += cpp libstdcpp显式声明对 C++ 工具链与标准库的依赖; - C 头文件一律包
extern "C":凡会被 C++ 源文件包含的 C 头文件,都用#ifdef __cplusplus extern "C" { ... }保护,消除名称修饰问题; - C++ 专属标志走
CXXEXFLAGS:如-std=gnu++11等只影响.cpp编译单元的标志,统一放入该变量; - C++ 与 RIOT 内核 API 可以无缝协作:
thread_create、printf的 PRI 宏等 RIOT 设施均可直接在 C++ 代码中使用,无需额外适配层。
该工程位于 RIOT 仓库 examples/lang_support/official/riot_and_cpp/ 目录,同目录下还有rust-hello-world、rust-async、rust-gcoap等其它语言支持示例,可对照学习 RIOT 对多语言应用开发的整体支持策略。
【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考