使用 C++ 开发用户应用程序有很多优势,因此该语言在包括基于 MCU 的系统中变得越来越流行也就不足为奇了。“mbed”项目完全专注于这种语言。许多 RTOS 提供 C++ 兼容层,但与“大”系统(具有 MMU)相比,大多数 RTOS 都有一些限制。在本文中,我们将研究 C++ 的内部结构并找出造成这些限制的原因。
MCU 上的 C++ 有两个主要限制:重新启动应用程序和标准 C++ 库的多线程功能。
本文中的大多数示例都将在Embox RTOS上进行考虑。该 RTOS 允许在 MCU 上运行像OpenCV这样复杂的 C++ 项目。该项目需要标准 C++ 库中的线程支持。此外,与 MCU 上的其他 RTOS 不同,Embox 允许重新启动 C++ 应用程序。我们将使用带有外部 SDRAM 的 STM32F769i 板来演示 OpenCV,因为该框架需要数百 KB 的 RAM。但是,几千字节的 RAM 足以运行简单的 C++ 应用程序。
基本语法
C++ 语言的语法由编译器实现。这些功能包含在名为“libsupc++”的语言支持库中。在应用程序运行时必须处理一些部分。例如,需要处理全局构造函数和析构函数。
全局构造函数和析构函数
让我们看看任何 C++ 应用程序如何与全局构造函数和析构函数一起工作。所有全局 C++ 对象都是在程序调用 main() 之前创建的。为此有一些特殊的部分:“.init_array”、“.init”、“.preinit_array”、“.ctors”。这些是指向函数的指针数组,必须从头到尾遍历,调用数组的相应元素。
Embox 中调用全局对象构造函数的代码如下:
void cxx_invoke_constructors(void) {
extern const char _ctors_start, _ctors_end;
typedef void (*ctor_func_t)(void);
ctor_func_t *func = (ctor_func_t *) &_ctors_start;
...
for (; func != (ctor_func_t *) &_ctors_end; func++) {
(*func)();
}
}
让我们看看 C++ 应用程序的终止是如何工作的,即调用全局对象的析构函数。有两种方法。
首先,编译器中最常用的是使用来自 C++ 应用程序二进制接口 (ABI) 的 __cxa_atexit()。这是 POSIX atexit() 的类似物。也就是说,您可以注册将在程序终止期间调用的特殊处理程序。当在应用程序开始时调用全局构造函数时,如上所述,还有编译器生成的代码,用于使用 __cxa_atexit() 注册析构函数处理程序。
第二种方法是将指向析构函数的指针存储在特殊部分“.fini_array”和“.fini”中。如果指定了“-fno-use-cxa-atexit”标志,GCC 编译器将使用这种方式。在这种情况下,在应用程序终止期间,必须以相反的顺序(从高地址到低地址)调用析构函数。这种方法不太常见,但在微控制器中很有用。因为,在这种情况下,可以找出编译时需要多少个处理程序。
Embox 中调用全局对象析构函数的代码如下:
int __cxa_atexit(void (*f)(void *), void *objptr, void *dso) {
如果(atexit_func_count >= TABLE_SIZE){
printf("__cxa_atexit: 静态销毁表溢出。\n");
返回-1;
}
atexit_funcs[