8.6 动态内存分配
一、传统数组的缺点
- 数组长度必须事先指定,且只能是常整数,不能是变量
- 传统形式定义的数组,该数组的内存程序员无法手动释放。
数组一旦定义,系统为该数组分配的存储空间就会一直存在,除非该数组所在的函数运行结束。在一个函数的运行期间,系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放。 - 数组的长度不能在函数运行的过程中动态的扩充或缩小,数组的长度一旦被定义,其长度就不能再更改。
- A 函数中定义的数组,在A 函数运行期间可以被其他函数使用,但在A函数运行结束后,A函数中的数组将再也无法被其他函数使用。(传统方式定义的数组不能跨函数使用)
void g(int *pArr,int len)
{
pArr[2]=88;
}
void f(void)
{
int a[5]={1,2,3,4,5};
g(a,5);
printf("%d\n",a[2]);
}
int main(void)
{
f();
return 0;
}
//输出 88
在f函数运行期间,f函数中的数组a可以被其他函数所使用,(本例被g函数所使用),但在f函数运行结束后,a数组所占的内存就被系统释放了,所以a数组就不能被其他函数所使用了。
二、为什么需要动态内存分配
动态数组很好的解决了静态数组的4个缺陷。
三、动态内存分配举例——动态数组的构造
(一) malloc 函数
malloc 是 memory (内存) allocate(分配)的缩写
- 静态分配 int i =5; 静态分配了4个字节
- 动态分配
int *p = (int *)malloc(4)
① 要使用malloc这个函数,必须添加malloc.h这个头文件,或者stdlib.h
② malloc函数函数原型
void * malloc(unsigned int size)
它的作用是在内存的动态存储区中分配一个长度为size的连续空间。返回值为所分配区域的第一个字节的地址。
或者说这是一个指针型函数,返回的指针指向该分配域的第一个字节。
注意: void 基类型 表示不指向任何类型的数据,只提供一个纯地址。 所以想让它返回的是一个特定类型数据的地址,需要进行强制类型转换。
② malloc函数只有一个形参,并且形参是整型。
③ 这个形参,本例中实参:4 表示请求系统为本程序分配4个字节。
④ malloc 函数只能返回第一个字节的地址
所以只写malloc(4)不知道这四个字节是如何划分的,加一个强制类型转换,例如:int *p = (int *)malloc(200),我们就可以知道这是向系统请求200字节内存,且它所指向的是int 类型变量的内存,所以是(int *) ,所以我们可以知道指针P所指向的这段内存可以存200/4=50个int 型变量。
int * P = (int *)malloc(4);
/*
这句其实让内存分配了8个字节
4个字节是malloc函数动态分配的
另外4个字节是指针变量P的占的静态分配的内存。
P占4个字节,P所指向的内存也占4个字节
*/
* P = 5; //*P代表一个整型变量,只不过*P这个整型变量的内存分配方式和 int i=5;这种分配方式不同。
free(P);//表示把P所指向的内存给释放掉,P本身所占的4个字节是静态的,不能由程序员手动释放,只能在P所在的函数运行终止时,由系统自动释放,free函数没有返回值。
(二) 利用 malloc 函数动态构造一维数组
# include <stdio.h>
# include <stdlib.h> //包含了malloc以及realloc函数声明。
int main(void)
{
int a[5]={1,2,3,4,5};//如果int类型占4个字节的话,这句分配了连续的20个字节,每4个字节放一个int型数据
int len;
int *pArr;
int i;
printf("请输入你要存放的元素的个数: \n");
scanf("%d",&len);
pArr = (int *)malloc(4*len);//动态分配内存
//对动态一维数组进行赋值
for (i=0;i<len;++i)
{
*(pArr+i) = i+1;
}
//对数组进行输出
printf("动态构造的一维数组的内容是:\n");
for (i=0;i<len;++i)
{
printf("%-5d",pArr[i]);
}
//对动态一维数组进行扩充
realloc(pArr,100);
/*
函数原型是 void * realloc(void *p , unsigned int size)
其功能是把P指向的动态内存改成size 个字节,如果操作不成功给,则会返回一个空指针。
本例中:
数字100表示扩充后的字节数
由50个字节扩充到100,保留原来的50个字节的信息,后50个字节为空
由100字节缩到50字节,原有的后50个字节的信息丢失。
*/
free(pArr);//把动态分配的数组手动释放,程序可以继续进行。
//而静态分配的a数组只能在程序运行后系统释放。
return 0;
}
pArr 存放的是这20个字节的首地址,但由于pArr是int * 类型,所以pArr指向的是前4个字节。
如果pArr指向100个字节,那么pArr+1 则指向后100个字节。
- 从这里可以看出pArr和数组名类似,所以动态一维数组和静态一维数组的使用效果一样。pArr[2]也和静态数组a[2]效果一样。
- 写 pArr = (int * )malloc(4 * len); 其实和int a[5]效果一样,只不过一个是动态分配的内存,一个是静态分配的内存。
四、静态分配内存和动态内存分配比较
(一) 静态内存
- 静态内存事由系统自动分配的,由系统自动释放
- 静态内存是在栈分配的(栈:见数据结构)
压栈方式,函数一终止就出栈了
(二) 动态内存
- 动态内存是由程序员手动分配的,手动释放
- 动态内存是在堆分配的
五、跨函数使用内存的问题
(一) 静态变量不能跨函数使用详解
# include <stdio.h>
void f(int **q)
{
int i=5;
*q=&i; // *q等价于p;
}
int main(void)
{
int *p;
f(&p);
printf("%d\n",*p); //这句话可能语法不报错,但是逻辑上有问题
return 0;
}
f函数执行完毕后,变量i的内存就被系统释放了,经过f()的运行,p指向了i,*p相当于i,然而主函数却想读 *p 的内容。 这个操作相当于查看一个上了锁房间里的东西,本身就是“违法”行为,原来的i 的内存已经归还给房东了。
(二) 动态内存可以跨函数使用
# include <stdio.h>
# include <stdlib.h>
void f(int ** q)
{
*q = (int *)malloc(sizeof(int));
//等价于 p = (int *)malloc(sizeof(int))
//q=5;error
//*q=5; //p=5 error
**q=5; //等价于*p = 5
}
int main(void)
{
int *p;
f(&p);
printf("%d\n",*p);//这次就完全正确了
return 0;
}