堆栈上的可变大小的对象

时间:2016-04-12 02:38:35

标签: c++ memory-management

我可以在堆栈上创建一个可变大小的C风格数组,没有任何问题:

void f(int n) {
  float data1[n]; // OK
}

但我不能对std::array执行相同的操作:

void f(int n) {
  std::array<float,n> data2; // error: ‘n’ is not a constant expression
}

以下示例更全面地说明了我的观点:

#include <array>
#include <cstdio>

template<int N>
class A{
  public:
    void print() { 
      printf("data[0]: %2.2f\n", data[0]);
    }
  private:
    float data[N];
};

void f(int n) {
  float data1[n]; // OK
  std::array<float,n> data2; // error: ‘n’ is not a constant expression
  A<n> data3; // error: ‘n’ is not a constant expression
}

int main(int argc, char **argv) {
  f(3);
}

我的问题:有没有办法在堆栈上严格创建一个可变大小的类/结构?如果是这样,怎么样?如果没有,这仅仅是因为规范还是因为技术原因而被禁止?

3 个答案:

答案 0 :(得分:4)

您正在将苹果与橙子进行比较。

当你使用variable-length array时,你会引用一个来自旧时代的特性(在C99中引入),它允许在堆栈上分配数组,而不需要在编译时知道大小。这是由编译器完成的,只需在运行时保留所需的堆栈大小,而不是预先计算位移。

如果我没记错的话,它实际上已经在C ++ 11中降级为可选功能状态(而C11带来了它)。

然后你有std::array<T,size_t>这是完全不同的东西。使用编译时类型/值来实例化template是必需的。所以你根本无法做你想做的事。

重点是C ++是一种允许您避免使用VLA的语言,因为您有std::vector和动态分配。

如果你真的想在堆栈上分配东西,你必须寻找99.9%的时间不需要特定和肮脏的技巧,比如放置新的alloca(在任何情况下都是非标准的) ,例如:

void *reserved = alloca(sizeof(MyClass));
new (reserved) MyClass();

答案 1 :(得分:3)

std::array不是“可变大小的类/结构”。这是一个模板。它碰巧实现了一个数组容器,其长度由第二个模板参数指定,但这与“一个可变大小的类/结构”不同。

std::array模板的第二个参数必须是常量值。模板参数可以是类,也可以是常量值。它不能是非常数值。这就是模板的工作方式。

您可能想要的是std::vector。声明它,并resize()它。这是你的大小不一的数组。

答案 2 :(得分:3)

  

有没有办法在堆栈上严格创建一个可变大小的类/结构?

没有。 C ++标准中不支持可变大小的class / struct,因此无法在任何地方创建它们。也就是说,您可以创建一个class / struct,其中最终数据成员是一个数组,并故意超出数组维度。这个技巧在C中被称为“结构黑客” - 请参阅FAQ。它的未定义行为是否取决于您的实现。您可以将其与alloca和展示位置 - new结合使用,以在堆栈上创建此类变量,但如果您的程序依赖于任何一方,则应谨慎地手动delete析构函数的影响。

  

如果没有,这仅仅是因为规格还是因为技术原因而被禁止?

原因是表现。通常,可以使用堆栈指针CPU寄存器加上常量偏移来访问堆栈上的变量,编译器可以在编译器时为任何给定的本地/自动变量计算。当堆栈上有可变长度内容时,之后的局部变量不再处于堆栈指针寄存器的固定偏移量,这意味着编译器必须生成代码以添加各种值,以便计算访问时的运行时偏移量变量。当您有多个可变长度的堆栈托管变量时,问题会复合,性能会进一步降低。为了避免程序员产生意外缓慢的代码,并使编译器更简单,标准不需要支持可变长度类型。