C语言动态内存释放问题?

#include <stdio.h>
#include <malloc.h>
int main(void)
{
	
	int a[5];//占用了20个字节,每四个字节被当成了一个int变量 
	
	int len;
	int * pArr;
	int i;
	 
	
	printf("请输入元素个数:");
	scanf("%d", &len);
	
	//构造动态一维数组 
	pArr = (int *)malloc(4 * len);//pArr是数组名,int *是数组类型 malloc()是数组长度 
	 
	//对动态一维数组进行操作,如:赋值.... 
	for (i=0; i<len; ++i)
		scanf("%d", &pArr[i]);
	
	//对动态一维数组进行输出
	printf("动态一维数组的内容为:\n");
	for (i=0; i<len; ++i)
		printf("%d\n", pArr[i]);
	
	
	
	printf("每个元素的大小为%d\n", sizeof(*pArr));//输出了第一个动态变量的大小 
	free(pArr);
	printf("%d\n", pArr[0]);
	printf("%d\n", pArr[1]);
	printf("%d\n", pArr[2]);
	printf("%d\n", pArr[3]);

这是我学习c语言中动态内存时候的一个问题,就是在free(pArr)之后,我输出的数组元素为什么会有不是乱码的值、

这下边使我测试的输出结果:为什么会有不适乱码的输出呢,释放了内存不是应该清空数据了么,

请输入元素个数:10
0 1 2 3 4 5 6 7 8 9
动态一维数组的内容为:
0
1
2
3
4
5
6
7
8
9
每个元素的大小为4
12517712
0
12517712
0
4
5
6
7
8
9
-1962934133

--------------------------------
Process exited after 8.157 seconds with return value 0
请按任意键继续. . .

各路大神能帮我解答,学生党一枚,自学c可能有点不太容易,希望大神帮忙解决

对于第二个问题可以分为两个子问题来回答。

1) 可以用sizeof获取数组大小吗?

可以也不可以。数组本质上只是连续存储空间在内存中起始点的标识,C语言中数组是可以获取大小的,但数组可以隐式退化为指针,尝试对指针sizeof只会得到指针大小,不能得到指针指向数组的大小。你的程序中,pArr从一开始就是指针而不是数组,那么就是无法通过sizeof获取其所指向数组的大小的。其原因是,sizeof是在编译期执行的运算符,无法提前获知pArr在运行期能指向多大空间(做预测也是不可能的,因为malloc调用有可能失败。如果编译器针对malloc来决定sizeof的值,那在malloc失败时整个程序将陷入严重的正确性问题)。

关键在于:pArr只是指向了一个动态生成的内存空间,这段内存空间被解释为一个数组,可以按数组来访问,但它本质上仍然是一个指针而不是真正的数组。

2) 如何获取数组大小?

对于形如int arr[20];这样的数组,用sizeof(arr)/sizeof(int)显然是足够的。对于malloc获取的伪数组,最好的办法就是用额外变量保存长度信息。诚然CRT在堆上的控制结构里保存了长度信息,但是首先,获取那个值比较麻烦;其次,那个值和你malloc传入的值不见得相同。内存分配时都是按若干字节(通常是2的幂)的整数倍去分配的,而不是按字节。这意味着,如果你试图获取13字节内存,而你的OS以8字节为单位分配,那么实际上这个malloc将会获得16字节而不是13字节。

一句话:最好还是老老实实用一个额外变量存储这个信息。

首先回答为啥free后还能读数据。

malloc和free是由C标准库提供的,这样的标准库并不是编译完就好的,C语言程序引用的外部调用通常以库(library)形式存在,而库分为静态库/归档文件(static library/archive)和动态库/共享目标(dynamic library/shared objects)。具体解释区别有点多,关于这个问题只有两个关键点需要知道:第一,动态库通常出现在有平台差异(换言之,计算机A和计算机B对同一个函数malloc的实现极有可能是不同的)的情况下,依赖于操作系统(OS)的底层实现;第二,经常重复使用的库使用动态库形式比较好,因为静态库可以带来夸张的代码膨胀,影响机器整体性能。C语言需要的运行时库被称作CRT(C Runtime),主要由C标准和操作系统共同约定其内容。

动态库通常并不容易部署,而静态库不需要额外部署,但动态库的这个缺点对CRT而言无关紧要,因为几乎任何现代OS都需要C语言程序,也就都自带CRT,多数时候不需要程序员费心进行额外部署。

接下来是这个问题的关键:CRT对malloc的实现。我先提交一部分免得网络问题白敲哈,稍等。

还有一个小问题就是,我怎么才能一次输出洞内数组所占用的字节大小,用sizeof()可以么?

由于CRT对malloc和free的实现与OS相关,我们得讲点关于CRT怎么分配内存的内容。内存分配算法有很多,我们这里不讨论到具体算法,只需要知道一件事:OS分配内存时,如果每次都要初始化,会造成巨大的性能浪费。实际上这已经足够解答你的问题了:是的,C语言在malloc分配内存时不会初始化,free后数据也不会被清空。重点是,为什么会有这种现象?

CRT在分配malloc获取的内存时,使用了一种称为堆(heap)的数据结构。学过OS你就会知道,OS底层在管理可用内存空间时,经常会把内存分成一段一段的(这里不关心段页式存储,只关心连续物理地址构成的区),而为了进行分配管理,最好把可用区按一定顺序组织起来,堆刚好是一种有序的结构。在经典的x86架构C语言内存模型中,一个程序启动后会获得一定内存(至少是逻辑内存)形成一个进程(process),在进程地址空间中,栈在高地址上,BSS和text段在低地址上,堆被夹在中间。对于堆区指示的每个空闲内存区,CRT会有自己的方法来标记哪个区用过而哪个区空闲,需要分配时,CRT做的事情是按照一定分配算法按顺序查找空闲分区,找个合适的,打个标记,就算分配上了,仅此而已。回收时,CRT将之前打的标记回收掉,就算是这段空间不用了。

另一个重要事实是,C语言中访问数组时,是直接访问的给定地址(实际上要先经过专门的硬件地址形成电路处理后,逻辑地址才会变成物理地址,访存实际上是按物理地址来的。但这里就算不知道这点也不影响),根本不在乎目标地址在堆上还是栈上还是其他什么地方,指令让访问哪它就访问哪,不会做任何检查。一言以蔽之:内存分配和回收是经过了CRT处理的,而访问却没有,因此访问了CRT没有进行分配的区域却没有报错,这实在是非常正常的。而且由于CRT不会清空回收区,如果你强行访问,还是能读出原有数据。这样的操作直到该区域下次被分配前,通常都不会产生什么实际影响。

尽管如此,访问free掉的区域是非常危险的:它带来了安全隐患。你无法控制CRT下次将内存分配到哪里,甚至不排除有时会分配给另一个进程,或者其他什么取决于OS行为的异常状况。这会导致称为段错误(segmentation fault)的情况。

(注:不同OS的行为千差万别,不排除有些OS的栈不是在高地址上,更不排除有些OS会将空闲区给其他进程,而另一些不会。C语言把这种差别全部抽象掉了,使得C程序员不需要在程序中实际考虑这么细节的事情。但这种情况有可能导致ill-behavior在不同平台上具有不同的具体表现。也许在这个平台上没事,换个平台就会挂掉。不过就我所知,“CRT不清空被回收的内存”似乎是不会变的。)

(纠正:严格而言栈是不是在高地址上跟OS关系是次要的,主要跟指令集架构有关。)

至于为什么仍然有少量乱码出现,这我不是很清楚,猜测和CRT的内部实现有关。