做餐饮连锁在哪个网站看,如何做网站联盟营销,中国外发加工网官网,自己怎样成为电商前言
动态内存管理是指在一个程序运行期间动态地分配、释放和管理内存空间的过程。在应用程序中#xff0c;当程序需要使用变量或对象时#xff0c;需要在内存中分配一段空间#xff0c;并在使用完毕后释放该空间#xff0c;以提高程序的效率和性能。本文意在介绍常用动态…前言
动态内存管理是指在一个程序运行期间动态地分配、释放和管理内存空间的过程。在应用程序中当程序需要使用变量或对象时需要在内存中分配一段空间并在使用完毕后释放该空间以提高程序的效率和性能。本文意在介绍常用动态内存函数以及如何使用它们来进行动态内存分配。
本章重点
为什么存在动态内存分配 动态内存函数的介绍 malloc free calloc realloc 常见的动态内存错误 柔性数组
1. 为什么存在动态内存分配
动态内存分配的存在可以带来以下好处
1.灵活性动态内存分配允许程序在运行期间动态地分配和释放内存从而提高了程序的灵活性。这使得程序能够适应不同的输入数据和工作负载并能在需要时分配足够的内存来完成任务。
2.节省内存使用动态内存分配可以避免在程序开始运行时分配过多的内存从而节省内存。当程序不再需要使用某个内存块时可以将其释放使得该内存可以被再次使用。
3.提高性能在某些情况下动态内存分配可以提高程序的性能。例如在操作大型数据集时可以只分配所需的内存块从而减少内存浪费和内存碎片的可能性提高程序的运行效率。
总之动态内存分配允许程序在运行时动态地分配和释放内存从而提高程序的灵活性、节省内存和提高性能。
在接触动态内存之前我们申请内存空间的方式都是这样的
int val 20;//在栈空间上开辟四个字节
char arr[10] {0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点 1. 空间开辟大小是固定的。 2. 数组在申明的时候必须指定数组的长度它所需要的内存在编译时分配。 但是对于空间的需求不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道 那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。
2. 动态内存函数的介绍
1.malloc
c语言提供了一种动态申请内存的函数malloc
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间并返回指向这块空间的指针。 如果开辟成功则返回一个指向开辟好空间的指针。 如果开辟失败则返回一个NULL指针因此malloc的返回值一定要做检查。 返回值的类型是 void* 所以malloc函数并不知道开辟空间的类型具体在使用的时候使用者自己 来决定。 如果参数 size 为0malloc的行为是标准是未定义的取决于编译器。 2.free
C语言提供了另外一个函数free专门是用来做动态内存的释放和回收的函数原型如下
void free (void* ptr);
free函数用来释放动态开辟的内存。 如果参数 ptr 指向的空间不是动态开辟的那free函数的行为是未定义的。 如果参数 ptr 是NULL指针则函数什么事都不做。 malloc和free都声明在 stdlib.h 头文件中。
下面是申请动态内存的示例
#include stdio.h
int main()
{
//代码1
int num 0;
scanf(%d, num);
int arr[num] {0};
//代码2
int* ptr NULL;
ptr (int*)malloc(num*sizeof(int));
if(NULL ! ptr)//判断ptr指针是否为空
{
int i 0;
for(i0; inum; i)
{
*(ptri) 0
}
}
free(ptr);//释放ptr所指向的动态内存
ptr NULL;//是否有必要
return 0;
} 注意每次free完后指向动态内存空间的指针必须置空当内存释放完后指针变量仍然存在此时会指向一个未知的地址不置空的话就成为了野指针如果后续在进行调用的话是非常危险的所以一定要置空。 3.calloc
C语言还提供了一个函数叫 calloc calloc 函数也用来动态内存分配。原型如下
void* calloc (size_t num, size_t size);
函数的功能是为 num 个大小为 size 的元素开辟一块空间并且把空间的每个字节初始化为0。 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
#include stdio.h
#include stdlib.h
int main()
{
int *p (int*)calloc(10, sizeof(int));
if(NULL ! p)
{
//使用空间}
free(p);
p NULL;
return 0;
} 所以如何我们对申请的内存空间的内容要求初始化那么可以很方便的使用calloc函数来完成任务。
4.realloc
realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了有时候我们又会觉得申请的空间过大了那为了合理的时 候内存我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。 函数原型如下
void* realloc (void* ptr, size_t size);
ptr 是要调整的内存地址 size 调整之后新大小 返回值为调整之后的内存起始位置。 这个函数调整原内存空间大小的基础上还会将原来内存中的数据移动到 新 的空间
当对一个空指针使用realloc函数时其效果相当于对该指针使用melloc函数开辟空间。
在调整空间上realloc会有两种情况
情况1 当是情况1 的时候要扩展内存就直接原有内存之后直接追加空间原来空间的数据不发生变化。 情况2 当是情况2 的时候原有空间之后没有足够多的空间时扩展的方法是在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。 由于上述的两种情况realloc函数的使用就要注意一些 3.常见的动态内存错误
1 对NULL指针的解引用操作
对NULL指针的解引用操作是一种未定义行为可能导致程序崩溃或其他未知行为。在C/C中NULL指针是一种特殊的指针其取值为0在进行指针解引用操作时程序会试图访问地址为0的内存这个地址是无效的可能会导致程序崩溃。因此在进行指针解引用操作之前应该先检查指针是否为NULL否则可能会出现难以调试的错误。
void test()
{
int *p (int *)malloc(INT_MAX/4);
*p 20;//如果p的值是NULL就会有问题
free(p);
}
2 对动态开辟空间的越界访问
对动态开辟空间的越界访问也是一种未定义行为可能导致程序崩溃或其他未知行为。当我们使用malloc或new等函数在堆上动态开辟空间时如果我们访问这些内存空间之外的位置就会导致指针指向了非法的内存地址。这个地址的数据可能是其他程序的数据也可能是系统保留的数据这些数据都是不属于我们的程序的可能会导致程序崩溃或者产生其他的不可预测的行为。因此在使用malloc或new等函数动态开辟空间后我们应该尽可能地避免越界访问确保我们只访问我们申请的内存空间。一种避免越界访问的方法是使用数组越界检查工具如ASan或Valgrind等。
void test()
{
int i 0;
int *p (int *)malloc(10*sizeof(int));
if(NULL p)
{
exit(EXIT_FAILURE);
}
for(i0; i10; i)
{
*(pi) i;//当i是10的时候越界访问
}
free(p);
} 3 对非动态开辟内存使用free释放
对非动态开辟内存使用free释放可能会导致程序崩溃或其他未知行为。在C/C中我们可以使用malloc/new等函数在堆上动态开辟内存然后使用free/delete等函数来释放内存。但是对于非动态开辟的内存如全局变量、静态变量、局部变量等我们不能使用free/delete等函数释放内存。
在C/C中对于非动态开辟的内存内存的分配和释放都是由编译器自动完成的。当程序运行结束时编译器会自动释放所有的内存。如果程序中使用了free/delete等函数来释放非动态开辟的内存会导致内存被重复释放从而可能导致程序崩溃或其他未知行为。
因此当我们需要释放内存时只需要释放动态开辟的内存即可对于非动态开辟的内存不要手动进行释放操作。
void test()
{
int a 10;
int *p a;
free(p);//ok?
}4 使用free释放一块动态开辟内存的一部分
使用 free 释放一块动态开辟内存的一部分是不安全的做法。因为 free 函数只能释放整块动态开辟的内存一旦释放了内存的一部分就会导致内存破坏或内存泄漏的问题。
void test()
{
int *p (int *)malloc(100);
p;
free(p);//p不再指向动态内存的起始位置
} 5 对同一块动态内存多次释放
也称作“double free”会导致程序运行时不可预测的行为比如崩溃、内存泄漏、数据损坏等。操作系统在释放一块内存后会将这块内存标记为可用再次释放已经被释放的块会导致操作系统数据结构出现问题。
为了避免这种错误我们需要使用合适的内存管理技术如内存池、智能指针等。此外还需要严格遵守内存分配和释放的规范确保每块动态分配的内存都有对应的释放。
void test()
{
int *p (int *)malloc(100);
free(p);
free(p);//重复释放
}
6 动态开辟内存忘记释放内存泄漏
动态开辟内存后忘记释放会导致内存泄漏即程序运行时分配的内存无法被释放导致系统内存占用不断增加。如果内存泄漏严重系统内存将会耗尽导致程序崩溃或无法运行。
为避免动态开辟内存后忘记释放我们应当严格遵循内存分配和释放的规范确保每块动态分配的内存都有对应的释放。当我们不再需要这块内存时应当使用 free() 函数将其释放。
void test()
{
int *p (int *)malloc(100);
if(NULL ! p)
{
*p 20;
}
}
int main()
{
test();
while(1);
} 4.C/C程序的内存开辟
来看看下面这张图 C/C程序内存分配的几个区域 1. 栈区stack在执行函数时函数内局部变量的存储单元都可以在栈上创建函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中效率很高但是 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返 回地址等。 2. 堆区heap一般由程序员分配释放 若程序员不释放程序结束时可能由OS回收 。分 配方式类似于链表。 3. 数据段静态区static存放全局变量、静态数据。程序结束后由系统释放。 4. 代码段存放函数体类成员函数和全局函数的二进制代码。
5.柔性数组
柔性数组flexible array是C99标准中的一个特性也被称为变长数组variable length arrayVLA。它允许在结构体内部定义一个可以动态调整长度的数组。
在柔性数组出现之前我们需要在结构体中定义一个指针然后再手动分配内存来存储数组。这样做很麻烦而且容易出错。柔性数组的出现极大地简化了这个过程并且使得代码更加清晰和易于维护。
C99 中结构中的最后一个元素允许是未知大小的数组这就叫做『柔性数组』成员
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a; 1 柔性数组的特点
结构中的柔性数组成员前面必须至少一个其他成员。 sizeof 返回的这种结构大小不包括柔性数组的内存。 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小。
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf(%d\n, sizeof(type_a));//输出的是4
2 柔性数组的使用
//代码1
int i 0;
type_a *p (type_a*)malloc(sizeof(type_a)100*sizeof(int));
//业务处理
p-i 100;
for(i0; i100; i)
{
p-a[i] i;
}
free(p);
这样柔性数组成员a相当于获得了100个整型元素的连续空间。
3 柔性数组的优势
//代码2
typedef struct st_type
{int i;int *p_a;
}type_a;
type_a *p (type_a *)malloc(sizeof(type_a));
p-i 100;
p-p_a (int *)malloc(p-i*sizeof(int));
//业务处理
for(i0; i100; i)
{
p-p_a[i] i;
}
//释放空间
free(p-p_a);
p-p_a NULL;
free(p);
p NULL;上述 代码1 和 代码2 可以完成同样的功能但是 代码1 的实现有两个好处 第一个好处是方便内存释放 如果我们的代码是在一个给别人用的函数中你在里面做了二次内存分配并把整个结构体返回给用户。用户调用free可以释放结构体但是用户并不知道这个结构体内的成员也需要free所以你不能指望用户来发现这个事。所以如果我们把结构体的内存以及其成员要的内存一次性分配好了并返回给用户一个结构体指针用户做一次free就可以把所有的内存也给释放掉。 第二个好处是这样有利于访问速度. 连续的内存有益于提高访问速度也有益于减少内存碎片。其实我个人觉得也没多高了反正你跑不了要用做偏移量的加法来寻址。