malloc
申请大块空间使用malloc
申请小块空间使用内存池
内存池由用户自己进行管理,不用系统进行malloc和free;因为系统进行malloc和free时耗时大,当申请空间过小,额外空间所占就大
因为malloc需要引入头部信息,当申请的空间过小头部信息的比重就大于了数据信息,浪费严重且效率不高
动态内存管理的原因:
- 栈区空间大小不够
- 有限的空间,最大的利用
堆和栈的区别
(一)初识动态内存
Windows 系统下,栈空间(.stack)分配1M
Linux系统下栈空间分配10M(16G内存);
int main()//测试堆区大小
{
int n = 1024 * 1024 * 100;//100M
char* p = nullptr;
int sum = 0;
for (;;)
{
p = (char*)malloc(sizeof(char) * n);
if (p != nullptr)
{
sum += 1;
}
else
{
break;
}
}
printf("%d \n", sum);
return 0;
}
(二)动态内存分配函数
前3个函数都用来申请空间,申请的空间都由free释放
calloc与realloc 底层都有调用malloc
malloc
malloc : 向堆区申请一块指定大小的连续内存空间,用cdcd…填充
- malloc 从堆区申请空间,成功申请并不进行初始化
- size 是用户从堆区申请空间的个数(多少字节)
- malloc申请的是字节的个数;
- 返回值是一个无类型指针,故对其返回值需要强转;
- 内存分配成功返回其申请到的堆区地址,若未成功返回NULL,因此每次申请完必须判空
- 使用完成必须用free来释放,free释放的并不是指针本身,而是将指针指向的堆区空间释放
int main()
{
int n = 0;
int i = 0;
int* ip = nullptr;
scanf_s("%d", &n);
//int ar[n];//如果系统支持C99,可动态开辟数组,但是数组在栈区开辟
//当n超过1M会栈溢出
ip = (int*)malloc(sizeof(int) * n);
if (nullptr == ip)exit(1);
for (int i = 0;i < n;++i)
{
ip[i] = i+10;
}
for (int i = 0;i < n;++i)
{
printf("%2d", ip[i]);
}
free(ip);//此时ip为空悬指针(失效指针)
ip = nullptr;
return 0;
}
malloc申请空间图示:
极端情况malloc(0)
malloc(0)并不意味着返回nullptr
辅助信息所占的比重是100%
int main()
{
int *ip= (int*)malloc(0);
free(ip);
return 0;
}
malloc与指针
改错题:
改进一:引用
void GetString(char*&p, int n)
{
p = (char*)malloc(sizeof(char) * n);
if (p == nullptr) exit(EXIT_FAILURE);
}
int main()
{
int n = 100;
char* cp = NULL;
GetString(cp, n);
strcpy_s(cp, n, "hello tulun");
printf("%s", cp);
free(cp);
return 0;
}
改进二:返回指针
char* GetString(char*p, int n)
{
p = (char*)malloc(sizeof(char) * n);
if (p == nullptr) exit(EXIT_FAILURE);
return p;
}
int main()
{
int n = 100;
char* cp = NULL;
cp = GetString(cp, n);
strcpy_s(cp,n, "hello tulun");
printf("%s \n", cp);
free(cp);
return 0;
}
改进三:二级指针
void GetString(char**p, int n)
{
*p = (char*)malloc(sizeof(char) * n);
if (p == nullptr) exit(EXIT_FAILURE);
}
int main()
{
int n = 100;
char* cp = NULL;
GetString(&cp, n);
strcpy_s(cp,n, "hello tulun");
printf("%s", cp);
free(cp);
return 0;
}
calloc
calloc : 向堆区申请一块指定大小的连续内存空间,用000…填充
int main()
{
int n = 5;
int *ip= (int*)malloc(sizeof(int)*n);
free(ip);
ip = nullptr;
int* is = (int*)calloc(n, sizeof(int));
free(is);
is = nullptr;
return 0;
}
ip指向的空间:
is指向的空间:
memset / memcpy () 在 <string.h> 中
<string.h>等价于《cstring》是C的字符函数库,里面封装有strcmp,strcpy,strlen;memset,memcpy…
《string》是C++的字符串库
自己写一个my_calloc函数模拟calloc:
// 元素个数 每个元素大小
void* my_calloc(size_t num, size_t size)
{
void* vp = malloc(size * num);
if (nullptr != vp)
{
memset(vp, 0, num * size);//将vp指向的空间全部置为0;
}
return vp;
}
realloc
动态内存扩容或者收缩,realloc函数在内存空间足够时直接追加;内存空间不足时,重新选堆空间重新申请到足够内存空间,并且释放原空间;空间不足且无法申请到指定大小空间直接返回NULL,且原空间不会释放。所以写代码时应该避免这种情况发生,避免内存泄露。当使用realloc()收缩空间时一定会成功,所以不需要在意是否会返回NULL,但是收缩空间会造成内存碎片。
- ptr: 指向已经存在的一块动态内存区域,没有被free 过
- new_size: 需要扩张或者收缩到的大小
#include<stdio.h>//标准解析代码
#include<stdlib.h> // _itoa_s malloc free
#include<assert.h>
#include<limits.h>
#include<string.h>//strcat strcpy strlen ; memset memcpy
int main()
{
int n = 5;
int m = 10;
int* p = (int*)malloc(sizeof(int) * n);
if (p == nullptr) exit(EXIT_FAILURE);
for (int i = 0;i < n;i++)
{
p[i] = i + 10;
}
p = (int*)realloc(p, sizeof(int) * m);//在p指向空间的基础上增加到m个整型空间
p = (int*)realloc(p, sizeof(int) * 2);//p指向的空间增加到2个整型空间(收缩)
free(p);
p = nullptr;
return 0;
}
p指向的空间大小为5时:
p指向空间大小为10时:
p指向空间大小为2时:
realloc 函数调整内存空间大小有3种情况:
情况一:
ip指针不变,只需要将下越界标记向下移动,将头部信息修改
情况二:
ip指向新的空间
在内存空间足够大的地方重新开辟m个内存空间,将原本的数据复制进新空间,并将新空间的地址赋值给ip,并且把ip以前指向的旧空间释放
情况三:
内存不足,ip = (int*)realloc(p, sizeof(int) * m);
,以这种情况写的代码会出现内存泄露。因为ip指针被赋值为空,但是ip指向的空间还没有被释放,当用户释放该空间时没有地址指向,所以内存泄露
代码修改避免内存泄露
int* newp = (int*)realloc(p, sizeof(int) * m);//在p指向空间的基础上增加到m个整型空间
if (newp == nullptr)
{
printf("内存不足\n");
exit(EXIT_FAILURE);
}
p = newp;
p= (int*)realloc(p, sizeof(int) * 2);//p指向的空间增加到2个整型空间(收缩)
//收缩空间一定会成功不用在意是否失败返回nullptr
free(p);
p = nullptr;
扩充空间ip为nullptr怎么办?
realloc扩充空间时如果ip为空时,realloc会退化为malloc ,有可能申请空间成功,也有可能申请空间失败
使用realloc()收缩空间
收缩空间一定都是成功的,都是讲下越界标记向上提升到指定位置。缺点就是形成内存碎片
int main()
{
int* ip = nullptr;
int n = 10;
int* newdata = (int*)realloc(ip, sizeof(int) * n);
//等价于:int *newdata=(int*)malloc(sizeof(int)*n);
if (newdata == nullptr)
{
exit(EXIT_FAILURE);
}
ip = newdata;
for (int i = 0;i < n;i++)
{
ip[i] = i + 10;
}
ip = (int*)realloc(ip, sizeof(int) * 2);
free(ip);
ip = nullptr;
return 0;
}
收缩前:
收缩后:
真实的内存碎片
free
free(p):释放时并不是将p释放,而是将p指向的空间从已用状态,变成未使用状态,且只能释放一次,释放之后一定将p=nullptr,防止失效指针
申请成功多少空间,系统必须释放相应大小的空间,不能多,也不能少。
void free(void* ptr)
{
if (nullptr == ptr)return;
}
注意一:
指针不赋nullptr,二次释放
所以free(ip);
一定要:ip=nullptr;
注意二:
指针不赋nullptr,再次申请到同一块内存空间,死都不知道怎么死的
注意三:
malloc时申请了多少空间可以计算出来;但是释放的是指针指向的连续空间,你怎么知道需要释放多少字节
解释一:
- 当malloc时,堆区申请到空间后,指针指向改空间,此外改空间上面有空间大小4字节的上越界标志,改空间下面有空间大小4字节的下越界标志,可以将改指针强转换成char类型,一直往下走,看是否到达下越界标志,这样就可以计算出申请的空间大小
- 本质:每次malloc申请空间成功时,初4字节的上/下越界标志,还要28字节(windows系统下)的头部信息,在这28字节中专门有4字节存放申请成功的空间字节个数,释放改空间是,指针向上迁移32字节(4+28)就可以读出头部信息,从而知道当时申请的空间大小,就意味着释放多少字节大小
内存泄露
错误理解:光malloc 没有free
真正意义上的内存泄漏:
在进行空间申请的时候一定要妥善保管malloc返回的地址;若malloc申请的空间用ip指向,不能轻易改变或者丢失ip的地址。一旦ip不指向该空间就会造成内存泄露。用户层面来说,一旦ip不能指向原本malloc的空间,就无法将申请的空间释放掉。内存泄漏的本质是丢失了malloc传回的指针
- 拿malloc申请空间,丢失了malloc申请成功返回的地址(一旦丢失该地址,从某种意义上讲当前程序无法做到将该地址释放掉)。这种情况类似于没有释放该指针,又将该指针指向了其他空间。只要发生地址丢失就会产生内存泄漏
此时free只是释放了指向400个字节空间大小的ip,而指向40个字节空间大小的ip释放不了
- 当程序执行的过程中,光malloc,没有free最终会将堆区3G的内存空间申请完了。
(三)易错点:
归纳:
- 1 .动态开辟内存一定要判空
- 2 .空间分配成功返回的地址,不要轻易改变(不要++或者–),因为一旦改变,起始地址就改变了,释放时指针会向上偏移4+28个字节读取头部信息,一旦起始地址改变,释放的空间就全乱套了
- 3 .申请成功多少空间,系统必须释放相应大小的空间,不能多,也不能少。
- 4 .内存泄露
- 5 .malloc(0)并不意味着返回nullptr
- 6.不正确的使用申请到的空间(例如原本申请到5个空间,但是赋值时赋了10个值,导致下越界标记被修改)程序虽然可以正常运行,可以正常打印该空间的值,但是会导致程序结束,释放该空间时程序崩溃。
(四)动态开辟二维数组
二级指针法:
动态开辟二维数组与静态二维数组的区别:
int** Get2Arrary(int row, int col)
{
int** s = (int**)malloc(sizeof(int*) * row);
if (s == nullptr) exit(1);
for (int i = 0;i < row;++i)
{
s[i] = (int*)malloc(sizeof(int) * row);
if (s[i] == nullptr) exit(1);
}
return s;
}
void Init_2Ar(int** s, int row, int col)
{
assert(s != nullptr);
for (int i = 0;i < row;++i)
{
for (int j = 0;j < col;++j)
{
s[i][j] = i + j;
printf("%4d", s[i][j]);
}
}
}
void Free_2Ar(int** s, int row)
{
assert(s != nullptr);
for (int i = 0;i < row;++i)
{
free(s[i]);
}
free(s);
}
int main()
{
int** s = nullptr;
int row, col;
scanf_s("%d %d", &row, &col);
s = Get2Arrary(row, col);
Init_2Ar(s, row, col);
Free_2Ar(s, row);
s == nullptr;
return 0;
}
结构体法:
typedef int ElemType;
struct Array2
{
ElemType* data;
int row;
int col;
};
void Init_Ar(Array2* br, int row, int col)
{
assert(br != nullptr);
br->row = row;
br->col = col;
br->data = (ElemType*)malloc((sizeof(ElemType)) * (br->row) * (br->col));
if (br->data == nullptr) exit(1);
int len = (br->row) * (br->col);
for (int i = 0;i < len;++i)
{
br->data[i] = i + 1;
}
}
ElemType GetItem(const Array2& par, int r, int c)
{
//assert(par != nullptr);引用不用判空
assert(r < par.row&& c < par.col);
return par.data[(r * (par.col)) + c];
}
void SetItem(const Array2& par, int r, int c, ElemType val)
{
assert(r < par.row&& c < par.col);
par.data[(r * (par.col)) + c] = val;
}
void Destory(struct Array2& par)
{
free(par.data);
par.data = nullptr;
par.col = par.row = 0;
}
int main()
{
Array2 ar;
int row, col;
scanf_s("%d %d", &row, &col);
Init_Ar(&ar, row, col);
for (int i = 0;i < row;++i)
{
for (int j = 0;j < col;++j)
{
SetItem(ar, i, j, i + j);
}
}
for (int i = 0;i < row;++i)
{
for (int j = 0;j < col;++j)
{
printf("%4d", GetItem(ar, i, j));
}
printf("\n");
}
printf("\n");
Destory(ar);//不调用销毁函数,内存也不会泄露,当程序整体结束,malloc的空间系统会自动释放
return 0;
}
代码升级:返回值为引用
typedef int ElemType;
struct Array2
{
ElemType* data;
int row;
int col;
};
void Init_Ar(Array2* br, int row, int col)
{
assert(br != nullptr);
br->row = row;
br->col = col;
br->data = (ElemType*)malloc((sizeof(ElemType)) * (br->row) * (br->col));
if (br->data == nullptr) exit(1);
int len = (br->row) * (br->col);
for (int i = 0;i < len;++i)
{
br->data[i] = i + 1;
}
}
ElemType& Item(struct Array2& par, int r, int c)
{
assert(r < par.row&& c < par.col);
return par.data[(r * (par.col)) + c];
}
int main()
{
Array2 ar;
int row, col;
scanf_s("%d %d", &row, &col);
Init_Ar(&ar, row, col);
for (int i = 0;i < row;++i)
{
for (int j = 0;j < col;++j)
{
Item(ar, i, j)= i + j;
}
}
for (int i = 0;i < row;++i)
{
for (int j = 0;j < col;++j)
{
printf("%4d", Item(ar, i, j));
}
printf("\n");
}
printf("\n");
return 0;
}
(五)拓展:动态开辟N维数组
三维数组:
typedef int ElemType;
struct Array3
{
ElemType* data;
int row;
int col;
int high;
};
N维数组:
typedef int ElemType;
struct Array_N
{
ElemType* data;
int* index;//下标
int n;//维度
};