可变长度数组在主函数中编译,但在 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 成员,例如 position
和 velocity
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
对象时,都必须分配 position
和 velocity
成员,使用 dim
的当前值来确定长度。如果 dim
没有分配给它的值,它将是 0
—— C 和 C++ 都不允许零长度数组。如果已经分配了一个值,那么 velocity
的偏移量将必须在 运行 时间计算;这并非不可能,但通常 classes 和 union 的成员具有恒定的偏移量。大概 position
和 velocity
的长度将由创建 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".
在this answer the user reports that his g++ version compiles VLAs, as well as in this question。在我的编译器上这也有效(具有该扩展名),但是如果我想将这样的数组声明为 class 成员,例如 position
和 velocity
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
对象时,都必须分配 position
和 velocity
成员,使用 dim
的当前值来确定长度。如果 dim
没有分配给它的值,它将是 0
—— C 和 C++ 都不允许零长度数组。如果已经分配了一个值,那么 velocity
的偏移量将必须在 运行 时间计算;这并非不可能,但通常 classes 和 union 的成员具有恒定的偏移量。大概 position
和 velocity
的长度将由创建 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".