【问题标题】:Why would you ever want to have an array on the heap?为什么要在堆上拥有一个数组?
【发布时间】:2013-11-16 19:34:50
【问题描述】:

为什么要在堆上拥有一个数组?我的教授给了我们两个理由:

  1. 将数组传递给函数,而不是传递副本
  2. 使数组比作用域更长

这些都不能通过以下方式解决:

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

谁能给我一个例子,说明堆中的数组必须在哪里使用?

【问题讨论】:

  • “数组的值而不是数组本身”是什么意思?您通过将指针传递给数组的第一个元素来传递数组。指针必须指向 to 某物,如果在堆栈展开时该 某物 消失了,则您还没有找到一种方法让“数组 [超过] 范围"。
  • @JensGustedt 我在上面列出了我的原因....
  • @JoshuaTaylor 使用复制承包商返回数组。
  • 在堆栈上分配大数组不是一个好主意。
  • 栈小,堆大。

标签: c arrays memory dynamic


【解决方案1】:

C 中的数组表示为引用数组数据位置的指针(它指向数组中的第一项)。对于基于堆栈的数组,数组指针和数据位于同一位置。在堆分配数组的情况下,数组指针位于堆栈上,并指向堆上数组数据开始的位置。

对于第 (2) 点,您不能返回数组的值。相反,返回的是数组在内存或堆栈中的位置。因此,将其分配到堆上可确保在从函数返回数组时保留数据。

另一方面,std::vector 在功能上类似于数组。这样,数组数据分配在堆上,但管理数组的对象在堆栈上。因此,数组的生命周期由向量对象的生命周期控制。

std::vector 具有您描述的行为:

  1. 将向量按值传递给函数会导致数据在传递给函数时被复制;

  2. 矢量数据仅在函数的生命周期内存在。

从函数中传递向量会导致数组数据被复制。但是,这可以使用返回值优化和 R 值引用等方法进行优化,从而避免复制。

【讨论】:

  • 提及这条规则的例外情况可能是一个好方法:首先,数组的声明给你一个数组,而指针的声明给你一个指针——它们不可互换。发生的情况是数组名称衰减为指向表达式中第一个元素的指针,除非: a) 它是 sizeof 的操作数; b) 您使用& 运算符获取其地址; c) 它是一个文字字符串初始值设定项。当然,这些例外适用于数组名称。如果您在一个函数中并将其作为参数接收,则您有一个指针,而不是一个数组。
  • “C 中的数组表示为引用数组数据位置的指针(它指向数组中的第一项)” - 这是不正确的。没有为指向数组的单独指针分配存储空间。分配的唯一存储空间是数组元素本身;指针值是从数组表达式中推断
【解决方案2】:

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

  1. 数组非常大;
  2. 数组的生命周期超出任何一个函数的范围;
  3. 数组大小在编译时未知, VLA 要么不可用,要么不能在特定情况下使用(例如,VLA 不能声明为static 或在文件范围内) ;
  4. 该数组可以调整大小。

【讨论】:

    【解决方案3】:

    堆中的数组用于延长函数的作用域。仅当您不想稍后在前一个(上层)调用者中使用它时,将指针传递到堆栈上的数组才有效。而且函数不能返回数组,可以返回指向数组的指针,但是如果分配在栈中,函数返回后会指向无效的内存位置。

    第一个原因是错误的:数组永远不会通过副本传递。当您调用函数时,数组名称总是衰减为指向其第一个元素的指针,正是为了避免复制整个数组。如果要通过副本传递数组,则必须将其嵌入到 struct 中,并改为传递 struct

    如果您事先不知道数组的大小,动态数组分配也很有用(尽管在 C99 引入可变长度数组之后这不是真的 - 但是,可变长度数组分配在堆栈上,所以您会有同样的问题)。

    使用堆分配的另一个很好的理由是,对于非常大的数组,您很容易耗尽堆栈内存。堆通常更大。

    【讨论】:

      【解决方案4】:
      #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 关键字从何而来?该问题用c 标记。
      【解决方案5】:

      除非您需要让数组超出声明和初始化它的函数的作用域,否则编译器可以进行一些优化,这些优化很可能最终会比程序员可以猜到的效率更高。除非您有时间进行基准测试和试验并且您的应用程序对性能至关重要,否则请将优化留给编译器。

      【讨论】:

        【解决方案6】:

        如果此代码运行时没有崩溃,您可以在堆栈上分配所有数组。

        #include <string.h>
        int main() {
            volatile char buf[1024 * 1024 * 64];
            memset(buf, 0, sizeof(buf));
        }
        

        【讨论】:

        • 640K = 640*1024(或 640*1000..)还是现在“64 MB 应该对每个人都足够了”? ;)
        • DyP:大多数系统只允许大约 1MB 的堆栈。
        • 是的,默认情况下。我刚刚尝试并调整了可执行文件(增加最大堆栈大小)以实际使您的程序运行;)
        • 我曾经使用过一个没有堆或类似堆分配的环境。虽然我可以动态调整数组的大小,但我只能从声明它们的函数而不是它的子函数中这样做。
        猜你喜欢
        • 1970-01-01
        • 2020-04-24
        • 2020-10-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多