需要用一个数组来保存用户的输入,但是却不知道用户会输入多少条数据。
(1) 如果设一个太大的数组,则显得浪费内存
(2) 如果设得太小,又怕不够
问题:如何做到恰好够用、又一点不浪费呢?
C/C++里,要求数组长度为常量
int Contact[100]; // 长度必须在代码里固定
// 不能这样
int n = 0;
scanf(“%d”, &n);
int Contact[n]; // 编译错误!!数组长度必须为常量❌
动态内存分配 – malloc/free
系统中存在一个内存管理器(MM, Memory Manager),它负责管理一堆闲置内存。它被设计用于解决此类问题。
MM提供的服务:应用程序可以向MM申请(借出)一块指定大小的内存,用完之后再释放(还回)。
例如:
void* ptr = malloc (1024); // 申请,从MM借出内存
free(ptr); // 释放,还回MM
- malloc函数申请的内存空间要用一个指针类型的变量指向其首地址
- 上面在堆内存开辟了1024个字节
1、malloc函数
// 参数size: 指定要申请的内存空间的大小
// 返回值: void* ,指向这一块内存地址
// (MM不关心你拿这块内存来存储何种数据,所以返回void*)
void* malloc(int size)
应用程序在使用malloc时,要把返回值转换成目标类型。
// 例如,要申请一块空间存放1000个Contact对象,则:
int size = 1000 * sizeof(Contact);
Contact* p = (Contact*) malloc(size);
这块内存和数组没有本质区别,用法完全相同。
2、free函数
// ptr: 先前malloc返回的内存地址
// 返回值: void* ,指向这一块内存地址
void free(void* ptr)
需要先计算需要多少字节的内存空间
// 举例:
char* p = (char*)malloc(8); // 申请8个字节
for(int i=0; i<8; i++)
{
p[i] = i + 1;
}
free(p); // 释放 (只需要释放p,不要被循环误导)
// 举例:
int size = 4 * sizeof(Contact);
Contact* p = (Contact*) malloc (size);
p[0].id = 1;
strcpy(p[0].name, “shaofa”);
free (p);
// 举例:
// 用户自己决定要输入多少条记录
int n = 0;
scanf(“%d”,&n);
// 用户需要多少,就分配多少内存
int size = n * sizeof(Contact);
Contact* p = (Contact*) malloc(size);
// 释放
free(p);
数组举例子:
double * pdoubles[10]; // 定义要一个指针数组
for(int i=0; i<10; i++) {
pdoubles[i] = malloc(sizeof(double));
if(pdoubles[i] == NULL) { // 判断是否成功分配
printf("report malloc error");
}
}
// 释放分配的内存
for(int i=0; i<10; i++) {
if(pdoubles[i] != NULL) {
free(pdoubles[i])
}
}
释放的时候需要注意, 因为在for循环执行之后,p的地址往前移动了10, 所以需要减去10, 然后再释放p,不然会有问题
char *p = (char *) malloc(4);
p[0] = 10;
p[1] = 11;
p[2] = 12;
// 等价
*p = 10
*(p+1) = 11
*(p+2) = 12
// 当销毁时只需要free一次,malloc了几个字节就会free几个字节,和char类型还是int类型无关
free(p);
在一个函数中动态分配的内存,在另一个函数中操作这块内存
void func(int *q) {
*q = 200;
}
void func2() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
printf("%d\n",*p); // 10
func(p);
printf("%d\n",*p); // 200
free(p);
}
关于MM
(1) MM是一个系统级的东西,所有的应用程序都向同一个MM申请内存。
(2) 何为借出?实际上,在内存被借出时,MM只是把它管理的内存标记了一下,表示该段内存已经被占用。比如,它把每一段被占用的内存给记录下来(首地址,长度)
(p0,n0) (p1, n1) (p2, n2) ...
(3) MM非常慷慨:①只要有人malloc
,它都同意借出 ②你不归还,它永远不会主动要求你free
。
(4) MM管理的内存区域称为“堆”Heap
这意味着,用户程序应该自觉得及时free
,以便不耽误别的应用程序的使用。如果有个应用程序不停地malloc
,而不free
,那最终会用光MM的内存。当MM没有更多闲置内存时,malloc
返回NULL
,表示内存已经用完。
再次重申:应用程序在malloc之后,应该尽早free
!
使用原则:需要的时候再申请,不需要的时候立即释放
为何free的时候只需一个首地址呢?为什么不传递长度?
实际上,MM对借出的内存块进行标识
(p0, n0) (p1, n1) (p2, n2) ...
它内部已经保证任意两块内存不会“交叠”,即不会重叠,不会把一块内存同时借给两个应用程序使用。
所以,每块内存的首地址都是不同的,在free
的时候只需要指明首地址即可。
何为“对象”
对象指的一块内存
// a是一个对象,即存放着一个对象的数据
Contact a;
// 确切地说:p指向了一个对象
Contact* p = (Contact*) malloc(sizeof(Contact));
示例:用Citizen表示一个市民,用Car表示一个辆车。他起初没有车,但未来可能有一辆车。
struct Car
{
char maker[32]; // 制造商
int price; // 价格
};
struct Citizen
{
char name[32]; // 名字
int deposite; // 存款
Car* car; // NULL时表示没车
};
// 定义一个对象,开始没车
Citizen shaofa = { “shaofa”, 100, NULL };
// 后来,他可能买了一辆车
void buy(Citizen* owner)
{
// 创建一个对象
Car* car = (Car*) malloc(sizeof(Car));
strcpy(car->maker, “Chevrolet”);
car->price = 10;
// 保存此对象 (确切地说是记住了指针)
owner->car = car; // 有车了
owner->deposite -= car->price; // 钱没了
}
// 终有一天,这车会报废...
void discard(Citizen* who)
{
free(who->car); // 此对象被销毁
who->car = NULL; // 回到无车状态
}
// 也有可能会买给别人
void sell(Citizen* owner, Citizen* other)
{
Car* car = owner->car;
car->price *= 0.5; //半价出售
other->car = car; // 别人拥有了这辆车
owner->deposite += car->price; // 收回一部分成本
//free(car); // oh,no! 不能free,这车在别人手里 ❌
owner->car = NULL; // 回到无车状态
}
注意事项
1、不是malloc的指针,不可以free
例如:
int a = 10;
int* p = a;
free (p ); // 开什么玩笑??这个指针根本不是从MM那里借来的
2、malloc的内存,必须及时free
怎么样才算“及时”? “不及时”会怎样?
MM里可用的内存是有限的,你用完了就得尽快还,因为别的应用程序也需要MM的内存。
只借不还,积累到一定程度,MM没有更多内存可用,于是malloc返回NULL。
while(1)
{
void* ptr = malloc(1024*512);
}
3、要free必须free首地址
错误的例子:
char* p = (char*) malloc(100); // 100个字节
free (p+50); // 借了100, 只还50?
要还就得全还,否则MM那边处理不了
4、malloc的返回值需要检测
char* ptr = (char*) malloc(1024); // 512K
if(ptr != NULL)
{
...
}
原因是:MM可能此时没有闲置内存可用。(虽然这种情况一般不会发生)
5、free之后,该指针不应再使用
free之后,该内存交还给MM,该内存不再可用(失效)
// 以下代码是错误的:
char* p = (char*) malloc(100);
free (p);
for(int i=0; i<100; i++)
{
p[i] = i; //该内存已经被free,绝不可继续使用!
}
// 良好的编程习惯是:
free(p);
p = NULL; // 置为空指针
6、malloc得到的内存,可以任意位置释放
不一定要在相同的函数里释放,在应用程序的任意一个角落释放都是有效的。
也就是说:这一块内存被malloc出来之后,完全交给你处置
内存处理函数
1、memset()
void *memset(void *s, int ch, size_t n);
功能:将s
中当前位置后面的n
个字节 (typedef unsigned int size_t )用 ch
替换并返回 s
参数:
s:需要操作内存s的首地址
ch:填充的字符,ch虽然参数为int,但必须是unsigned char, 范围为 0-255
n:指定需要设置的大小
返回值:s的首地址
例如:
int *p = (int *)malloc(sizeof(int) * 10);
memset(p, 0, 40); // 重置内存空间的值
2、 memcpy()
void *memcpy(void *dest, void *src, unsigned n);
参数:
dest: 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
src: 指向要复制的数据源,类型强制转换为 void* 指针。
n: 要被复制的字节数
返回值
该函数返回一个指向目标存储区dest的指针
3、 memmove()
void *memmove( void* dest, const void* src, size_t n);
功能:由src
所指内存区域复制n
个字节到dest
所指内存区域。
memmove()
功能用法和memcpy())
一样,区别在于:dest
和src
所指的内存空间重叠时,memmove()
仍然能处理,不过执行效率比memcpy()
低一些
网友评论