我正在参加哈佛大学的MOOC课程CS50。我的最后一个讲座是关于内存分配和指针(两个对我来说绝对新的概念)。
所教的是malloc(10*sizeof(char))
在堆上分配足够的字节来存储10个字符并返回指向第一个字节的指针,该指针可以保存在另一个变量中,如下所示char *x = malloc(10*sizeof(char))
。为了释放记忆,人们会使用free(x)
。
但还有另一种方法可以让计算机保留足够的内存来存储10个字符,即char c[10]
。
c
上面的代码片段中也是char*
类型的指针?char c[10]
是否也像malloc
那样在堆上保留内存?char c[3] = "aaa";free(c);
返回运行时错误;所以我似乎无法释放我用char c[3]
分配的内存。这是为什么?我真的很感激为那些刚学过指针的人量身定做的答案。
所教的是
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 x
s的无类型对象。
还有(更多),但是你的旅程中的这一点很早就深入研究了标准。尽管如此,我发现这种表征对于解决诸如你提出的问题更有用。
但还有另一种方法可以让计算机保留足够的内存来存储10个字符,即
char
。
是的,这是真的。
- 在上面的代码片段中c也是
char c[10]
类型的指针吗?
在该声明的范围内,标识符char*
指的是10个c
s的数组。数组和指针之间存在密切关系,但它们完全没有相同之处。这是一个至关重要的观点,许多新的C程序员都会绊倒,所以我再说一遍:数组和指针不是一回事。然而,详细信息将提供一个完整的答案,并且您已经可以在SO上找到几次这样的答案。
换句话说,标识符char
指定c
的值可以指向的一种东西,但要记住x
的(指针)值与它指向的对象不同。
x
是否也像char c[10]
那样在堆上保留内存?
如果你的malloc
声明出现在函数内,那么它声明一个具有自动存储持续时间的数组。这意味着数组的生命周期将持续到标识符c
超出范围。实现的关注点是该阵列的存储位置,但在提供堆/堆栈区别的实现上,存储很可能是堆栈而不是堆。
- 分配内存的方法是否相同?
不.c
分配一个具有已分配存储持续时间的对象,该程序的生命周期是该程序负责明确管理的。另一个分配具有自动存储持续时间的对象,其生存期由标识符的范围确定。
malloc()
返回运行时错误;所以我似乎无法释放我用charchar c[3] = "aaa";free(c);
分配的内存。这是为什么?
最直接的,是因为c[3]
函数的规范明确说明
[I]如果参数与先前由内存管理函数返回的指针不匹配,或者如果通过调用free或realloc释放了空间,则行为未定义。
(Qazxswpoi)
也就是说,如果您尝试释放指向具有自动持续时间的对象的指针,则该标准不需要运行时错误(或任何特定的其他行为),但它明确否认您可以通过这种方式释放内存的任何承诺。
但是,我认为更令人满意的答案是,free()
是如何标记具有分配存储持续时间的对象的生命周期的结束,而不是具有自动(或其他)持续时间的对象。对象的存储位置(例如堆栈与堆)是辅助的。
- 在上面的代码片段中c也是char *类型的指针吗?
不它不是。这是十个C2011, 7.22.3.3/2阵列。
但是,当在期望指针的上下文中使用时,数组的名称可以转换为指针,因此有效地使用它就好像它是指针一样。
- char c [10]是否也像malloc一样在堆上保留内存?
不会。堆和堆栈也不是完全准确的术语,但我不会进一步扩展。
根据标准,free()
所做的是“动态内存分配”。
char
的行为取决于背景。
malloc()
内),它会创建一个自动存储持续时间的数组。就程序而言,当退出范围时(例如,如果函数返回),该数组就不再存在。
- 分配内存的方法是否相同?
不。
char c [3] =“aaa”; free(c);返回运行时错误;所以我似乎无法释放我用char c分配的内存[3]。这是为什么?
因为char c[10];
在传递指向动态分配内存的指针时只定义了行为 - 即由{}
,free()
或malloc()
或calloc()
指针(导致realloc()
无效)返回。
NULL
是静态或自动存储持续时间的数组,具体取决于上下文,如上所述。它不是动态分配的,因此将其传递给free()
会给出未定义的行为。常见的症状是运行时错误,但不是唯一可能的症状。
“char c [10];”在本地堆栈或加载程序时创建的全局数据区域中保留空间,就程序而言,它不是堆内存(对于OS可能不同)。
当在表达式中使用时,数组会衰减到指向其第一个元素的指针(例如,这就像下面的代码一样):
您不能释放使用类型var [size]语法声明的数组,因为它既可以是当前函数的本地函数,也可以是整个程序的全局函数。堆内存不以这种方式绑定到任何特定上下文(用于引用堆内存的变量但不是指向内存的变量)。
句法考虑因素:
首先,c
and和free()
的类型是不同的:c
的类型是你期望的x
,而x
的类型是char*
,这是一个由10个字符元素组成的数组。
因此,c
和char[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)
,然后取消引用生成的指针。听起来很复杂,令人难以置信,但具有访问数组第四个元素所需的效果。
无论如何,考虑到语法上的考虑因素,让我们看一下实际的内存分配:
c
保留一个给定大小的内存块,并且该块在c+3
返回的指针上调用malloc()
之前一直有效。
这与程序中的控制流无关:一个函数可以将free()
的结果返回给它的调用者并让调用者释放它。或者它可以将malloc()
的结果传递给其他一些释放它的函数。或者它可以将结果返回给它的调用者,并且调用者将它传递给其他函数以释放它。或者结果可能存储在某些其他内存对象中一段时间。等等等等。可能性与在世界各地编写的源代码一样多种多样。
必须强调的是,释放它后使用指针是一个很大的错误。如果你这样做,就C标准而言,粉红色的大象有可能出现并践踏你。作为程序员,您的工作就是确保每个malloc指针都完全免费一次。如果你没有这样做,那么所有的赌注都会被取消。malloc()
在文件范围内(在函数或malloc()
定义之外),您声明了一个全局变量。这个数组将存在于char c[10];
被调用到你的过程死亡之前(通过从struct
返回,或通过执行main()
)。main()
在函数中,您声明了一个局部变量。这个数组在执行声明时就存在了,并且在封闭块的末尾(一对括号exit()
之间的部分)不再存在。
因此,分配和释放严格地与程序的控制流程相关联。char c[10];
在{}
的定义中,您声明了一个成员变量。只要存在封闭对象(char c[10];
),该数组就会存在。如果封闭对象是全局的,则数组生存期是全局的,如果封闭对象是本地的,则数组生存期是本地的生命周期,如果封闭对象是某个其他对象的成员,则数组的生命周期是另一个对象(这是递归的)。