没有常量表达式的C++数组大小

C++ array sizes without constant expressions

本文关键字:数组 C++ 常量 表达式      更新时间:2023-10-16

我正在阅读Stroustrup的C++教程。在第9页,他指出:

"数组的大小必须是一个常量表达式。"

然而后来,在第16页,他使用了以下代码示例:

void vector vector_init(Vector& v, int s)
{
  v.elem = new double[s]; // Allocate an array of s doubles
  v.sz = s;
}

这里s不是一个常量表达式,那么将v.elem初始化为new double[s]是合法的吗?

分配的数组(即使用new[]表达式创建的数组,如new double[s])的生存期必须由代码(通过delete[])管理,而声明的

int* p = new int[s];  // allocated array, p has type int*
int q[10];            // declared array, q has type int[10]
std::vector<int> u;   // has member allocated array
std::array<int, 5> v; // has member declared array

差异不是基于堆栈/堆的。声明的数组可以是堆分配的(例如new array<int,5>),也可以不在堆栈上(例如static double x[100];

对于分配了数组,大小不必是常量表达式。大小将简单地编码到分配器以某种方式产生的内存块中(例如实际数据开始前的四个前导字节),以便相应的delete[]知道要删除多少元素。

对于声明的数组(或未分配的数组,没有new/malloc/等),大小必须被编码到类型中,这样析构函数就知道该做什么。唯一允许的标准数组声明是:

T D[constant-expression_opt];

(其中D是一个声明符,可以是一个名称或另一个数组声明,等等)声明的数组不限于堆栈。请注意,为了增加混淆,常量表达式是可选的。

数组在C++中提供了许多混乱的来源。分配的数组和声明的数组有不同的大小规则和不同的管理实践,但您可以将T*分配给其中任何一个,并且它们被等效地索引。分配的数组指针(仅此而已),但声明的数组衰减为指针(但数组!)。


注意,是可变长度阵列(VLA)的一个概念。例如,gcc支持将它们作为扩展,但它们是非标准C++。不过,它会定期被提出,你可以看到这个问题来了解更多关于它们的信息。

当创建一个内存由编译器管理的数组时,其大小必须是(编译时)常数。例如:

int a[5];
const int sz = 7;
int b[sz] = {0};

(ex:C(C99以后)的一些语言支持动态数组大小)

如果你想要一个动态大小的数组(你给出的示例片段),你需要自己为它分配内存——完成后你还需要用delete释放它。这样的数组的大小也可以是非常数。此外,您需要自己管理内存,分配可能会失败,运算符(例如sizeof)将对指针而不是数组进行操作。

在现代C++(C++11以后)中,即使stl容器std::array也必须具有恒定的大小。

报价

数组的大小必须是常量表达式。

正在讨论数组声明,例如

double a[EXPR];

其中EXPR必须是常量或constexpr(C有可变长度的数组,但它们不是标准C++的一部分)。

你提到的作为反例的表达式

new double[s]

而不是数组,尽管[]。它是一个新的表达式,产生的是指针,而不是数组。您没有显示v.elem的定义,但我可以看出它是一个指向double的指针。

从关于新表达式的链接讨论中注意到,

如果类型是数组类型,则除第一个维度外的所有维度都必须指定为正{类似于积分常数-细节已消除}。

因此,上面提到的类型double[s],这是明确允许的。

诚然,数组和传递给新表达式的数组类型之间的区别有点微妙,但你不能仅仅因为[]就把它们混为一谈,就像你可以声称一样

map["key"]

通过声明长度为CCD_ 22的数组违反了某些内容。