可变长度数组在主函数中编译,但在类声明中不编译
Variable length array compiles in main function, but not in class declaration?
在这个答案中,用户报告他的g++版本编译了VLA,在这个问题中也是如此。在我的编译器上,这也可以(具有该扩展名),但如果我想将这样的数组声明为类成员,例如中的position
和velocity
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
的当前值来确定长度来分配position
和velocity
成员。如果dim
没有赋值,那么它将是0
——而且C和C++都不允许零长度数组。如果已经指定了一个值,则必须在运行时计算velocity
的偏移量;这并非不可能,但通常情况下,阶级和工会的成员会有不断的补偿。假设PSO
对象创建时position
和velocity
的长度将由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
,它可以将变量保存在动态大小的数组中。
- 是什么原因导致它无法编译?它是声明签名还是在函数本身的实现中
- 未声明的标识符编译暗黑破坏神 2 程序"muleview"
- 模板方法访问正向声明的类仅在没有此指针的情况下无法编译
- 使用直接大括号初始化时,C++ 编译错误"声明末尾的预期";"
- 即使不包含其标头,如何成功向前声明的类编译?
- 如何制作 cmakelists.txt编译使用在其他地方声明和实现的函数和类的 CPP
- C++ 在编译过程中 strtok 函数 Eclipse 说没有在范围内声明?
- Lamda 仅在声明为"自动"时编译,而不是在声明为'bool'时编译
- 由于值返回函数中的错误,程序无法编译.它说未声明的标识符
- C++ SPDLOG 编译错误:变量或字段"set_error_handler"声明为无效
- 在 mingw64- 变量下的窗口中编译 openvpn3 时出错,未在范围内声明
- 尝试编译GoogleTest,但出现以下错误:尚未声明'::OpenThread'
- 如何实现声明功能-C 11,编译时间
- 可变参数模板仅在正向声明时编译
- std::d eclare_if 或其他在编译时丢弃成员声明的假设方法
- 编译错误 在 C++ 上,Calcarea 未在此范围内声明
- 试图过渡到GTK3(针对GTK -3.0库编译)时,在此范围内未声明GTK_Object
- 如何为 std::array 声明全局编译时常量?
- 我的Qt程序可以编译,但是当我在声明中使用ifstream时崩溃
- 代码块编译错误.Cin 未在此范围内声明