【问题标题】:Should std::vectors be used extensively for embedded systems?std::vectors 是否应该广泛用于嵌入式系统?
【发布时间】:2014-01-17 10:46:59
【问题描述】:

在为 CPU 和内存资源有限的嵌入式系统编写 C++ 代码时,常见的经验法则是在堆栈中实例化对象,除非确实需要,否则避免使用堆。这样做当然有很多已知的好处,但是随着 STL 的出现和人们推荐 std::vectors 作为一种有效的数据结构,它是否违反了我提到的经验法则,因为向量将使用堆?

示例:在过去,人们会声明具有已知大小的静态数组以满足使用需求。如今,人们只会使用向量。

我对这种转换不太满意,因为向量总是有可能无法分配所需的内存(提醒:这是针对内存有限的嵌入式系统)。在堆栈中使用已知大小的数组可以保证在编译时有空间分配。

调用 reserve() 有点帮助,但这是在运行时完成的。

那么,这是引起关注的原因,还是我只是偏执?使用这些向量肯定容易得多,但对于嵌入式环境,这可能不是一个好主意?

注意:这不是关于动态数组还是固定数组,而是更多关于如何在内存中分配数据,这对我的环境来说很重要。例如,有些人会这样做:假设数组可以在 1 到 10 个元素之间增长或缩小。有些人会在堆栈中创建一个覆盖此大小的数组,并且 NULL 根据当前大小终止。这样,可以避免碎片,并保证在编译期间进行分配。然而,切换到向量使它更干净,但代价是使用堆,并且如果分配失败,可能不得不处理异常。这就是我所关心的。

【问题讨论】:

  • 如果您知道向量中有多少项目(或至少是粗略估计),那么您可以reserve 为数据留出空间。
  • 如果在编译时知道大小并且确保堆栈足够大,那么静态或自动数组没有任何问题。当您特别需要动态数组时,请使用 vector。 (现在,如果您愿意,可以使用std::array 使固定大小的数组看起来更像 STL。)
  • 堆可以炸,就像堆可以满。
  • “有限的 CPU 和内存资源”在今天的含义与创建类似的经验法则时有所不同。这取决于您的硬件到底有多有限。如果您的内存以数十或数百字节为单位,CPU 速度以 kHz 为单位,那么这些规则仍然有效,但如果您有兆字节的 RAM 和千兆赫的周期,那么您通常可以避免担心优化。
  • 克里斯托弗·约翰逊,我不敢苟同。我正在开发的硬件有 32MB 内存,并且遵循经验法则使我们能够维护这个规范,而无需创建新硬件。随着新程序员的加入,每个功能的内存使用量突然激增。

标签: c++ c++11 vector stl embedded


【解决方案1】:

我相信您已经忘记了 STL 容器的一个非常重要的属性:allocators

一个 STL 容器(无论是 vector 还是其他)从其分配器中获取其所有内存(除了您可以使用 sizeof 检查的非常基本的堆栈占用空间)。因此,在嵌入式开发中提供一个专用的分配器是非常合适的:

  • 将从预先保留的内存区域分配
  • 将限制最大资源消耗以防止OOM
  • ...

随着 C++11 的出现,您甚至可以使用有状态分配器,以便单个分配器类型可以指向不同的内存池。

因此,使用std::vector,甚至std::setstd::map,与预分配策略并不矛盾;请记住,除了std::vector 之外,其他 STL 容器通常有一些每个项目的开销,在调整它们应该利用的内存区域时必须考虑这些开销。

【讨论】:

  • 谢谢,马特。我会研究分配器。
  • 看起来就像您使用的任何动态分配方法,无论是堆、“分配器”还是堆栈,如果超出编译时定义的内存,您将需要管理内存不足的情况。捕捉异常并不难。困难的是设计对内存不足错误的故障安全或稳健响应。对于具有相关风险/危害的嵌入式系统,我建议坚持使用固定大小的数组。
  • @Dana:这里固定大小的数组和向量没有区别;无论哪种情况,您都需要计算(并验证)所需的最大内存量,并确保它存在。
【解决方案2】:

“取决于”在这里可能是轻描淡写。通过使用带有向量的保留,您可以有效地为数据分配空间并防止不必要的复制。如果编译器足够好,向量数据结构本身将归结为具有大小的堆分配数组。

另外,请注意我说的是。如果您更愿意将数据分配到堆栈中,那么您会坚持使用固定大小的数组(包括 std::array)。

