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

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

本文关键字:编译 声明 函数 数组      更新时间:2023-10-16

在这个答案中,用户报告他的g++版本编译了VLA,在这个问题中也是如此。在我的编译器上,这也可以(具有该扩展名),但如果我想将这样的数组声明为类成员,例如中的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或指针,但我想知道为什么它会在主函数中编译,而不是在类声明中编译。

编辑2

需要明确的是,这编译了(在我的情况下)

int main()
{
int size;
double data [size];
size = 5;
return 0;
}

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

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对象时都必须使用dim的当前值来确定长度来分配positionvelocity成员。如果dim没有赋值,那么它将是0——而且C和C++都不允许零长度数组。如果已经指定了一个值,则必须在运行时计算velocity的偏移量;这并非不可能,但通常情况下,阶级和工会的成员会有不断的补偿。假设PSO对象创建时positionvelocity的长度将由dim的值确定,但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++实现类似的VLA将在不付出太多努力的情况下获得可观的回报。按照您的建议扩展它们将需要大量的工作(尤其是设计语义,以便能够一致地使用它们),因为他们可能没有认为这是足够的好处——尤其是因为C++在标准库容器类中具有更强大的功能。

这里描述了gcc对可变长度数组的支持,或者键入"info-gcc"并搜索"arrays of variable length"。

所以,让我们先弄清楚一些事情。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。但最终,这些问题是可以解决的。

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

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

这绝对是猜测,我最近主要是用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];

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

别忘了,由于您是用C++编写的,您还可以访问其他结构,如vector,它可以将变量保存在动态大小的数组中。