【问题标题】:Variable length array compiles in main function, but not in class declaration?可变长度数组在主函数中编译,但不在类声明中?
【发布时间】:2015-06-29 09:00:03
【问题描述】:

this answer 中,用户报告说他的g++ 版本可以编译VLA,在this question 中也是如此。在我的编译器上这也有效(有那个扩展名),但是如果我想将这样的数组声明为类成员,例如 positionvelocity in

class PSO
{
    private:
        static double * data;
        static int len;
        static int dim;
        double position [dim];
        double velocity [dim];

        double get_min();
        double get_max();
        void copy_data(double data *);

//...

它不编译。这是什么原因?为什么编译器扩展允许在 main 函数中使用 VLA,但不允许在类成员的声明中使用 VLA?

附录

我使用的是 gcc 4.8.2

编辑

我知道我应该使用std::vector 或指针,但我想知道为什么它会在主函数中编译而不是在类声明中。

编辑 2

要清楚,这是编译(在我的情况下)

int main()
{
    int size;
    double data [size];

    size = 5;

    return 0;
}

这与我展示的其他问题完全不同,因为我在数组声明之前没有std::cin >> size 语句。其他人的编译器中有这个(怪癖)吗?

【问题讨论】:

    标签: c++ arrays class c++11 g++


    【解决方案1】:

    VLA(可变长度数组)是 1999 年 ISO C 标准中添加到 C 语言中的一项功能(并且在 2011 年标准中成为可选)。 C++ 标准没有采用它们。

    C++ 中的 VLA 是 gcc 扩展。它们紧密基于相同功能的 C 版本。 g++ 不允许 VLA 作为类成员的很大一部分原因是 C 没有类。它确实具有与类非常相似的结构,但 C 不允许在结构中使用 VLA。

    将 VLA 实现为具有自动存储持续时间的对象(非static 局部变量)相对简单。数组的长度是通过在遇到声明时评估[] 之间的表达式来确定的,这决定了必须分配多少空间(通常在堆栈上)。释放 VLA 通常是在离开声明对象的块时拆除堆栈帧的一部分。

    作为类或结构的成员的 VLA 会更复杂。在您的示例中:

    class PSO {
        ...
        static int dim;
        double position [dim];
        double velocity [dim];
    };
    

    每次创建PSO 对象时都必须分配positionvelocity 成员,使用dim 的当前值来确定长度。如果dim 没有分配给它的值,它将是0——C 和C++ 都不允许零长度数组。如果已经分配了一个值,那么velocity 的偏移量必须在运行时计算;这并非不可能,但通常类和联合的成员具有恒定的偏移量。大概positionvelocity 的长度将由dim 的值在PSO 对象创建时决定——但dim 可以稍后修改,因此很难确定数组的长度对于任意 PS0 对象。

    sizeof 应用于VLA 对象会在运行时计算大小。该大小通常存储在与 VLA 的 type 关联的编译器创建的对象中。每个定义的 VLA 对象都有自己的类型。如果允许 VLA 作为类成员,则不同大小的数量可以是任意的,因为多个 PS0 对象是动态创建的。

    这些困难都不是不可克服的。例如,Ada 允许记录(类似于 C 和 C++ 结构)的成员是数组,其长度可以随记录类型的每个实例而变化:

    type Rec(Length: Natural) is
        record
            S: String(1 .. Length);
        end record;
    

    将其作为 C++ 中的语言扩展支持需要在编译器中进行大量工作。大概 gcc 团队,因为他们必须为 C 实现 VLA,因此决定以类似的方式为 C++ 实现它们将获得可观的回报,而无需付出太多努力。按照您的建议扩展它们需要大量的工作(尤其是设计语义以便可以一致地使用它们),因为他们可能认为没有足够的好处 - 特别是因为 C++ 在标准库容器类中具有更强大的功能.

    gcc 对可变长度数组的支持描述为here,或者输入“info gcc”并搜索“Arrays of Variable Length”。

    【讨论】:

      【解决方案2】:

      所以,让我们先说清楚。 C++ 中的可变长度数组是一个编译器扩展。 C++ 标准不直接支持这一点。

      因此,这里讨论了两种不同类型的 VLA。堆栈分配和最后一个结构成员分配。让我们一次处理一个。

      堆栈分配

      在这里,我们只是在谈论类似的事情:

      int main() {
          int length = 12;
          float array[length];
      }
      

      这很简单。我们有一个指向当前正在使用的堆栈空间末尾的指针,当我们到达 array 声明时,我们会扩展它。

      结构分配

      这更复杂。支持的正常编译器扩展允许最后一个成员是可变长度数组。但我们只能通过一种方式做到这一点:

      struct MyObject {
          int x;
          float y;
          double z[]; // Note that we didn't give this any length!
      };
      

      现在。这个对象的sizeof() 的大小是多少?可能是8。此大小为数组z 分配无空间。它假设它的长度为 0。那么我们可以用它做什么呢?

