嵌入式系统的C++数据容器

7
我从事嵌入式系统工作。一般情况下,我使用64-512 KB RAM和128-1024 KB闪存的小微控制器,如STM32。我喜欢使用C++来编程这些系统。但是我还没有找到一种可接受的方法来处理常见的数据结构,如栈、队列、映射等。
当然,STL包含了所有这些数据结构以及许多其他方便的功能,但大多数STL容器需要支持异常和动态内存分配,这在嵌入式编程中通常是不可取的。
我知道我们可以通过使用自定义分配器来避免这个问题,我们可以从静态对象池或类似的地方分配内存。然而,我认为主要的问题是我们无法可靠地处理这样一种情况:在向容器插入新元素时没有足够的分配空间。STL和其他类似STL的库只提供了两个选项:
  • asserts. Which means system fails when it's not enough allocated space.
  • callbacks. A bit better but isn't still convenient for me.

    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 ...

有人有如何处理这件事情的建议吗?

2
你似乎并不是在处理数据容器方面遇到了问题,而是在决定错误报告策略方面遇到了问题。 - François Andrieux
2
异常实际上是从任意嵌套调用中报告失败的唯一明智方式,所以如果你不能使用异常,基本上就必须确保不会发生失败。这是可行的,只要付出足够的努力并提前进行规划。在每个可能失败的操作之前预分配足够的内存。使用类似boost中的侵入式链表。对于向量和双端队列,在添加内容之前进行预留。在任何地方都使用移动(而不是复制)。 - n. m.
@n.m. 对不起,我之前的评论是针对你的。 - Raman Sakovich
1
@ Lundin,所有我们可以在C语言上做的事情都同样适用于C ++。此外,C++还为我们提供了模板、函数重载、命名空间、面向对象编程等许多好东西。现在所有这些功能都可以在没有任何重大开销的情况下实现。我认为没有使用C而不是C ++的理由。 - Raman Sakovich
1
不,C++标准库中没有不使用动态内存和异常的数据容器,而C根本没有标准库中的数据容器。因此,从这个角度来看,它们是相等的。无论如何,我们需要选择第三方库或自己编写。因此,我的选择C++并不影响这个问题。 - Raman Sakovich
显示剩余7条评论
1个回答

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!" });
});

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


4
异常处理在可执行二进制文件中占用了很大的空间,数百KB大小。所以,在嵌入式系统中,往往无法接受,因为闪存大小可能只有128 KB甚至更小。 - Raman Sakovich
是的,我同意,虽然有办法可以减轻这个问题。虽然可能会很繁琐,但可以将大部分方法标记为noexcept,这样编译器就可以剪枝掉大部分异常处理表,在可能内联的函数中抛出和捕获异常时,它还可以优化掉实际的异常处理。我不知道编译器现在对这种情况的优化效果如何,但值得尝试一下。 - nate
嵌入式系统中异常处理的另一个问题是中断。 - Alex D
自定义分配器可以调用错误处理程序,该程序将记录日志(类似于上面的内容),然后在某些错误状态下重置或仅停止系统。 - Macke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接