动态内存分配
传统内存分配
传统数组的缺点:
- 数组长度必须事先指定,且只能是长整数,不能是变量
例子:
int a[5]; //OK
int len = 5; int a[len]; // error - 传统形式定义的数组,该数组的内存程序员无法手动释放, 在一个函数运行期间系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会释放
- 数组的长度一旦定义,其长度就不能更改,或数组的长度不能在函数运行的过程中动态的扩充或缩小
- A函数定义的数组,在A函数运行期间可以被其它函数使用,但A函数运行完毕之后,A函数中的数组将无法再被其它函数使用。传统方式定义的数组不能跨函数使用
动态内存分配举例——动态数组的构造
malloc的介绍
malloc 是 memory(内存)allocate(分配)的缩写
int main(void)
{
int i = 5; // 分配了4个字节 静态分配 3行
int *p = (int *)malloc(4); // 4行
/*
1. 要使用malloc函数,必须添加malloc.h这个头文件
2. malloc函数只有一个形参,并且形参是整型
3. 4表示请求系统为本程序分配4个字节的空间
4. malloc函数只能返回第一个字节的地址
5. 4行分配了8个字节,p变量占4个字节,p所指向的内存也占4个字节
6. p本身所占的内存是静态分配的,p所指向的内存是动态分配的
*/
*p = 5; // *p代表的就是一个int变量,
//只不过*p这个整型变量的内存分配方式和3行的i变量的分配方式不同
free(p); // free(p)表示把p所指向的内存给释放掉 p本身的内存是静态的,
//不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时由系统自动释放
}
# include <stdio.h>
# include <malloc.h>
void f(int *);
int main(void)
{
int *p = (int *)malloc(sizeof(int)); // sizeof(int) 返回值是int所占的字节数
*p = 10;
printf("%d\n", *p); // 10
f(p);
printf("%d\n", *p); // 200
return 0;
}
void f(int * q)
{
// *p = 200; //error,这是因为在函数f内部不能操作主函数中的变量p
// q = 200; //error,这是因为变量q为指针变量,定义的时候系统会为它分配一块内存,
//q的值就是这个内存的指针,所有必能再修改了
// **q = 200; //error,因为 *q 为 *p 的一份拷贝或者是一个副本,所有 *q 代表的是,
//动态内存的4个字节的值,所有 *q就是一个常量 ** 常量是一个错误的语法
*q = 200; // OK,因为 *q 和 *p 是一样的。
// free(q); // 把q所指向的内存释放掉 ,本语句必须注释掉,
//否则会导致13行代码出错。
}
动态内存分配举例——动态一维数组的构造
定义一个动态的数组,数组的大小由用户输入,元素由用户输入
# include <stdio.h>
# include <malloc.h>
int main(void)
{
int a[5]; // 如果int占4个字节的话,则本数组空包含有20个字节,每个字节被当做了一个int变量来使用
int len;
int * pArr;
int i;
// 动态的构造一维数组
printf("请输入你要存放的元素个数:");
scanf("%d", &len);
pArr = (int *)malloc(sizeof(int)*len);
// 类似于 int pArr[len]; 动态的构造一维数组,数组的名字为pArr,因为它存放了数组的第一个元素
// 本行动态的构造了一个一维数组,该数组的长度为len,该数组的数组名为pArr,该数组的每个元素是int类型
// int * 表示 把返回的第一个字节的地址 当做了 int类型地址,所有元素的类型就为int类型
// 对一维数组进行操作 如:对动态一维数组进行赋值
for (i=0; i<len; i++)
scanf("%d", &pArr[i]);
// 对一维数组进行输出
printf("一维数组的内容是:\n");
for (i=0; i<len; i++)
printf("%d\n", pArr[i]);
// free(pArr); // 释放掉动态分配的数组
printf("%#X\n", pArr); // 输出动态一维数组的首地址,你会发现每次运行此值都不一样
return 0;
}
动态扩充或缩小动态内存的大小
使用realloc函数,realloc(动态数组名,内存大小),假设pArr原来的大小为100字节
- 扩充 realloc(pArr, 150) ,保留原来100个字节,往他后面继续添加50个字节
- 缩小realloc(pArr, 50) ,把数组后面50个字节去掉
动态内存和静态内存的比较
- 静态内存是由系统自动分配,由系统自动释放
- 静态内存是在栈分配的
- 动态内存是由程序员手动分配,手动释放
- 动态内存是在堆分配的
多级指针
解释 int、int *、int **、 int ***;int 表示整型,int * 表示,他是一个指针变量,存放int类型的地址;int ** 表示,它是一个指针变量,存放 int * 类型的地址;int *** 表示,他是一个指针变量,用来存放 int ** 类型的地址。
所以 int * p; int ** q = &p;
修改g函数 p 变量的值
# include <stdio.h>
void f(int **);
void g(void);
int main(void)
{
g();
return 0;
}
void f(int ** q) // 修改g函数 p 变量的值
{
**q = 20;
// *q 就是 p
}
void g(void)
{
int i = 10;
int *p = &i;
printf("%d\n", *p); //10
f(&p); // p是int *类型 , &p 是 int ** 类型
printf("%d\n", *p); // 20
}
跨函数使用内存
静态变量不能跨函数使用
# include <stdio.h>
void f(int **);
int main(void)
{
int *p;
f(&p);
printf("%d\n", *p);
return 0;
}
void f(int ** q)
{
int i = 5;
// *q等价于p q和**q都不等价于p
// *q = i; // error 因为 *q = i; 等价于 p = i;这样写错误
*q = &i; // OK , p = &i
}
要想修改p的值,就必须把p的地址传到函数f里,所有f函数的形参就必须为int ** 类型,则*q = p,所有 *q = &i,推出p = &i,所以 *p = i;这句话看上去是正确的,但是呢,当执行完f函数,函数就会释放掉里面的变量空间,所有此时p指向的变量i就不存在指向了,因为程序运行并不会报错,还会输出5,这让用户很容易参数一种假象, *p = 5 是正确的这种错误想法。
注意:静态变量不能跨函数使用
动态内存可以跨函数使用
# include <stdio.h>
# include <malloc.h>
void f(int **);
int main(void)
{
int *p;
f(&p);
printf("%d\n", *p);
return 0;
}
void f(int ** q)
{
// *q <==> p
*q = (int *)malloc(sizeof(int)); //动态分配了一块内存,大小为sizeof(int) ==> 4字节或8字节,该空间能存放int型的变量
// 把该动态内存的首字节地址存放在 *q中,也就是 主函数的 p中。
** q = 20;
}
在主函数中调用函数f,会申请一块动态内存供p使用,要手动释放