而且它还很大程度上取决于编译器如何组合向量。较旧的编译器可能(重点强调可能)效率较低。这就是为什么对于嵌入式系统,在全面概括什么是“好”什么是“坏”使用之前,您确实需要了解架构和编译器。

【讨论】:

    【解决方案3】:

    std::vector 应该用于动态调整大小的数组。如果您在编译时知道大小,则可以改用std::array

    如果你知道数组的唯一近似大小,C++14 起你可以使用std::dynarray。该数组具有固定大小,在对象生命周期内无法更改。当使用不带分配器的std::dynarray 时,可以进行额外的优化,operator new 可能不会被调用并且将使用基于堆栈的分配。

    【讨论】:

    • 有些人会这样做:假设数组可以在 1 到 10 个元素之间增长或缩小。有些人会在堆栈中创建一个覆盖此大小的数组,NULL 根据当前大小终止。切换到向量要干净得多,但要以使用堆为代价。这就是我所关心的
    • 酷,我会研究一下 std::dynarray。谢谢!
    • @Ryuu 请注意,std::dynarray 仅从 C++14 开始可用。
    【解决方案4】:

    在堆栈中使用已知大小的数组可确保在编译时有空间进行分配。

    错误的假设。

    当您收到大量调用(甚至不是递归的,而是具有很多级别的堆栈)时,您无法确定堆栈是否有足够的空间来容纳必须在其上创建的对象。

    有一些基本检查(如果你的对象的大小至少超过 size_t),但我什至不认为它们是标准强制要求的。

    一个——有点过分——例子在这里:

    #include <iostream>
    
    template <unsigned long size>
    struct BigObject
    {
        unsigned long array[size * size];
        BigObject<size - 1> object;
    };
    
    
    template <>
    struct BigObject<0>
    {
        unsigned long array[1]; 
    };
    
    BigObject<900>& recurse(BigObject<900>& object1, unsigned long n)
    {
        if (n == 0)
        {
            return object1;
        }
    
        BigObject<900> object;
    
        return recurse(object, n);
    }
    
    int main(int argc, char const *argv[])
    {
        BigObject<900> object;
        recurse(object, 20);
    
        std::cout << sizeof(object) << std::endl;
        return 0;
    }
    

    http://ideone.com/pkHo43

    它确实崩溃了。 这不是一个特例。我在一个面向 32 位桌面的应用程序中遇到了问题(所以没有你的内存限制)在堆栈上分配一个太大的数组。

    【讨论】:

    • 如果对象/数组是全局的,不保证吗?
    • 是的,由 elinker 保证。您必须检查,但我认为如果程序仅使用存储在 .data 段中的对象,则不能使程序耗尽内存(除非您无法加载库本身)
    • 虽然您可以获得测量可能调用链并计算必要的最大堆栈深度的工具(或者,至少,您可以 - 它们对于支持递归的语言可能不太有用)。跨度>
    【解决方案5】:

    当使用足够的优化标志编译时,std::vector 只是一个指针和两个size_t。现在size_t 的内存在你事先知道向量的大小并且永远不会改变时是一种浪费。

    我会说:

    正如 cmets 中提到的,您可以使用大部分功能,例如&lt;algorithms&gt; 在固定大小的数组上使用 begin/end 迭代器的指针。

    例如:

    int array[8] = { 1,2,3,4,5,6,7,8 };
    std::random_shuffle(array, array+8);
    

    【讨论】:

    • 这是两个指针和一个size_t,或者等效地(但在实践中很少见)一个指针和两个size_ts。而且它不仅仅是向量本身的大小,它的内存是动态分配的这一事实会产生后果:存在分配器开销(时间和空间),并且存在碎片风险。
    • 为什么会有碎片化的风险? vector 将其元素存储在连续内存中。
    • 嗯,其他 STL 功能是过渡的原因。示例包括排序、查找等,而无需重新实现它们。我知道 STL 的排序也适用于数组,但总的来说,STL 提供了更多功能(或减少了重新发明轮子的需要)
    • 在嵌入式系统中,您可能希望使用向量来降低堆栈使用率,尤其是在递归函数调用时。
    • @Ryuu 在重构所有代码之前,您知道您也可以在连续内存的指针上使用 std::sort 等,对吧?
    猜你喜欢
    • 1970-01-01
    • 2011-10-07
    • 2010-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 2017-01-07
    相关资源
    最近更新 更多