【问题标题】:C++ data containers for embedded systems用于嵌入式系统的 C++ 数据容器
【发布时间】:2018-04-06 16:09:40
【问题描述】:

我使用嵌入式系统。通常这意味着我有一个小型微控制器,具有 64 - 512 KB 的 RAM 和 128 - 1024 KB 的闪存,如 STM32。我更喜欢使用 C++ 来编写这样的系统。但是我还没有找到可以接受的方法来处理常见的数据结构,例如堆栈、队列、映射等。

当然,STL 拥有所有这些和许多其他方便的东西,但是大多数 STL 容器需要支持异常和动态内存分配,这在嵌入式编程中通常是不受欢迎的。

我知道我们可以通过使用自定义分配器来避免这个问题,我们可以从静态对象池或类似的东西中分配内存。但是,我看到的主要问题是,当分配的空间不足以将新元素插入容器时,我们无法可靠地处理这种情况。我遇到的 STL 和其他类似 stl 的库只提出了两种选择:

  • 断言。这意味着当分配的空间不足时系统会失败。
  • 回调。好一点,但对我来说仍然不方便。

    q.push(newElem); /* fails or just calls predefined callback
                      * when not enough space in queue.
                      */
    

也许我错了。但在我看来,最好的方法是返回状态以通知调用者容器中的新元素没有足够的内存。我想自己决定如何处理该错误。例如,我想删除新元素,向调试日志发送消息并恢复正常的程序流程。从我的角度来看,它看起来更可靠。

换句话说,我想要这样的东西:

queue<uint32_t, 128> q;
// some code ... 
queue::status sts = q.push(newElem);
if (sts != queue::OK)
    LOG("Not enough space in queue\r\n");

// continue normal program execution ...

有人建议如何处理那件事?

【问题讨论】:

  • 听起来你对数据容器没有问题。相反,在我看来,您在决定错误报告策略时遇到了问题。
  • 异常是报告任意嵌套调用失败的唯一明智的方法,所以如果你不能使用异常,你几乎必须确保失败永远不会发生。只要付出足够的努力和提前计划,这是可行的。在每个可能失败的操作之前预先分配足够的内存。使用像 boost 中的侵入式链表。对于向量和双端队列,请在添加内容之前保留。到处使用移动(而不是复制)。
  • @FrançoisAndrieux,我认为在很多情况下,只需删除新数据(我的意思是当容器已满时不要将其推入容器中)就足够了,并且不需要抛出异常或在调用堆栈的上方返回错误状态。无论如何,感谢您参考提升侵入性列表。我会尝试在那里挖掘。
  • @Lundin,我们可以在 C 上做的所有事情,我们都可以在 C++ 上做同样的事情。此外,C++ 还为我们提供了模板、函数重载、命名空间、OOP 等好东西。今天,我们可以在没有任何显着开销的情况下获得所有这些。而且我看不出有任何理由使用 C 而不是 C++。
  • 没有。 C++ 在标准库中没有没有动态内存和异常的数据容器,C 在标准库中根本没有数据容器。所以,从这个角度来看,他们是平等的。无论如何,我们需要选择第三方库或自己编写。所以我选择的 c++ 不会影响这个问题。

标签: c++ data-structures stl embedded cortex-m


【解决方案1】:

您可以使用自定义分配器,在空间不足时抛出 std::bad_alloc,然后在您的代码中将依赖于插入成功的部分包装在异常处理块中,并在 catch 子句中处理分配失败.

std::queue <message> Queue;

try
{
  Queue.insert (message {"hello world"});
}
catch (std::bad_alloc const & Error)
{
  LOG (Error.what ());
}

对于简单的场景,这有点冗长,小心(使用 RAII)它可以成为一个强大的工具。此外,如果您分配了这些简单的场景,您可以在模板中隐藏此模式。

template <typename fn>
void log_failure_and_contiue (fn Fn)
{
    try
    {
      Fn ();
    }
    catch (std::bad_alloc const & Error)
    {
      LOG (Error.what ());
    }
}

std::queue <message> Queue;

log_failure_and_continue ([&] {
  Queue.insert ({ "Hello World!" });
});

从性能的角度来看,大多数编译器都实现了零成本的异常处理,因此只有在抛出异常时才会受到影响,我认为在这种情况下应该很少见。

【讨论】:

  • 异常在可执行二进制文件中的开销很大。数百KB。因此,在闪存大小可能为 128 KB 或更少的嵌入式系统中,这通常是不可接受的。
  • 是的,我同意,尽管有一些方法可以缓解这种情况。虽然这可能很乏味,但可以将大部分方法标记为noexcept,这将允许编译器修剪大部分异常处理表,并且在可能内联函数中抛出和捕获的情况下,它可以优化实际的例外。我不知道编译器现在对这类事情的优化程度如何,但值得一试。
  • 嵌入式系统异常的另一个问题是中断。
  • 自定义分配器可以调用错误处理程序,该处理程序会记录(类似于上面)然后重置或只是在某些错误状态下停止系统。
猜你喜欢
  • 2010-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-12
  • 1970-01-01
  • 2012-07-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多