为具有有限CPU和内存资源的嵌入式系统编写C ++代码时,常见的经验法则是实例化堆栈中的对象,并避免使用堆,除非确实有必要。当然这样做有很多已知的好处,但是随着STL和人们推荐std :: vectors作为一种有效的数据结构的出现,它是否违反了我提到的经验法则,因为向量将使用堆?
示例:在过去,人们会声明具有已满足用量的已知大小的静态数组。如今,人们只会使用矢量。
我对这种转换并不是很满意,因为矢量无法分配所需的内存(提醒:这适用于内存有限的嵌入式系统)。在堆栈中使用已知大小的数组可确保在编译期间有分配空间。
调用reserve()有点帮助,但这是在运行时完成的。
那么,这是引起关注的原因,还是我只是偏执狂?使用这些向量肯定要容易得多,但对于嵌入式环境,它可能不是一个好主意吗?
注意:这不是关于动态与固定数组的关系,而是关于如何在内存中分配数据的更多内容,这对我的环境来说是一个大问题。举个例子,有些人会这样做:假设数组可以在1到10个元素之间增长或缩小。有些人会在堆栈中创建一个覆盖此大小的数组,并根据当前大小终止NULL。这样就避免了碎片,并且在编译期间保证了分配。但是,切换到vector会使它更清晰,但代价是使用堆,并且如果分配失败可能不得不处理异常。这就是我所关注的。
答案 0 :(得分:8)
我相信您忘记了STL容器的一个非常重要的属性: allocators 。
STL容器(无论是vector
还是其他)从其分配器获取所有内存(除了可以使用sizeof
检查的非常基本的堆栈占用空间)。因此,它非常适合在嵌入式开发中提供专用的分配器:
随着C ++ 11的出现,您甚至可以使用有状态分配器,以便单个分配器类型可以指向不同的内存池。
因此,std::vector
,甚至std::set
或std::map
的使用与预分配策略不相容;请记住,除了std::vector
之外,其他STL容器通常都有一些每项开销,在确定它们应该使用的内存区域时必须考虑到这一点。
答案 1 :(得分:4)
“取决于”在这里可能是轻描淡写。通过使用带向量的保留,您可以有效地为数据分配空间并防止不必要的副本。如果编译器足够好,则向量数据结构本身将归结为具有大小的堆分配数组。
另外,请注意我说堆。如果您希望将数据分配给堆栈,那么您会遇到固定大小的数组(包括std :: array)。
它还在很大程度上取决于编译器如何将向量放在一起。较旧的编译器可能(非常强调可能)效率较低。这就是为什么对于嵌入式系统,在对“好”和“坏”使用的内容进行全面概括之前,您确实需要了解架构和编译器。
答案 2 :(得分:2)
std::vector
应该用于动态调整大小的数组。如果您在编译时知道大小,则可以使用std::array
代替。
如果您知道数组的唯一近似大小,自C ++ 14 ,您可以使用std::dynarray
。此数组具有固定大小,在对象生存期内无法更改。当使用没有分配器的std::dynarray
时,可以进行额外的优化,可能不会调用operator new
并且将使用基于堆栈的分配。
答案 3 :(得分:2)
在堆栈中使用已知大小的数组可确保在编译期间有空间进行分配。
错误的假设。
当你有很多调用时(甚至不是递归的,而是一个具有很多级别的堆栈),你不能确定你的堆栈有足够的空间来容纳必须创建的对象。
有基本的检查(如果你的对象的大小至少超过size_t),但我甚至认为它们不是标准强制要求。
A - 有些过分 - 例如:
#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;
}
确实崩溃了。 这不是一个特例。我在32位面向桌面的应用程序中遇到了问题(因此没有任何内存限制)在堆栈上分配太大的数组。
答案 4 :(得分:1)
当使用足够的优化标志进行编译时,std::vector
只是一个指针和两个size_t
。现在,size_t
的内存在您事先了解向量的大小时就会浪费,而且永远不会改变。
我会说:
std::array
std::vector
正如评论中所提到的,您可以使用大多数功能,例如:通过使用begin
/ end
迭代器的指针,在固定大小的数组上<algorithms>
。
例如:
int array[8] = { 1,2,3,4,5,6,7,8 };
std::random_shuffle(array, array+8);