目录
1.为什么要存在动态内存分配
int val = 20;//在栈空间开辟4字节
char arr[20] = {0};//在栈空间开辟20字节
上述开辟空间的方式的特点为:
1.开辟的空间大小是固定的.
2.数组在声明时必须指定数组长度,它所需要的内存在编译时分配.
但是在实际应用中我们往往不知道我们需要多大的空间才足够我们使用,太大导致空间浪费,太小不够使用.这个时候只能试试动态内存开辟了.
首先我们需要知道动态内存管理都是在堆区开辟空间的如下图
2.动态内存函数的介绍
2.1malloc和free
函数名: malloc
功 能: 动态开辟内存
用 法: void* malloc(size_t size);
返回值:程序例:
#include <stdio.h>
#include <stdlib.h>int main()
{
int* p = (int*)malloc(40);
//可以先判断p是否为NULL,空间开辟失败会开辟空指针
if (p == NULL)
{
perror("malloc");//打印错误信息
return 1;
}
//使用
free(p);//使用完指针后释放开辟的内存并把该指针置为NULL
p = NULL;return(0);
}
函数名: free
功 能: 释放动态开辟的内存
用 法: void free(void* ptr);
返回值: 无返回值如果参数ptf为NULL,该函数什么都不做
程序例:
#include <stdio.h>
#include <stdlib.h>int main()
{
int* p = (int*)malloc(40);
//使用
free(p);//使用完指针后释放开辟的内存并把该指针置为NULL
p = NULL;return(0);
}
2.2calloc
函数名: calloc
功 能: 动态开辟内存
用 法: void* calloc(size_t num, size_t size);
返回值: 和malloc函数一样和malloc函数唯一的区别是calloc函数会把开辟的空间初始化为0.
程序例:
#include <stdio.h>
#include <stdlib.h>int main()
{
int* p = (int*)calloc(10, sizeof(int));//开辟10个整型int的空间
//可以先判断p是否为NULL,空间开辟失败会开辟空指针
if (p == NULL)
{
perror("calloc");
return 1;
}
//使用
free(p);//使用完指针后释放开辟的内存并把该指针置为NULL
p = NULL;return(0);
}
2.3realloc
函数名: realloc
功 能: 调整已动态开辟的内存的大小
用 法: void* realloc(void* ptr, size_t size);
返回值: 和malloc函数一样程序例:
#include <stdio.h>
#include <stdlib.h>int main()
{
int* p = (int*)calloc(10, sizeof(int));//开辟10个整型int的空间
//可以先判断p是否为NULL,空间开辟失败会开辟空指针
if (p == NULL)
{
perror("calloc");
return 1;
}
//使用
int* ptr = (int*)realloc(p, 80);
if (ptr == NULL)
{
perror("realloc");
return 1;
}
p = ptr;
//扩容成功开始使用
//不再使用,就释放
free(p);//使用完指针后释放开辟的内存并把该指针置为NULL
p = NULL;return 0;
}
3.常见动态内存错误
3.1对NULL指针的解引用操作
例如:
void test()
{
int* p = (int*)malloc(INT_MAX);
*p = 20;
//解决方法,开辟动态空间后对返回的指针
//if (p == NULL)
//{
// perro("malloc");
// return;
//}
free(p);
}
3.2对动态开辟的空间的越界访问
例如:
#include <stdio.h>
#include <stdlib.h>int main()
{
int* p = (int*)calloc(10, sizeof(int));//开辟10个整型int的空间
//可以先判断p是否为NULL,空间开辟失败会开辟空指针
if (p == NULL)
{
perror("calloc");
return 1;
}
//使用
//越界访问了(解决方法自己对内存边界检查)
int i = 0;
for (i = 0; i <= 25; i++)
{
*(p + i) = i;
}free(p);//使用完指针后释放开辟的内存并把该指针置为NULL
p = NULL;return 0;
}
3.3对非动态开辟内存使用free释放
例如:
#include <stdio.h>
#include <stdlib.h>int main()
{
int a = 0;
int* p = &a;
//......
free(p);
p = NULL;return 0;
}
3.4使用free释放一部分动态开辟内存的一部分
例如:
#include <stdio.h>
#include <stdlib.h>int main()
{
int* p = (int*)calloc(25, sizeof(int));//开辟10个整型int的空间
//可以先判断p是否为NULL,空间开辟失败会开辟空指针
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*p = i;
p++;
}
free(p);//指针p的位置移动了只释放了部分动态开辟的内存空间
p = NULL;return 0;
}
3.5对同一块动态内存多次释放
#include <stdio.h>
#include <stdlib.h>int main()
{
int* p = (int*)calloc(10, sizeof(int));//开辟10个整型int的空间
//可以先判断p是否为NULL,空间开辟失败会开辟空指针
if (p == NULL)
{
perror("calloc");
return 1;
}free(p);
//......free(p);//err
return 0;
}
3.6动态内存忘记释放(内存泄漏)
#include <stdio.h>
#include <stdlib.h>int main()
{
int* p = (int*)calloc(10, sizeof(int));//开辟10个整型int的空间
//可以先判断p是否为NULL,空间开辟失败会开辟空指针
if (p == NULL)
{
perror("calloc");
return 1;
}return 0;
}
4.C/C++程序内存开辟
C/C++程序内存分配的几个区域:
栈段(stack):存放函数调用相关的参数、局部变量的值,以及在任务切换的上下文信息。栈区是由操作系统分配和管理的区域。
堆段(heap): 动态内存分配的区域,也就是malloc申请的内存区,使用free()函数来释放内存,堆的申请释放工作由程序员控制,容易产生内存泄漏。
数据段(data):用来存放显式初始化的全局变量或者静态(全局)变量,常量数据。
代码段(text):就是C程序编译后的机器指令,也就是我们常见的汇编代码。
5.柔性数组
C99中结构中最后一个元素允许是未知大小的数组 ,这个数组就是柔性数组。 但 结构中的柔性数组前面必须至少一个其他成员 ,柔性数组成员允许结构中包含一个大小可变的数组,sizeof返回的这种结构大小不包括柔性数组的内存。
例如:
struct type_a
{
int i;
int a[0];//柔性数组成员或者int a[]也可以
};
5.1柔性数组的特点:
5.2柔性数组的使用
#include <stdio.h>
#include <stdlib.h>struct s1
{
int num;
int arr[];
};int main()
{
struct s1* ps = (struct s1*)malloc(sizeof(struct s1) + 40);
ps->num = 20;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", ps->arr[i] = i);
}
//扩容
struct s1* ptr = (struct s1*)realloc(ps, sizeof(struct s1) + 80);
if (ptr == NULL)
{
perror("realloc");
return 1;
}
else
{
ps = ptr;
}
//释放
free(ps);
ps = NULL;return 0;
}
5.3柔性数组的优势
代码1
代码2