目录
1、当前我们知道的内存使用方式
1.创建一个变量
int a = 10; //局部变量 - 栈区
int g_a = 10; //全局变量 - 静态区
栈区:局部变量、函数的形参、返回数据、返回地址等(临时)
自动被释放
堆区:动态内存分配
一般由程序员分配释放
静态区:全局变量、静态变量(static)
由系统释放
2.创建一个数组
局部范围内的数组在栈区
全局范围内的数组在静态区
2、为什么存在动态开辟空间
对于空间的需求,不仅仅是上述的情况。
有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了。
这时候就只能试试动态存开辟了。
动态开辟空间在堆区。
3、malloc和free
1.C语言提供了一个动态内存开辟的函数:void* malloc (size_t size);
void* - 由用户来决定返回的是什么类型
size_t - 无符号整型
size - 开辟多少个字节
举例:
int* p = (int*)malooc( 10*sizef(int) );//10个整型的字节大小
malooc函数会返回一个指向开辟好空间的指针,或者是NULL指针,因此malloc的返回值一定要做检查。
2.C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的。
void free (void* ptr);
void* ptr - 开辟空间的数据块的首地址
注意:free之后,ptr的地址还在,我们要把地址ptr赋值为空指针,以避免适应使用ptr找到这个地址。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
4、calloc函数
1.calloc 函数也用来动态内存分配:void* calloc (size_t num, size_t size);
num - 元素的个数
size - 元素的长度(字节)
举例:
int *p = (int*)calloc(10, sizeof(int));
与函数 malloc 的区别:只在于 calloc 会在返回地址之前,把申请的空间的每个字节初始化为全0。
5、relloc函数
1. realloc 函数就可以做到对动态开辟内存大小的调整:void* realloc (void* ptr, size_t size);
ptr - 要调整的内存地址
size - 调整之后新大小
返回值 - 调整之后的内存起始位置
举例:
ptr = (int*)realloc(ptr, 1000);
realloc函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
注意:
realloc在调整内存空间的是存在两种情况:
a.原有空间之后有足够大的空间 - 追加之后,再返回起始地址
b.原有空间之后没有足够大的空间 - 一次性开辟一片足够的空间,然后将旧的数据拿下来,放在新的开辟的空间。在这种情况下,ptr的地址将发生改变。还可能将原数据弄丢。所以要用一个新的变量来接收返回值。
if(p != NULL)
{
ptr = p;
}
先使用malloc函数,后使用realloc函数。
先释放realloc函数开辟的空间,后释放malloc函数开辟的空间,
举例:
int main()
{
int *ptr = (int*)malloc(100);
if(ptr != NULL)
{
//业务处理
}
else
{
exit(EXIT_FAILURE);
}
//扩展容量
//代码1
ptr = (int*)realloc(ptr, 1000);//可能导致数据丢失
//代码2
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
}
6、动态内存开辟的常见错误
1.对NULL指针的解引用的操作
如果在开辟空间的时候,没有开辟成功,这时在该内存存储数据的时候,就会产生NULL指针,导致非法访问内存空间。
2.对动态开辟空间越界访问
如果开辟空间小于我们的数据要存储的空间,会导致越界访问。
3.使用free释放非动态开辟空间
使用free释放非动态开辟空间后,程序会卡死。
4.使用free释放动态内存中的一部分
指向动内存空间起始地址发生了变化,使在释放的时候,只释放了一部分。并且有一个巨大的风险,起始位置找不到之后,可能永远也找不到这块空间了。
5.对同一块动态内存空间多次释放
也会卡死。
6.动态开辟空间忘记释放
忘记释放后,想释放的时候已经找不到了,导致内存泄漏。
注意:
动态开辟的空间,两种回收方式:
1.主动free
2.程序结束
切记: 动态开辟的空间一定要释放,并且正确释放 。
7、C/C++程序的内存开辟
图源比特
栈区:局部变量、函数的形参、返回数据、返回地址等(临时)
自动被释放
堆区:动态内存分配
一般由程序员分配释放
静态区:全局变量、静态变量(static)
由系统释放
8、柔性数组
1、什么是柔性数组?
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
举例:
typedef struct st_type
{
int i;
int a[0];//大小未知 - 柔性数组成员
//有些编译器会报错无法编译可以改成:int a[];
}type_a;
2、特点
1.结构中的柔性数组成员前面必须至少一个其他成员。
2.sizeof 返回的这种结构大小不包括柔性数组的内存。
举例:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4
3、包含柔性数组成员的结构,用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
举例:
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
使用malloc函数的次数多了,会影响效率,并且增加内存碎片。
4、优势
方便内存释放
连续的内存有益于提高访问速度,也有益于减少内存碎片