      struct MyObject *object = malloc(sizeof(MyObject) + 8*sizeof(double));
      

      这允许我们通过 object->z 数组访问 8 个双精度数。

      现在,为什么我们不能做其他事情呢?

      给定足够多的编译器扩展,我们可以做更多的事情。但问题是,一般来说,我们最多希望对系统中的每个变量执行一个指针加上一对(但固定数量)计算的偏移量。这两个扩展并没有打破这种愿望。

      你建议的代码怎么样?

      允许这样的事情:

      struct MyOtherObject {
          int dim;
          int x[dim];
          int y[dim];
      };
      

      可以工作。但是,这是一个更复杂的扩展。它不包括在内。存在一些问题,例如在分配发生后能够更改dim。但最终,这些问题是可以解决的。

      但是编译器扩展不接受该代码,因为它超出了定义它的规范。

      【讨论】:

      • 是的,很好的答案。但是为什么 main 函数会在我的情况下编译?我也不明白它为什么会编译,因为当我声明 data 时,size 确实是未定义的,但它确实可以编译。也许是我的编译器中的错误?
      • 所以,澄清一下:你的意思是编译器需要在我声明它时(以及当我最终实例化它时)为一个类/结构分配一定的大小,但在定义中有一个 VLA的类会使该大小的分配复杂化?我知道在main 函数中它更简单,它只是在堆栈上分配更多空间,而在类/结构的情况下,事情就不太清楚了。
      • @KonradKapp:size 中有一些未指定的值。我怀疑系统只是在使用那个未指定的值。但无论哪种方式,我想这只是编译器认为未定义的行为。
      • @KonradKapp:当您考虑多重继承和虚拟继承等情况时,拥有可变长度成员变得更加复杂。我上面谈到的最后一个成员 VLA 只是在这些情况下报告编译器错误。
      • @BillLynch 啊是的,我从来没有想过,size 在声明后会有一些随机数。比为什么允许编译更让我困惑,因为它可以分配给data 一些疯狂的大小。更有理由使用 -Wall 进行编译。
      【解决方案3】:

      使用alloca/只需修改堆栈指针即可轻松创建本地 VLA。在类型中支持它有很多挑战(打破偏移量/指向成员的指针等)。将其作为对象的一部分进行分配会使它的大小动态化——使赋值和指针算术等事情变得更加复杂。由于我们可以使用指向对象外部数据的指针,因此似乎不值得为类型支持它。

      【讨论】:

        【解决方案4】:

        这绝对是一个猜测,我最近主要是用 C 编程。可能是因为类和结构需要以字节为单位的标准大小,以便它们可以以固定的间隔线性存储在内存中,同时也可以被使用 sizeof 的代码遍历。如果他们允许 VLA,那么一个对象可能有一个 16 字节的数组,而另一个可能有一个 16000 字节的数组。那么编译器如何编写代码来遍历不同长度的对象呢?我的猜测:它只是将其称为错误并且不会打扰。

        我相信你可以通过使用 malloc 和指针来解决这个问题。

        【讨论】:

          【解决方案5】:

          当您直到运行时才知道数组的长度时,您应该使用指针。在您的示例中,这将按如下方式完成:

          class PSO
          {
              private:
                  static double * data;
                  static int len;
                  static int dim;
                  double* position;
                  double* velocity;
          
                  double get_min();
                  double get_max();
                  void copy_data(double data *);
          
              //...
          }
          

          然后您将在代码中的某处实例化指针(例如 PSO 的构造函数),如下所示:

          position = new double[dim];
          velocity = new double[dim];
          

          请记住,您不能在类定义中定义变量,只能声明它们。

          不要忘记——因为您是用 C++ 编写的,所以您还可以访问其他结构,例如 vector,它可以将变量保存在动态大小的数组中。

          【讨论】:

          • 这不能回答问题。无法在类中定义可变长度数组是一个合理的解决方法,但问题是为什么不允许这样做。
          • 因为直到运行时才能知道。数组具有预先分配的内存;如果你不知道你有多少数据,你必须在运行时自己分配内存。
          • @KonradKapp 我给出了解释。请记住,您不能在类定义中定义变量。此外, dim 是未定义的;正如你所问的那样,这没有意义。
          • 我认为你在掩饰一步。为什么不能在课堂上声明 VLA?
          • dim 是静态的。原则上,可以在创建任何PSO 对象之前为其分配一个值,并且该值用于确定数组的长度。我并不是说这会是一个好主意,但这并不是不可想象的——而且 VLA 无论如何都是一种语言扩展。
          猜你喜欢
          • 1970-01-01
          • 2013-08-11
          • 2012-12-17
          • 2020-08-04
          • 1970-01-01
          • 2015-09-22
          • 1970-01-01
          • 2021-10-26
          • 2014-06-23
          相关资源
          最近更新 更多