C中的malloc vs数组

问题描述 投票:4回答:4

我正在参加哈佛大学的MOOC课程CS50。我的最后一个讲座是关于内存分配和指针(两个对我来说绝对新的概念)。

所教的是malloc(10*sizeof(char))在堆上分配足够的字节来存储10个字符并返回指向第一个字节的指针,该指针可以保存在另一个变量中,如下所示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);返回运行时错误;所以我似乎无法释放我用char c[3]分配的内存。这是为什么?

我真的很感激为那些刚学过指针的人量身定做的答案。

c memory cs50
4个回答
6
投票

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

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

即使在介绍性课程中 - 尤其是在入门课程中 - 使用C语言概念比使用特定实现风格的概念更好。在这种情况下,相关的C语言概念是存储持续时间:

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

(Qazxswpoi)

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

还有(更多),但是你的旅程中的这一点很早就深入研究了标准。尽管如此,我发现这种表征对于解决诸如你提出的问题更有用。

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

是的,这是真的。

  1. 在上面的代码片段中c也是char c[10]类型的指针吗?

在该声明的范围内,标识符char*指的是10个cs的数组。数组和指针之间存在密切关系,但它们完全没有相同之处。这是一个至关重要的观点,许多新的C程序员都会绊倒,所以我再说一遍:数组和指针不是一回事。然而,详细信息将提供一个完整的答案,并且您已经可以在SO上找到几次这样的答案。

换句话说,标识符char指定c的值可以指向的一种东西,但要记住x的(指针)值与它指向的对象不同。

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

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

  1. 分配内存的方法是否相同?

不.c分配一个具有已分配存储持续时间的对象,该程序的生命周期是该程序负责明确管理的。另一个分配具有自动存储持续时间的对象,其生存期由标识符的范围确定。

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

最直接的,是因为c[3]函数的规范明确说明

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

(Qazxswpoi)

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

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


6
投票
  1. 在上面的代码片段中c也是char *类型的指针吗?

不它不是。这是十个C2011, 7.22.3.3/2阵列。

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

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

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

根据标准,free()所做的是“动态内存分配”。

char的行为取决于背景。

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

不。

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

因为char c[10];在传递指向动态分配内存的指针时只定义了行为 - 即由{}free()malloc()calloc()指针(导致realloc()无效)返回。

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


2
投票

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

当在表达式中使用时,数组会衰减到指向其第一个元素的指针(例如,这就像下面的代码一样):

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


2
投票

句法考虑因素:

首先,cand和free()的类型是不同的:c的类型是你期望的x,而x的类型是char*,这是一个由10个字符元素组成的数组。

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

c

将打印

char

在64位机器上。 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 给出了指针sizeof(x)指向的字节数,sizeof(*x)给出了存储十个x元素的完整数组所需的字节数。

那么,为什么我可以在任何可以在C中使用sizeof(c)的地方使用char

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


无论如何,考虑到语法上的考虑因素,让我们看一下实际的内存分配:

  1. c保留一个给定大小的内存块,并且该块在c+3返回的指针上调用malloc()之前一直有效。 这与程序中的控制流无关:一个函数可以将free()的结果返回给它的调用者并让调用者释放它。或者它可以将malloc()的结果传递给其他一些释放它的函数。或者它可以将结果返回给它的调用者,并且调用者将它传递给其他函数以释放它。或者结果可能存储在某些其他内存对象中一段时间​​。等等等等。可能性与在世界各地编写的源代码一样多种多样。 必须强调的是,释放它后使用指针是一个很大的错误。如果你这样做,就C标准而言,粉红色的大象有可能出现并践踏你。作为程序员,您的工作就是确保每个malloc指针都完全免费一次。如果你没有这样做,那么所有的赌注都会被取消。
  2. 如果你说 malloc() 在文件范围内(在函数或malloc()定义之外),您声明了一个全局变量。这个数组将存在于char c[10]; 被调用到你的过程死亡之前(通过从struct返回,或通过执行main())。
  3. 如果你说 main() 在函数中,您声明了一个局部变量。这个数组在执行声明时就存在了,并且在封闭块的末尾(一对括号exit()之间的部分)不再存在。 因此,分配和释放严格地与程序的控制流程相关联。
  4. 如果你说 char c[10]; {}的定义中,您声明了一个成员变量。只要存在封闭对象(char c[10]; ),该数组就会存在。如果封闭对象是全局的,则数组生存期是全局的,如果封闭对象是本地的,则数组生存期是本地的生命周期,如果封闭对象是某个其他对象的成员,则数组的生命周期是另一个对象(这是递归的)。
© www.soinside.com 2019 - 2024. All rights reserved.