一,动态内存管理介绍
1.1 为什么要有动态内存分配
我们已经熟悉的内存开辟的方式有
int a= 10; 变量
int arr[10]; 数组
但是上面两种方式有两个缺点:1,空间开辟大小是固定的 2,数组在声明的时候必须指明数组长度,在编译时分配
但是对于空间的需求,不仅仅时上面的情况,有时候我们对内存的需求是会随着程序的依次执行而改变的,于是上面两种方式就不适合了,于是C语言推出了动态开辟的内存管理方式
1.2 malloc和free
void* malloc (size_t size);
void free (void* ptr);
这个函数可以向内存申请一块连续可用的空间,并返回指向这块空间的指针,有以下几点规则:
①开辟成功后,返回一个指向该空间的指针,该指针位void*类型,所以有时候需要强转
②如果开辟失败,返回一个NULL指针,因此malloc的反返回值一定要做检查
③如果参数size为0,malloc的行为标准是未定义的,取决于编译器
④free是专门用来释放和回收动态开辟出来的内存的,如果参数为NULL,那么free什么也不做
#include<stdlib.h>
#include<stdio.h>
//malloc申请到空间后,只会返回这块空间的起始地址,不会初始化空间的内容,而且申请后不会自动释放,需要手动free
void main1()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
//开辟成功
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
free(p);//释放
p = NULL;
}
1.3 calloc
void* calloc (size_t num,size_t size);
该函数功能是为num个大小为size的元素开辟一块空间,并且把该空间的每个字节均初始化为0
void main2()
{
//给我开出10个整形大小的空间
int* p = (int*)calloc(10, sizeof(int));
//int* p = (int*)calloc(INT_MAX, sizeof(int)); INT_MAX太大,会开辟失败
if (p == NULL)
{
perror("calloc");
return 1;
}
//打印数据
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
//释放
free(p);
p = NULL;
}
调试时打开内存窗口查看p的值时,会发现被初始化成0
1.4 realloc
void* realloc (void& ptr,size_t size);
有时候我们发现前面申请的空间太小了或者太大了,为了合理地使用内存,我们一定会对申请的内存的大小做调整,所以realloc这个函数的功能就是这样,让动态内存管理更加灵活
对于realloc也有以下几点规则:
①ptr是要调整的内存的地址,size调整后的大小
②返回值为调整之后的的新内存的起始位置
③这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间里去
realloc在调整内存空间有两种情况:
①原有空间后面有足够大的空 ②原空间后没有足够大空间
如果是情况①,要扩展内存就直接原有内存后直接追加空间,原来空间的数据不发生变化,如果是情况②的时候,会在堆空间上找一块合适大小的连续空间来使用,这样函数返回的是一个新的内存地址
void main3()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
//初始化为1~10
int i = 0;
for (i = 0; i < 10; i++)
{
p[i] = i + 1;
}
//增加一些空间
int* ptr = (int*)realloc(p, 8000);
if (ptr != NULL)
{
p = ptr;
ptr = NULL;
}
else
{
perror("realloc");
return 1;
}
//打印数据
for (i = 0; i < 20; i++)
{
printf("%d\n", p[i]);
}
//释放
free(p);
p = NULL;
}
1.5 常见动态内存错误
1.5.1 对NULL指针的解引用
void test()
{
int* p=(int*)malloc(INT_MAX/4);
*p = 20; //INT_MAX太大了,会开辟失败返回NULL,这时候就会错误
free(p);
}
1.5.2 对动态开辟空间的越界访问
void main4()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
//对动态开辟空间的越界访问
for (i = 0; i < 20; i++)
{
p[i] = i;//越界访问
}
free(p);
p = NULL;
}
1.5.3 对非动态内存使用free释放
void main5()
{
int a = 10;
int* p = &a;
printf("%d\n", *p);
free(p);
p = NULL;
}
1.5.4 对一块内存多次释放
void main6()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return 1;
}
//使用
free(p);
p = NULL;
free(p);
}
1.5.5 忘记释放内存(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
//下面while循环,可以让程序永远不结束
//而对于上面的部分,我申请了100个字节空间然后用p指向它
//但是函数结束后,我没有free掉这块空间
//p是变量,存在栈帧中,函数结束会销毁,但是申请的100字节空间不会销毁
//造成内存泄漏 -- 所以一定要记得释放
}
void main7()
{
test();
while (1);
}
二,关于动态内存的经典题目
题目一
//问:Test函数执行后结果如何
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}
我们按照题目要求执行Test后程序会直接挂掉,不会打印任何数据,因为把str传参传给p,然后给p一个100字节空间,但是p是形参,出了函数后会销毁,p销毁了不关str的事所以str依旧是NULL,对NULL拷贝数据就会报错
而且p销毁后开出的100字节空间没有释放,也会造成内存泄漏
解决办法如下
//void GetMemory(char* p)
void GetMemory(char** p)
{
*p = (char*)malloc(100);
//p = (char*)malloc(100);
//原题目会挂掉,因为p是形参,函数结束后销毁,所以和下面的str没关系,str还是空指针
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);
//GetMemory1(str);
strcpy(str, "hello world");
printf(str);
//原题目没有释放,会出现内存泄漏
free(str);
str = NULL;
}
题目二
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
这题和上面那一题差不多,p被创建,里面有hello world,出函数的时候p传给str,但是hello world不是动态开辟出来的,属于GetMemory函数栈帧里的数据,出函数后会被销毁,也就是p指向的空间被销毁了,于是通过str打印数据的时候会打印“烫烫烫烫”
解决办法如下,将p定义为静态
char* GetMemory(void)
{
static char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
题目三
void GetMemory3(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory3(&str, 100);
strcpy(str, "hello");
printf(str);
}
这题没有什么语法或逻辑错误,单纯的就是忘记释放空间了,在打印str后free(str)并把str赋值为NULL即可
void GetMemory3(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory3(&str, 100);
strcpy(str, "hello");
printf(str);
free(str);
str = NULL;
}
题目四
void Test()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
这题有点棘手,因为free在前面就释放了,所以可能会有人说是提前释放的问题,但是如果是提前释放的原因的话,后面又加了个if判断,逻辑上说不过去
要记住,free做的只是释放空间,但是指向该空间的指针没有被销毁,这个指向已经被销毁的空间的指针就是传说中的野指针,所以free指针后最好把该指针也赋值为NULL,这样才能完美保证内存泄漏和内存安全
void main12()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str); //释放了,但是str不为NULL哦
//题目没有置为空,所以free后尽量把释放的指针变量置为空
str = NULL;
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
三,C/C++的内存开辟
先看以下代码和运行结果:
int d = 200;
void main13()
{
int a = 10;
int b = 20;
static int c = 100;
printf("&a = %p\n", &a);
printf("&b = %p\n", &b);
printf("&c = %p\n", &c);
printf("&d = %p\n", &d);
}
①栈区(stack):在执行函数时,函数内的局部变量都可以在栈上创建,每个函数都有属于自己的函数栈帧,函数结束时这些空间会自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,对于需要频繁申请释放的局部变量非常适合,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量,函数参数,返回数据,返回地址等
②堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束后由OS回收
③数据段(静态区):存放全局变量,静态数据,程序结束后由系统释放
④代码段:存放函数体的二进制代码
说多了没啥用,直接看下面这个图吧
四,柔性数组
C99标准中,规定结构体的最后一个元素允许是未知大小的数组,这个数组叫做柔性数组
struct S
{
int n;
//int arr[];//柔性数组,前面至少有一个成员
int arr[0]; //柔性数组
};
柔性数组顾名思义,是能改变自身大小的数组,它的规则如下:
①结构体中的柔性数组成员前面必须至少有一个其他的成员变量
②sizeof计算结构体大小时不计算柔性数组的大小
③包含柔性数组成员的结构用malloc()函数进内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
struct S
{
int n;
int arr[0];
};
void main14()
{
printf("%d\n", sizeof(struct S));
struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);
//在堆上开44空间,4给n,40给arr
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->n = 100;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i + 1;
}
//空间不够,需要增容
struct S* ptr = realloc(ps, sizeof(struct S) + 60);
if (ptr == NULL)
{
perror("realloc");
return 1;
}
ps = ptr;
ps->n = 15;
for (i = 0; i < 15; i++)
{
printf("%d\n", ps->arr[i]);
}
//释放
free(ps);
ps = NULL;
}
下面是另一种用法
struct S1
{
int n;
int* arr;
};
void main15()
{
struct S1* ps = (struct S1*)malloc(sizeof(struct S1));
if (ps == NULL)
{
perror("malloc->ps");
return 1;
}
ps->n = 100;
ps->arr = (int*)malloc(40);//1 2 3 4 5 6 7 8 9 10
if (ps->arr == NULL)
{
perror("malloc->arr");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i + 1;
}
//调整
int* ptr = (int*)realloc(ps->arr, 60);
if (ptr != NULL)
{
ps->arr = ptr;
}
else
{
perror("realloc");
return 1;
}
//打印
for (i = 0; i < 15; i++)
{
printf("%d\n", ps->arr[i]);
}
//释放
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
}