没有全球运营商的裸机新
Bare metal without global operator new
考虑安全软件,其中通常不允许动态分配并且不允许异常。仅当 class 显式定义运算符 new
和 delete
时才允许动态分配。对其他 class 使用运算符 new
应该会导致编译失败。
在上述情况下导致编译失败的最简单方法是删除全局新运算符:
void* operator new(std::size_t) = delete;
一方面,这会对标准库造成副作用。例如,包含 <array>
通过 <stdexcept>
将包含传播到 <new_allocator>
。 <new_allocator>
使用 ::new
运算符,即使您不想使用异常和内存分配,这也会导致构建失败。根据 Scoot Meyers <array>
应该是裸机友好的。
另一方面,这会导致编译器内置运算符出错
src/main.cpp:91:31: error: deleted definition of 'void* operator new(std::size_t)'
void* operator new(std::size_t) = delete; ^
<built-in>: note: previous declaration of 'void* operator new(std::size_t)'
有什么办法可以禁止 ::new
而使用 <array>
吗?
是否有任何解决方案可以在全球范围内 完全禁止 ::new
?
无论您使用何种编程语言:
在任何健全的裸机系统上,您只需从 linker 脚本中完全删除 .heap
段。任何依赖动态分配的代码都将无法 link。而且您不必为您无论如何都不会使用的段分配 RAM。
如果您使用 GCC 和 GNU LD,那么我认为您可以将 --wrap=malloc
添加到您的链接器标志中。由于全局 ::new
在内部使用 malloc()
,因此在您的应用程序中对 malloc()
的所有调用都将替换为 __wrap_malloc()
。如果此函数未定义,则链接将失败。
另一个可能更简单的选项是将 ASSERT(DEFINED(malloc) == 0, "Dynamic allocation used!");
添加到您的链接描述文件中。这将断言 malloc()
未定义。
这些选项都不能防止您重新定义全局 ::new
以使用某种其他形式的全局分配。您可以在链接描述文件中对全局符号 ::new
执行相同的操作,但它的名称被破坏了(在此处 _Znwj
),所以这会有点奇怪...
有两点我想强调一下:
- 如果异常没有在您的代码中找到它们的方式,那么包含异常相关的 headers 及其定义不应该打扰您。
- 禁用异常后,您将无法在代码中使用它们(如果这样做,编译将失败)。但是,它不会从标准库中删除异常的使用。标准库可能包含 pre-compiled 版本的模板 类,例如
std::string
(即 std::basic_string<char, ...>
)或 std::streambuf
(即 std::basic_streambuf<char, ...>
),以及当您在代码中使用它时,编译器不会尝试实例化模板,它只是重用 pre-compiled 版本,但有例外。
为了更好地控制您的代码,我强烈建议使用 G++ 编译器的 -nostdlib
编译选项完全排除标准库。它不会阻止您使用各种模板 类,例如来自 STL 的 std::array
,它只会为您排除整个 C++ 库和运行时。
我也推荐阅读Practical Guide to Bare Metal C++。它可能会更深入地了解 C++ bare-metal 内部结构。
考虑安全软件,其中通常不允许动态分配并且不允许异常。仅当 class 显式定义运算符 new
和 delete
时才允许动态分配。对其他 class 使用运算符 new
应该会导致编译失败。
在上述情况下导致编译失败的最简单方法是删除全局新运算符:
void* operator new(std::size_t) = delete;
一方面,这会对标准库造成副作用。例如,包含 <array>
通过 <stdexcept>
将包含传播到 <new_allocator>
。 <new_allocator>
使用 ::new
运算符,即使您不想使用异常和内存分配,这也会导致构建失败。根据 Scoot Meyers <array>
应该是裸机友好的。
另一方面,这会导致编译器内置运算符出错
src/main.cpp:91:31: error: deleted definition of 'void* operator new(std::size_t)'
void* operator new(std::size_t) = delete; ^
<built-in>: note: previous declaration of 'void* operator new(std::size_t)'
有什么办法可以禁止 ::new
而使用 <array>
吗?
是否有任何解决方案可以在全球范围内 完全禁止 ::new
?
无论您使用何种编程语言:
在任何健全的裸机系统上,您只需从 linker 脚本中完全删除 .heap
段。任何依赖动态分配的代码都将无法 link。而且您不必为您无论如何都不会使用的段分配 RAM。
如果您使用 GCC 和 GNU LD,那么我认为您可以将 --wrap=malloc
添加到您的链接器标志中。由于全局 ::new
在内部使用 malloc()
,因此在您的应用程序中对 malloc()
的所有调用都将替换为 __wrap_malloc()
。如果此函数未定义,则链接将失败。
另一个可能更简单的选项是将 ASSERT(DEFINED(malloc) == 0, "Dynamic allocation used!");
添加到您的链接描述文件中。这将断言 malloc()
未定义。
这些选项都不能防止您重新定义全局 ::new
以使用某种其他形式的全局分配。您可以在链接描述文件中对全局符号 ::new
执行相同的操作,但它的名称被破坏了(在此处 _Znwj
),所以这会有点奇怪...
有两点我想强调一下:
- 如果异常没有在您的代码中找到它们的方式,那么包含异常相关的 headers 及其定义不应该打扰您。
- 禁用异常后,您将无法在代码中使用它们(如果这样做,编译将失败)。但是,它不会从标准库中删除异常的使用。标准库可能包含 pre-compiled 版本的模板 类,例如
std::string
(即std::basic_string<char, ...>
)或std::streambuf
(即std::basic_streambuf<char, ...>
),以及当您在代码中使用它时,编译器不会尝试实例化模板,它只是重用 pre-compiled 版本,但有例外。
为了更好地控制您的代码,我强烈建议使用 G++ 编译器的 -nostdlib
编译选项完全排除标准库。它不会阻止您使用各种模板 类,例如来自 STL 的 std::array
,它只会为您排除整个 C++ 库和运行时。
我也推荐阅读Practical Guide to Bare Metal C++。它可能会更深入地了解 C++ bare-metal 内部结构。