【问题标题】:Can a graceless exit corrupt the C++ memory allocator?优雅的退出会破坏 C++ 内存分配器吗?
【发布时间】:2015-06-22 01:24:28
【问题描述】:

众所周知,抛出std::bad_malloc 的常见原因是内存耗尽。

我正在执行一个嵌入式裸机(无操作系统)应用程序。初始分配有时会成功,有时会失败。由于没有其他代码在运行(没有操作系统,没有其他进程),我有充分的理由相信这个std::bad_alloc 比分配比系统可用的更多内存更复杂。当它工作时,分配了大约 500kB 的内存。硬件已分配 16MB。

相反,系统似乎对分配了多少内存有错误的记录,特别是当分配器开始时,它认为已经分配了一些非零的内存量。

当裸机应用程序启动时,它应该分配相同的零内存。这里好像不是这样的。

内存分配器中的某些状态是否可能在软重置之间保留?我怎样才能知道分配是如何完成的?

这是在带有 gcc (Sourcery CodeBench Lite 2013.05-40) 4.7.3 的 ARMv7 上运行的,并链接到 libstdc++6.0。我们正在使用-O0 -g 进行编译。

我们使用 new 关键字和标准分配器分配 std:: 对象和用户定义的对象。

【问题讨论】:

  • 它是特定于实现的。因此,编辑您的问题以通过提供更多信息来改进它(哪个编译器和版本,哪个libstdc++ 和版本,::operator new 是如何实现的,您如何 - 使用哪些优化 - 您正在编译等等......)。跨度>
  • 即使是编辑也不够说明问题。内存分配器实际上是如何完成的?
  • 这完全取决于您(和您的库)是初始化变量,还是依靠上电复位来为它们提供预期值。软重置不会将所有内容恢复为开机默认设置。
  • 对,那我要如何找出这些东西呢?我将编辑问题...
  • libstdc++ 使用标准 C 库 (malloc/free) 的内存分配服务;所以我们需要知道使用的是什么 C 库——默认的 glibc 需要 POSIX 环境,所以在裸机系统上你很可能使用替代方案。

标签: c++ memory memory-management embedded dynamic-memory-allocation


【解决方案1】:

如果在重新启动之间保持状态,那么这很可能是由于启动时的初始化不正确。

初始化标准库是 C++ 运行时启动的责任。在裸机系统中,这包括初始化堆和内存分配器。这是如何完成的,以及您是否必须执行任何特殊操作将取决于您的特定工具链和库。

例如,在 ARM RealView(也被 Keil MDK-ARM 使用)中,您通常会在启动代码配置中明确指定堆大小。在其他情况下,链接描述文件通常会自动分配所有未静态分配或分配给堆栈的可用内存以自动分配给堆。当然,链接描述文件仍然需要知道可用 RAM 的位置和大小。检查链接器的映射文件输出以验证堆的大小和位置。

大多数嵌入式系统库都包含必须由用户实现以将库与目标匹配的存根——这主要与 I/O 相关,但在 Newlib 库中(通常与 GCC 裸机一起使用-metal 工具链),例如,sbrk(或sbrk_r)存根必须重新实现才能正确进行堆操作。 sbrk 的实现对于堆的正确操作至关重要。


已添加

Sourcery CodeBench Lite 似乎使用了 Newlib。我已经看到如下开始的实现:

caddr_t _sbrk(int incr) 
{

    extern char _ebss; // Defined by the linker
    static char *heap_end;
    char *prev_heap_end;

    if (heap_end == 0) { ...

如果运行时初始化没有正确地将静态数据初始化为零,这可能是不安全的(一些嵌入式系统这样做是为了更快地启动;尽管很少值得潜在的错误)。验证您的启动是否正确执行零初始化,并且在任何情况下都将 heap_end 显式初始化为零:

static char *heap_end = 0 ;

为了保证无论启动是否严格正确,它都能正常工作。

【讨论】:

  • 信息量很大。是的,我的_sbrk() 看起来非常相似。 heap_end 初始化为零。我在哪里可以找到零初始化?但是,即使堆中有垃圾,它会导致bad_alloc吗?
  • @Cuadue :我假设“有垃圾留在里面”,这只是没有正确初始化的问题,这会产生不确定的结果。您也许应该更清楚“软重置”的含义 - C 运行时初始化肯定在这样的重置上执行吗?
  • 我们戳 CPU 寄存器以停止时钟,发出复位,然后重新启动时钟。附加的 JTAG 调试器指示程序从引导向量(地址 0)开始执行。
  • @Cuadue :也许与您的问题无关,但我想知道为什么您明确停止时钟,因为无论如何所有外围寄存器都会在真正的复位时恢复到其复位状态? “ARMv7”是指多个内核,例如 Cortex-M (ARMv7-M/v7E-M),通过 NVIC 发出复位,但也可以简单地停止复位看门狗定时器。两者都与简单地跳转到重置向量不同,例如,这不会将堆栈指针重置为其重置状态。
  • 说实话,我不知道为什么我们必须停止时钟,但这是供应商建议的。关于SP和LR的好点,我去看看。
【解决方案2】:

内存分配的良好实现不应因分配所有可用内存(或尝试分配超过所有内存)而损坏,例如调用std::bad_alloc。当然,您正在使用的操作系统中的内存分配器中可能存在错误 - 没有人知道。但是一个“好的”分配器不应该仅仅因为你的应用程序分配到它得到std::bad_alloc而被破坏或停止工作。

但是,此时使用exit(1) 退出应用程序是否会释放应用程序代码分配的内存取决于您的操作系统。如果它处理现有应用程序代码的清理,那么你是安全的。如果操作系统没有,那么您需要捕获异常并自己清理所有内容。由于实际上存在数百个操作系统,因此不可能以一般方式回答这个问题。

这应该很容易测试:只需编写一个在循环中分配大量内存的应用程序,运行几次并确定循环中的迭代次数是否大致相同,或者,例如, 为应用程序的每次运行减少 - 在后一种情况下,您会遇到某种问题。

【讨论】:

  • 最初的问题明确指出这是裸机(无操作系统)嵌入式代码。在这方面讨论操作系统的职责可能毫无意义。
猜你喜欢
  • 1970-01-01
  • 2021-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多