我们能够创建一个大小为运行时常量的静态数组吗?

6

我们都知道静态数组的基本规则:

int size = 20;
char myArray[size];

不合法。

而且。
const int size = 20;
char myArray[size];

没问题。

但是,这个怎么办。

int f(const int size)
{
    char myArr[size];
}

void main()
{
   f(2);
   f(1024);
}

MSVC表示这是一个错误,而gcc似乎可以编译并正常执行。

显然,它不具备可移植性,但它应该被接受吗?

哪个编译器在这种情况下做得正确?

此外,如果编译器允许,良好的编程标准/实践是否应该允许它?

编辑:我的想法是我想要堆栈分配以获得速度,但我不知道数组的大小在编译时是多少。我知道还有其他解决方案,并且堆栈分配可能不是一个重要的优化,但我认为这是一种有趣的用法。


你的前两个例子是逐字相同的。 - AlcubierreDrive
5个回答

9

不,C++没有可变长度数组。C99有,而且gcc通过扩展允许使用。

使用std::vector


假设您已经对应用程序进行了分析,并发现这是一个瓶颈,请编写自定义分配器,从堆栈中分配并使用它。如果没有问题,则没有问题。

堆栈分配非常快,但在实际应用程序中,这可能不是主要问题。(您应该有一个自定义内存管理方案,其性能接近于堆栈分配。)


1
@GMan:发布一个问题“如何编写堆栈分配器?”,并将您的分配器作为答案发布。就像使用复制和交换惯用语一样。 - sbi
1
@jslap: 我一直在向人们指出http://y60.artcom.de/redmine/repositories/entry/y60/asl/base/static_vector.h这个链接,它是一个用于存储固定数量元素的栈上数据结构,可以替代`std::vector`。 - sbi
@sbi:社区常见问题解答似乎是个好主意,但管理起来可能会很困难。不过我肯定会考虑写一些分配器教程的形式。 - GManNickG
@GMan:刚刚看到这个讨论,我不太喜欢聊天,因为它感觉太像统治精英试图在幕后操纵网站。简而言之,我认为重要的是我们不能掌控向询问问题的人呈现C++信息的方式。我们存在偏见,因为通常我们是回答问题的人,而我们发现方便的并不一定是那些*重要的人(即提问者)想要的。 - jalf
很容易就想让人们看到我们已经写好的答案,而不是再次询问同样的问题,但我认为,某种类似阴谋的方式来劫持SO过程并引导人们进入我们“规范”的预定义答案并不是解决方案。 - jalf
显示剩余25条评论

3
您可以使用std::array来实现这个功能。 std::array是在TR1扩展中添加的,并且在命名空间std或std::tr1中可用,具体取决于您使用的编译器/标准库版本。
#incldue <array>
int main()
{
   std::tr1::array<int,25> myArray;
   //etc...
   myArray[2] = 42;
}

重新阅读问题,涉及堆栈分配...
如果您想要堆栈分配,可以使用alloca在堆栈上进行分配,而不是使用malloc(所有常见警告都适用)。
如果您想要更友好的界面,可以基于alloca实现自定义stl分配器,并使用带有此分配器的std :: vector(在实施之前应该阅读相关资料)。
例如:
#include <vector>
template <class T>
class MyStackAllocator
{ // implemented per std::allocator spec
...
}
int main()
{
//allocate a vector on the stack and reserve n items
vector<int, MyStackAllocator<T>> vecOnStack(25);
...
}

谢谢,但是在执行前我不知道我所需的尺寸。 - jslap
+1 for alloca。不幸的是,它不是C++标准的一部分,也不是类型安全的,但它非常便携,因为它被BSD标准化,然后被几乎所有编译器采用。尽管如此,您打算如何使用更友好的接口包装alloca,以便包装器返回后存储仍然有效? - Ben Voigt

1

实际上,正确的答案已经在这个线程中提供了,所以我只想为它提供更多的背景。

您需要创建自定义分配器,该分配器使用alloca()(或Windows中的_alloca())函数进行动态堆栈分配。创建一个非常容易,您可以使用典型的分配器样板,将allocate()成员函数更改为return (pointer)(alloca(size * sizeof(T)));并使deallocate()函数为空,因为没有手动堆栈释放。之后,您可以将您的分配器提供给标准容器,例如vector<T, stack_allocator<T>>

但是有两个注意点。可分配的堆栈大小可能会有很大差异,通常您可以在编译时设置它。在32位应用程序中,Visual Studio默认情况下将其限制为1MB。其他编译器可能具有不同的限制。在64位应用程序中,实际上没有任何问题,因为堆栈可以像堆一样大。您可能需要捕获堆栈溢出的结构化异常并将其转换为C++异常。

第二个注意点是,您不应将堆栈指针复制到函数外部,因此如果将堆栈分配的对象传递到/从函数,则移动语义等将无法工作。

还有一个不方便的地方,就是您无法复制具有不兼容分配器的容器,但您可以逐个元素地复制它们。例如:

vector<int> vint;
vector<int, static_allocator<int>> vsint;

vint = vsint; // won't compile, different allocators
std::copy(vsint.begin(), vsint.end(), vint.begin()); // fine

1

可变长度数组(VLA)由C99支持,但不被C++支持。gcc通过扩展允许它。

在C++中,std::vector正在执行VLA在C99中的功能。


这和我的答案有什么不同?你甚至得到了相同的句子数量和布局。(尽管你说“向量”是VLA的drop-in,这是不正确的;“向量”动态分配,而VLA则不是。) - GManNickG
1
@GMan:更准确地说,vector从堆上动态分配内存,而VLAs从自动存储器(在大多数平台上意味着调用堆栈,但实际上并不是必需的)动态分配内存。 - Ben Voigt
@GMan 抱歉,我开始回复了,然后离开去做其他事情。当我回来时,我发了帖子。现在我看到这是相同的回复。 - BЈовић

0
你真正想要的并不完全是你所问的。似乎你真正想要的是一个数字到数字的映射,使得索引2042保存值23等。
由于你实际上不知道可能需要使用的最高数字的上限,你可能需要重新构造代码,以便它使用一个(数学)映射,其中你不再将2042视为数组索引,而是将其视为访问值23的键。
---- 对于能否分配一个具有执行时间常量的静态数组的回答 ----
如果你想要一个执行时间静态数组,最好的选项是使用备用指针语法声明数组,然后在程序执行开始时在非静态的init()函数中初始化它。
如果你尝试在main(...)执行之前分配该数组,则可能会遇到无法确定哪些静态代码块将以何种顺序调用的风险。有时这没什么区别;但在你的情况下,似乎你需要在运行时计算数字,因此模块的顺序变得很重要。
通过使用一个非静态方法,你可以保证所有静态代码都在分配数组之前执行。

这不是我想要使用它的方式。我想在我的函数中使用一个位于堆栈上的临时数组,例如用于动态编程算法。另外,请查看我关于堆栈分配的澄清。 - jslap
在这种情况下,似乎你最终会被迫使用替代指针语法来分配堆上的数组。C的编译器对于需要提前知道要布置多少内存到栈中非常严格(以查看是否需要实际在堆中布置它,因为它可能不适合执行帧中栈的额外内存)。C++对于数组也有同样严格的标准,由于这是一个编译时检查,所以在运行时完全做到100%是不可能的,除非进行一些深入而可怕的魔法。 - Edwin Buck
@jslap:就像我说的,这是从C99采用的扩展。 - GManNickG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接