为什么你会想要在堆上拥有一个数组?

4

为什么你会想在堆上拥有一个数组?我的教授给出了两个原因:

  1. 将数组传递给函数,而不是传递副本
  2. 使数组超出范围

这两个问题是否可以通过以下方式解决:

  1. 传递指向堆栈上数组的指针
  2. 返回数组的值而不是数组本身(即使用复制构造函数)

能否给我一个必须使用堆上数组的示例?


@JoshuaTaylor 使用复制构造函数返回数组。 - dfg
1
在堆栈上分配大型数组并不是一个好主意。 - Charlie Burns
@DyP 通过一些谷歌搜索,我得知 std::array 自 C++11 起就已经存在了。难怪我没有想到它。不过请原谅我,我得去把一些孩子从我的草坪上赶走。 - Joshua Taylor
@JoshuaTaylor,现在有了std::array并不意味着数组现在有构造函数了。该语句只是变得模糊了;)(“原始”数组与std::array)。 - dyp
3
堆很大,栈很小。 - Guido
显示剩余7条评论
6个回答

4
堆中的数组用于超出函数作用域的情况。如果您不想在上一个(更高级的)调用程序中使用它,则仅在堆栈上传递指向数组的指针是有效的。您不能从函数返回数组,但可以返回指向数组的指针,但如果它在堆栈中分配,则在函数返回后,它将指向无效的内存位置。
第一个原因是错误的:数组永远不会通过复制传递。当您调用函数时,数组名称总是衰减为其第一个元素的指针,以避免复制整个数组。如果要通过复制传递数组,则必须将其嵌入到结构体中并传递结构体。
动态数组分配也很有用,如果您事先不知道数组的大小(尽管在C99引入可变长度数组之后,这不再是真的 - 但仍然,可变长度数组在堆栈上分配,因此您将遇到相同的问题)。
另一个使用堆分配的好处是,对于非常大的数组,您可以轻松地跳出堆栈内存。堆通常更大。

1
在C中,数组被表示为指向数组数据位置的指针(它指向数组中的第一个项目)。对于基于堆栈的数组,数组指针和数据位于同一位置。对于基于堆分配的数组,数组指针位于堆栈上,并指向堆上数组数据开始的位置。
对于点(2),您不能返回数组的值。相反,返回的是内存或堆栈上数组的位置。因此,在堆上分配它可以确保在从函数返回数组时保留数据。
另一方面,std :: vector的功能类似于数组。使用这个,数组数据分配在堆上,但管理数组的对象在堆栈上。因此,数组的生命周期由向量对象的生命周期控制。
std :: vector具有您描述的行为:
1.将向量按值传递到函数会导致在将其传递到函数时复制数据; 2.向量数据仅在函数的生命周期内存在。
从函数传递向量可能会导致数组数据被复制。但是,可以使用返回值优化和R值引用等方法进行优化,避免复制。

2
提到这个规则的例外可能是一个好的方法:声明数组会给你一个数组,而声明指针会给你一个指针 - 它们不可互换。发生的情况是,数组名称在表达式中会衰减为第一个元素的指针,除非:a)它是 sizeof 运算符的操作数;b)你使用 & 运算符获取其地址;c)它是文字字符串初始化器。 当然,这些例外也适用于数组名称。 如果您在函数中将其作为参数接收,则具有指针,而不是数组。 - Filipe Gonçalves
在C语言中,数组被表示为指向数组数据位置的指针(它指向数组中的第一个元素),这是不正确的。并没有为指向数组的单独指针分配存储空间。唯一分配的存储空间是用于数组元素本身;指针值是从数组表达式中推断出来的。 - John Bode

1
#include <assert.h>
#include <stdlib.h>

int * f(int* array) {
    assert(array[0] == 1); // OK

    int static_array[] = {1, 2, 3};
    //return static_array = {1, 2, 3}; //BAD: only lives in this function

    int * dynamic_array = malloc(sizeof(int) * 2);
    dynamic_array[0] = 1;
    dynamic_array[1] = 2;
    return dynamic_array; // OK: lives outside also
}

int main()
{
    int static_array[] = {1, 2, 3};
    int * returned_array;
    returned_array = f(static_array);
    assert(returned_array[0] == 1);
    free(returned_array);
}

那个 new 关键字是从哪里来的?这个问题标记了 [tag:C]。 - Joshua Taylor

0

除非你需要让数组在声明和初始化它的函数的作用域之外继续存在,否则编译器可以进行一些优化,这些优化很可能比程序员猜测的更有效。除非你有时间进行基准测试和实验,并且你的应用程序对性能至关重要,否则将优化留给编译器。


0

在堆上分配数组而不是栈的原因:

  1. 数组非常大;
  2. 数组的生命周期超出任何一个函数的范围;
  3. 数组大小在编译时未知,并且可变长度数组在特定情况下不可用或无法使用(例如,可变长度数组不能在静态或文件作用域中声明);
  4. 数组需要调整大小。

0
如果这段代码能够正常运行而不崩溃,那么你可以在堆栈上分配所有的数组。
#include <string.h>
int main() {
    volatile char buf[1024 * 1024 * 64];
    memset(buf, 0, sizeof(buf));
}

640K = 6401024(或者6401000..)现在是“64 MB应该足够每个人使用了”吗? ;) - dyp
DyP:大多数系统只允许 ~1MB 的堆栈。 - Joshua
是的,默认情况下。我刚刚尝试并调整了可执行文件(增加最大堆栈大小),以使您的程序运行 ;) - dyp
我曾经使用过一个没有堆或类似堆分配的环境。虽然我可以动态地调整数组的大小,但我只能在声明它们的函数中进行操作,而不是其子函数中进行操作。 - Joshua

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