malloc 与 C 中的数组

malloc vs array in C

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

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

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

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

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

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

当在表达式中使用时,数组衰减为指向其第一个元素的指针(例如,这就是类似以下内容的工作方式):

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

  1. Is in the code snippet above c also a pointer of type char*?

不,不是。它是一个包含十个 char.

的数组

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

  1. Does char c[10] also reserve memory on the heap as malloc does?

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

malloc()所做的按照标准叫做"dynamic memory allocation"。

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

  • 如果它在块范围内(在一对 {} 内),它会创建一个自动存储持续时间的数组。就您的程序而言,当范围退出时,该数组不再存在(例如,如果函数 returns)。
  • 如果它在文件范围内(在函数之外),那么它会创建一个静态存储持续时间的数组。该数组将被创建一次,并一直存在直到程序终止。
  1. Are the ways to allocate memory equivalent?

没有。

char c[3] = "aaa";free(c); returns a runtime error; so it seems I can not free the memory I have allocated with char c[3]. Why is that?

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

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

What was taught is that malloc(10*sizeof(char)) allocates enough bytes on the heap to store 10 characters and returns a pointer to the first byte which can be saved in another variable as follows char *x = malloc(10*sizeof(char)). To free the memory one would use free(x).

"On the heap"是一个实现概念,不是C语言的概念。 C 语言本身并不关心将内存划分为具有不同特性的单独区域,事实上,任何给定的 C 实现实际上都不一定是这样。

即使在介绍性课程中——也许 尤其是 在介绍性课程中——使用 C 语言概念比使用特定实现风格的概念要好。本例中相关的C语言概念是存储时长:

An object has a storage duration that determines its lifetime. There are four storage durations: static, thread, automatic, and allocated.

(C2011, 6.2.4/1)

您的 malloc() 调用分配的对象,(在)您的指针 x 指向的对象具有 "allocated" 持续时间。这意味着它的生命周期一直持续到调用 free() 释放该对象为止。请注意变量 x 和具有自动存储持续时间的指针与 x 最初指向的对象之间的区别,这是一个大小为 10 chars.[=45 的无类型对象=]

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

But there is another way to make a computer to reserve enough memory to store 10 characters i.e. char c[10].

是的,没错。

  1. Is in the code snippet above c also a pointer of type char*?

没有。在该声明的范围内,标识符 c 引用 10 char 的数组。数组和指针之间有着密切的关系,但它们根本不是一回事。这是非常重要的一点,很多新的 C 程序员都被这一点绊倒了,所以我重复一遍:数组和指针不是一回事。但是,详细信息将提供另一个完整的答案,您已经可以在 SO 上多次找到这个答案。

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

  1. Does char c[10] also reserve memory on the heap as malloc does?

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

  1. Are the ways to allocate memory equivalent?

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

  1. char c[3] = "aaa";free(c); returns a runtime error; so it seems I can not free the memory I have allocated with char c[3]. Why is that?

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

[I]f the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.

(C2011, 7.22.3.3/2)

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

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

语法注意事项:

首先,cx的类型不同:x的类型是你所期望的char*,而[=的类型15=] 是 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

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


无论如何,抛开语法上的考虑,让我们看看实际的内存分配:

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

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

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

  2. 如果你说

    char c[10];
    

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

  3. 如果你说

    char c[10];
    

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

    因此,分配和释放与程序的控制流严格相关。

  4. 如果你说

    char c[10];
    

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