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

时间:2015-04-21 23:40:01

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

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

中的positionvelocity
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,而不是类成员的声明?

附录

我正在使用gcc 4.8.2

修改

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

编辑2

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

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

    size = 5;

    return 0;
}

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

5 个答案:

答案 0 :(得分:4)

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

每次创建position对象时,必须使用当前值velocity来分配PSOdim成员来确定长度。如果dim没有为其分配值,那么它将是0 - 并且C和C ++都不允许零长度数组。如果已分配值,则必须在运行时计算velocity的偏移量;这并非不可能,但通常班级和工会的成员都有不断的抵消。大概positionvelocity的长度将由创建dim对象时PSO的值确定 - 但dim可以在以后修改,使得很难确定任意PS0对象的数组长度。

sizeof应用于VLA对象会在运行时计算大小。该大小通常存储在与VLA的类型关联的编译器创建的对象中。每个定义的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"并搜索"可变长度阵列"。

答案 1 :(得分:3)

所以,让我们先说清楚。 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。但最终,这些问题可以得到解决。

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

答案 2 :(得分:1)

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

答案 3 :(得分:0)

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

我确定你可以通过使用malloc和指针解决这个问题。

答案 4 :(得分:0)

如果在运行时之前不知道数组的长度,则应使用指针。在您的示例中,将执行以下操作:

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,它可以在动态大小的数组中保存变量。