std::array<>是否只保证在栈上分配内存?

69

不使用 new 关键字,std::array<int,10> 是否有保证被 C++ 标准在栈上而不是堆上分配内存?

需要澄清的是,我不是指 new std::array<int, 10>,我主要想知道标准库是否允许在其实现中使用 new 关键字。


当我把std::array放在一个结构体中时,我想到了一个相关的问题:它是否会扩展结构体的大小以包含数组内容的大小,还是(像向量一样)仅扩展结构体的大小以包含元数据的大小? - 把友情留在无盐
@把友情留在无盐 这个结构体的大小足以容纳数组本身。也就是说,在 struct S { std::array<int8_t, 10> a; }; 中,sizeof(S) == 10 - ljleb
2个回答

57
TL;DR:是的,它在堆栈中。
更长的故事:
C++没有堆栈或堆的概念。这些都是实现细节,至少有一个平台不使用传统的堆栈(而是使用堆分配的链接列表)。
它具有自动存储和自由存储。new访问自由存储,而“在堆栈上”的变量进入自动存储。
在实践中,为了在自由存储上分配东西,您必须冒着内存不足异常的风险。因此,一般规则是保证不抛出异常的事情必须使用自动存储。数组做出了这个保证(除了其中可能会抛出的内容)。它也是一个普通数据的聚合体,被迫看起来像:
template<class T,std::size_t N>
struct array {
  T __no_fixed_name__[N];
  // non-constructor/destructor methods omitted as they are noise at this point
};

在理论上,它可以通过编译器的魔法实现,但这并不是真正的C++,但没有必要这样做,所以没有人费心去做。因此,总之:是的,std::array在堆栈上。

5
你能指引我找到那个有趣的无栈实现吗?听起来非常有意思。(这些东西从来都不是简短易读的……) - towi
@towi 抱歉,我知道它的存在,仅此而已。 - Yakk - Adam Nevraumont
6
@towi IBM主机,显然。 在那里Jerry Coffin的回答中有更多细节评论。 - user743382
2
我想指出std::array模板签名有两个参数。第一个是类型,第二个是元素数量。需要注意的是,没有第三个模板参数用于分配器。 - Trevor Boyd Smith
2
我认为了解std::array存储在堆栈上很重要,因为这样你就可以避免在堆栈上声明大型std::array并导致程序崩溃。 - Trevor Boyd Smith
4
我曾经使用过一个 C 语言系统,其中自动变量不是存储在堆栈中,而是使用静态数据区域的空间;它默认允许每个函数变量的3个副本以处理可重入性,但是如果您进入超过3次,系统会出错。 - M.M

31
我在标准中没有找到更明确的答案,但是[array.overview]/2说:

数组是一个聚合体 ([dcl.init.aggr]),它可以用最多N个元素进行列表初始化,这些元素的类型可转换为T

[dcl.init.aggr]/1说:

聚合体是一个数组或类 (第[class]条) ,其中

  • 没有用户提供的、显式的或继承的构造函数 ([class.ctor])

...

基本上就是这样了。聚合体不可能动态分配内存(或者说,在构造过程中自行执行任何操作)。只有一个隐式声明的平凡构造函数。
当然,如果你使用new std::array<...>,你会得到一个位于"堆"上的数组。
一些人可能对我们在cppreference上的解释更满意:

std::array是一个封装了固定大小数组的容器。

这个容器是一个聚合类型,其语义与持有C风格数组T[N]作为其唯一非静态数据成员的结构相同。


第三,std::array是在C++11中引入的。为什么?例如,为了在某些方面补充std::vector的使用,比如在constexpr函数中使用,其中不允许动态分配内存。

4
换句话说,就是在布局方面您将得到与template <class N, size_t N> struct array { T elems[N]; };相同的保证(实际上并没有elems存在)。 - GManNickG
6
请注意,C++并没有真正的堆栈或堆的概念。通常情况下我们可以理解你的意思,但是你提到了“按照C++标准”,你得到的保证和我之前举的例子一样。如何进行分配取决于你的实现。(理论上,我可以编写一个不使用堆栈而完全动态分配所有内容的愚蠢编译器。) - GManNickG
@GManNickG 你知道吗,我完全没注意到这一点!“没有堆栈和堆的概念”,真的吗?也没有“动态”或“静态”分配的概念?你可能是对的,在标准库中进行快速搜索可以证实这一点。我想定义new<new>规范特别描述了动态分配,所以我错过了静态分配规范的缺失。考虑到你的愚蠢编译器,我想知道是否可能在堆栈上完成所有操作。不,可能不行,我怎么支持new呢?但这是一个不同的问题。 - towi
@towi,您可以通过重载operator new()并使用自己选择的静态分配(某个大小)来实现。或者更好的方法是提供一个静态分配器。 - user2296177

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