【问题标题】:malloc vs array in CC中的malloc与数组
【发布时间】:2017-12-23 21:46:47
【问题描述】:

我正在参加哈佛大学的公开在线课程CS50。我上一堂课是关于内存分配和指针(这两个概念对我来说绝对是新的)。

教导的是malloc(10*sizeof(char)) 在堆上分配足够的字节来存储 10 个字符并返回指向第一个字节的指针,该指针可以保存在另一个变量中,如下所示char *x = malloc(10*sizeof(char))。要释放内存,可以使用free(x)

但是还有另一种方法可以让计算机保留足够的内存来存储 10 个字符,即char c[10]

  1. c上面的代码sn-p中也是char*类型的指针吗?
  2. char c[10] 是否也像malloc 那样在堆上保留内存?
  3. 分配内存的方法是否等效?
  4. char c[3] = "aaa";free(c); 返回运行时错误;所以看来我无法释放我用char c[3] 分配的内存。这是为什么呢?

我非常感谢为刚刚了解指针的人量身定制的答案。

【问题讨论】:

  • 请从现在开始对每个帖子提出一个问题。其中所有 4 个都有重复项,但确实很难将问题作为 4 个重复项来结束。
  • 指针只是一个变量,它持有其他东西的地址作为它的值。您通常会想到一个保存立即值的变量,例如int a = 5;,而指针只会保存存储5 的地址,例如int *b = &a;。对于任何动态分配,访问内存块的唯一方法是通过指向起始地址的指针。 (您必须保留一个搬运工到起始地址,以便以后能够free 内存)。在这种情况下,malloc 返回指向第一个字节的指针是必须的。

标签: c memory cs50


【解决方案1】:

所教的是malloc(10*sizeof(char)) 分配了足够的 堆上的字节来存储 10 个字符并返回一个指向 第一个字节可以保存在另一个变量中,如下char *x = malloc(10*sizeof(char))。要释放内存,可以使用free(x)

“在堆上”是一个实现概念,而不是 C 语言概念。 C 语言本身并不关心将内存划分为具有不同特征的单独区域,事实上,任何给定的 C 实现都不一定会这样做。

即使在介绍性课程中——也许尤其是在介绍性课程中——使用 C 语言概念比使用某种实现风格的原生概念更好。本例中相关的 C 语言概念是storage duration

对象具有决定其生命周期的存储持续时间。有四种存储持续时间:静态、线程、自动和分配。

(C2011, 6.2.4/1)

malloc() 调用分配的对象,(in) 指针 x 指向的对象,具有“已分配”持续时间。这意味着它的生命周期一直持续到通过调用free() 释放该对象。请注意这里变量x(具有自动存储持续时间的指针)与x 最初指向的对象(大小为10 chars 的无类型对象)之间的区别。

还有(很多)更多,但您旅程中的这一点对于深入研究标准还为时过早。不过,我发现这种表征对于解决诸如您提出的问题更有用。

但是还有另一种方法可以让计算机保留足够的内存 存储 10 个字符,即char c[10]

是的,没错。

  1. c上面的sn-p代码中是否也是char*类型的指针?

没有。在该声明的范围内,标识符 c 指的是 10 个 chars 的数组。数组和指针之间有着密切的关系,但它们根本不是一回事。这是至关重要的一点,也是许多新的 C 程序员绊倒的一点,所以我重复一遍:数组和指针不是一回事。然而,这些细节将构成一个完全不同的答案,而且你已经可以在 SO 上找到好几次了。

换句话说,标识符c 指定x 的值可以指向的一种事物,但请记住x 的(指针)值与其指向的对象不同.

  1. char c[10] 是否也像 malloc 那样在堆上保留内存?

如果您的 c 声明出现在函数中,则它声明了一个具有自动存储持续时间的数组。这意味着数组的生命周期一直持续到标识符c 超出范围。该数组的存储位置是实现的关注点,但在提供堆/堆栈区分的实现上,存储很可能位于堆栈上,而不是堆上。

  1. 分配内存的方法是否等效?

没有。 malloc() 分配具有已分配存储持续时间的对象,程序负责显式管理其生命周期。另一个分配一个具有自动存储期限的对象,其生命周期由标识符的范围决定。

  1. char c[3] = "aaa";free(c); 返回运行时错误;所以看来我无法释放我用 char c[3] 分配的内存。为什么 是吗?

最直接的,是因为free()函数的规范明确说

[I]如果参数与内存先前返回的指针不匹配 管理功能,或者如果空间已通过调用释放 free 或 realloc,行为未定义。

(C2011, 7.22.3.3/2)

也就是说,如果您尝试释放指向具有自动持续时间的对象的指针,该标准不需要运行时错误(或任何特定的其他行为),但它明确否认您可以通过这种方式释放内存的任何承诺。

但我认为,一个更令人满意的答案是free() 是您如何标记具有分配存储持续时间的对象的生命周期结束,而不是具有自动(或其他)持续时间的对象。对象的存储位置(例如堆栈 vs. 堆)是辅助的。

