可变长度数组在主函数中编译,但在 class 声明中不编译?

Variable length array compiles in main function, but not in class declaration?

this answer the user reports that his g++ version compiles VLAs, as well as in this question。在我的编译器上这也有效(具有该扩展名),但是如果我想将这样的数组声明为 class 成员,例如 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,但不允许在 class 成员的声明中使用?

附录

我正在使用 gcc 4.8.2

编辑

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

编辑 2

需要说明的是,这可以编译(在我的例子中)

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

    size = 5;

    return 0;
}

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

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

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

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

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];

记住你不能在你的class定义中定义变量,只能声明它们。

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

使用alloca创建本地 VLA 非常容易/只需修改堆栈指针。在一个类型中支持它有很多挑战(打破 offsets/pointers 到成员等)。将其分配为对象的一部分会使它的大小动态变化——使赋值和指针算法等事情变得更加复杂。由于我们可以使用指向对象外部数据的指针,因此似乎不值得为类型提供支持。

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

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

堆栈分配

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

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

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

结构分配

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

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

现在。此对象的 via sizeof() 大小是多少?大概是8。此大小为数组 z 分配 no space。它假定它的长度为 0。那么我们可以用它做什么?

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

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

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

如果有足够的编译器扩展,我们可以做更多的事情。但问题是,一般来说,我们最多只想为系统中的每个变量做一个指针加上一对(但固定数量)的计算偏移量。这两个扩展不会破坏这种愿望。

你建议的代码怎么样?

允许这样的事情:

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

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

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

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

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

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

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

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

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

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

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

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

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

描述了 gcc 对可变长度数组的支持 here,或键入 "info gcc" 并搜索 "Arrays of Variable Length".