为什么要使用动态内存分配?函数的局部变量会进行回收,相比于函数的局部变量,有什么好处呢?

《c和指针》阅读笔记

基础

动态内存分配就是在程序运行的时候去再去申请所需要的内存空间。

malloc 和free

malloc所分配的是一块连续的内存。并没有进行初始化,要么手动初始化,要么用calloc进行初始化。

注意:如果内存池是空的,或者他的可用内存无法满足你的需要,会返回一个NULL指针,因此对每个从malloc返回的指针进行检查,确保它并非NULL非常重要。

malloc又是如何知道你说请求的内存是整数,浮点还是数组呢?它并不知情,它只是返回了一个void *指针。标准表示一个void *类型的指针可以转换为其他任何类型的指针,但是有些编译器,可能要求你在转换时强制类型转换。

calloc和realloc

malloc和calloc的主要区别是后者在返回指向内存的指针之前将它初始化为0。另一个小区别是,它们请求内存数量的方式不同,calloc的参数包括所需元素的数量和每个元素的字节数,根据这个值去计算出总共需要分配多少内存。

realloc用于修改一个原先已经分配的内存块的大小。使用这个函数,你可以是一块内存扩大或者缩小。扩大的话,不会对原先的内容有所改变,缩小的话,剩余部分的内容也依然保留。

如果原先的内存块无法改变大小,会将另外分配一个正确大小的内存,并把原先的内存内容复制到新的块上。因此,使用realloc之后,就不能再使用指向就内存的指针,而是应该改用realloc返回的指针。

如果realloc的第一个参数是NULL,那么和malloc一样。

常见错误

对NULL指针进行解引用操作

对分配的内存进行操作是越过边界

释放并非动态分配的内存

试图释放一块动态分配的内存的一部分

一块动态内存被释放后被继续使用

实例测试

不易发生错误的内存分配器实现

动态内存分配最常见的错误是忘记检查所请求的内存是否成功分配。下面是一种技巧,可以很可靠的进行这个错误检查。不经过直接调用malloc函数,通过自定义的alloc函数调用malloc,并对malloc进行检查。

这个#define malloc指令,是用于防止由于其他代码块直接调用malloc的行为。增加这个指令后,直接调用malloc将会因为语法错误而无法编译。在alloc.v中必须加入#undef,这样它才能调用malloc而不至于出错。

/*
**alloc.h
**定义一个不易发生错误的内存分配器
*/
#include "stdlib.h"
//不要直接调用malloc!
#define malloc
#define MALLOC(num, type) (type *)alloc((num) * sizeof(type))
extern void *alloc(size_t size);
/*
**alloc.c
**不易发生错误的内存分配器的实现
*/
#include "stdio.h"
#include "alloc.h"
#undef malloc

void *alloc(size_t size){
void *new_mem;
/*
** 请求所需的内存
*/
new_mem = malloc(size);
if(new_mem == NULL){
printf("out of memory\n");
exit(1);
}
return new_mem;
}
/*
**a_client.c
**一个使用很少引起错误的内存分配器的程序
*/
void function(){
int *new_memory;
mew_memory = MALLOC(25,int);
}

当动态内存分配的程序失败时,我们很容易将问题责任推给malloc和free函数,其实往往都是出现在了自己的程序中,而且常常是由于访问了分配内存以外的区域而引起的。

当使用free的时候,可能会出现不同的错误,传递给free的指针必须死一个从malloc,calloc,realloc返回的指针。让free函数释放一块不是动态分配的内存可能导致程序立刻终止或者晚些时候终止。

试图释放一块动态内存的一部分也可能会引起类似问题。比如说在程序中对指针进行了修改,如自增自减之后,再去free,就可能会出现问题。

必须小心不要访问已经被free函数释放了的内存。(很容易出现)假设你对一个指向动态分配的内存的指针进行了赋值,而且这个指针的几个拷贝散布于程序各处。你无法保证当你使用其中一个指针的时候它所指向的内存是不是已经被另一个指针释放。另一方面,你必须确保程序中所有使用这块内存的地方在这块内存被释放之前停止对它的使用。

内存分配实例

//invendor.h
#ifndef __INVENDOR_H__
#define __INVENDOR_H__
//定义一个存货记录的声明
typedef struct {
int cost;
int supplier;
}Partinfo;

typedef struct {
char partno[20];
short quan;
}SUBASSYPART;

typedef struct {
int n_parts;
SUBASSYPART *part;
}Subassyinfo;

typedef struct {
char partno[20];
int quan;
enum {PART, SUBASSY}type;
union {
Partinfo *part;
Subassyinfo *subassy;
}info;
}Invrec;

Invrec *create_subassy_record(int n_parts);
void discard_inventory_record(Invrec *record);

#endif // !__DATA_TYPE_H__
/*
**invrecord.c
**用于创建SUBASSEMBLY存货纪录的函数
*/
#include "pch.h"
#include "invendor.h"

//创建存货信息
Invrec *create_subassy_record(int n_parts) {
Invrec *new_rec;
//尝试为Invrec部分分配内存
new_rec = MALLOC(1, Invrec);
new_rec->info.subassy = MALLOC(1, Subassyinfo);
new_rec->info.subassy->part = MALLOC(n_parts, SUBASSYPART);
new_rec->type = SUBASSY;
new_rec->info.subassy->n_parts = n_parts;
return new_rec;
}

//删除存货信息
void discard_inventory_record(Invrec *record) {
switch (record->type) {
case SUBASSY:
free(record->info.subassy->part);
free(record->info.subassy);
break;
case PART:
free(record->info.part);
break;
default:
printf("record type is error");
}
free(record);
}
#include "pch.h"
#include "invendor.h"

int main()
{
Invrec *a;
/*
**注意了,由于动态分配的内存需要释放,如果将这个指针进行了运算
**free的时候就会出问题,另外如果运算了,其他地方进行引用,也容易
**出现找错内存块的问题。
*/
a = create_subassy_record(3);
//将参与运算的指针保护起来。
SUBASSYPART *new_part = a->info.subassy->part;
strcpy(new_part->partno,"hello world");
printf("%p\r\n", new_part);
new_part++;
strcpy(new_part->partno, "this is my");
printf("%p\r\n", new_part);
new_part++;
//注意此处如果拷贝的数据超过数组长度,内存溢出
strcpy(new_part->partno, "first function");
printf("%p\r\n", new_part);
printf("%s", new_part->partno);
discard_inventory_record(a);
printf("ok");
}

出现过的问题:

  • 注意了,由于动态分配的内存需要释放,如果将这个指针进行了运算,free的时候就会出问题,可能只释放了内存的一部分。另外如果运算了,其他地方进行引用,也容易内存块位置偏移的问题。
  • 用strcpy的时候需要注意不能超出规定的内存范围,注意不能越界访问

代码位置https://github.com/xiaoqinxing/myrepo_c