【讨论】:

    【解决方案2】:
    1. c上面的sn-p代码中是否也是char*类型的指针?

    不,不是。它是一个由十个 char 组成的数组。

    但是,当在需要指针的上下文中使用数组的名称时,可以将其转换为指针,因此可以像指针一样有效地使用。

    1. char c[10] 是否也像 malloc 那样在堆上保留内存?

    没有。堆和堆栈也不是完全准确的术语,但我不会进一步展开。

    malloc() 所做的根据标准称为“动态内存分配”。

    char c[10]; 的行为取决于上下文。

    • 如果它在块范围内(在一对{} 内),它会创建一个自动存储持续时间数组。就您的程序而言,当范围退出时(例如,如果函数返回),该数组将不复存在。
    • 如果它在文件范围内(在函数之外),则它会创建一个静态存储持续时间数组。该数组将被创建一次,并继续存在直到程序终止。
    1. 分配内存的方法是否等效?

    不。

    char c[3] = "aaa";free(c);返回运行时错误;所以看来我无法释放我用 char c[3] 分配的内存。这是为什么呢?

    因为 free() 仅在将指针传递给动态分配的内存时具有定义的行为 - 即由 malloc()calloc()realloc()NULL 指针返回(这导致 free() 什么都不做)。

    c 是一个静态或自动存储持续时间的数组,具体取决于上下文,如上所述。它不是动态分配的,因此将其传递给 free() 会产生未定义的行为。一个常见的症状是运行时错误,但不是唯一可能的症状。

    【讨论】:

      【解决方案3】:

      句法注意事项:

      首先cx的类型是不同的:x的类型是你所期望的char*,而c的类型是char[10],它是一个数组十个字符元素。

      因此,xc 不能完全等价:当您说x 时,编译器只是认为单个char 的单个地址。但是,当您说c 时,编译器会考虑整个数组对象及其所有十个char 元素。因此,代码

      printf("sizeof(x) = %zd\n", sizeof(x));
      printf("sizeof(*x) = %zd\n", sizeof(*x));
      printf("sizeof(c) = %zd\n", sizeof(c));
      

      将打印

      sizeof(x) = 8
      sizeof(*x) = 1
      sizeof(c) = 10
      

      在 64 位机器上。 sizeof(x) 给出了存储地址所需的字节数,sizeof(*x) 给出了指针 x 指向的字节数,sizeof(c) 给出了存储一个完整的十个数组所需的字节数char元素。

      那么,为什么我几乎可以在任何可以在 C 中使用 x 的地方使用 c

      这个技巧称为数组指针衰减:每当您在需要指针的上下文中使用数组时,编译器都会默默地将数组衰减为指向其第一个元素的指针。 C 中只有两个地方可以实际使用数组。第一个是sizeof()(这就是sizeof(x) != sizeof(c)的原因),第二个是地址运算符&在所有其他情况下,c 的任何使用都会调用数组指针衰减。这包括像 c[3] 这样的东西。该表达式被定义为等效于*(c+3),因此编译器将数组c 衰减为指向其第一个元素的指针,然后应用指针算术c+3,然后取消引用结果指针。听起来很复杂,令人难以置信,但具有访问数组第四个元素的预期效果。


      不管怎样,抛开语法上的考虑,让我们看看实际的内存分配:

      1. malloc() 保留给定大小的内存块,并且该块一直有效,直到您在 malloc() 返回的指针上调用 free()

        这与程序中的控制流无关:函数可以将malloc() 的结果返回给它的调用者并让调用者释放它。或者它可能会将malloc() 的结果传递给其他释放它的函数。或者它可能会将结果返回给它的调用者,然后调用者将它传递给其他函数以释放它。或者结果可能会在某个其他内存对象中存储一段时间。等等等等。可能性与世界各地正在编写的源代码一样多种多样。

        必须强调的是,释放指针后使用是一个很大的错误。如果这样做,就C标准而言,可能会出现粉红色的大象并将您踩死。作为程序员,你的工作是确保每个 malloc 的指针都被释放一次。如果你不这样做,那么,所有的赌注都被取消了。

      2. 如果你说

        char c[10];
        

        在文件范围内(函数或struct 定义之外),您正在声明一个全局变量。该数组将在 main() 被调用之前一直存在,直到您的进程死亡(通过从 main() 返回,或通过执行 exit())。

      3. 如果你说

        char c[10];
        

        在一个函数中,你声明了一个局部变量。该数组在其声明执行时开始存在,并在封闭块的末尾(一对大括号 {} 之间的部分)停止存在。

        因此,分配和解除分配与程序的控制流密切相关。

      4. 如果你说

        char c[10];
        

        struct 的定义中,您声明了一个成员变量。只要封闭对象(struct)存在,这个数组就会存在。如果封闭对象是全局的,则数组的生命周期是全局的,如果封闭对象是局部的,则数组的生命周期是局部的,如果封闭对象是其他对象的成员,则数组的生命周期是另一个对象的(这是递归的)。

      【讨论】:

        【解决方案4】:

        "char c[10];"在本地堆栈或加载程序时创建的全局数据区域中保留空间,就程序而言,它是不是堆内存(对于操作系统,它可能不同) .

        当在表达式中使用时,数组会衰减为指向其第一个元素的指针(例如,下面是这样工作的):

        您不能释放使用类型 var[size] 语法声明的数组,因为它要么是当前函数的本地函数,要么是整个程序的全局变量。堆内存不会以这种方式绑定到任何特定的上下文(用于引用堆内存的变量是但不是指向的内存)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-01-22
          • 2015-04-16
          • 1970-01-01
          • 2010-12-16
          • 1970-01-01
          • 1970-01-01
          • 2011-03-17
          • 1970-01-01
          相关资源
          最近更新 更多