做影视网站侵权不,媒体广告,专门做图的网站,襄阳商城网站建设本篇概要
本篇文章从基本出发讲述为什么要存在动态内存分配#xff0c;动态内存函数有哪些#xff0c;常见的动态内存错误#xff0c;一些关于内存分配的练习题以及柔性数组的相关知识。 文章目录 本篇概要1.为什么存在动态内存分配1.1为什么要动态分配内存1.2内存结构 2.常…本篇概要
本篇文章从基本出发讲述为什么要存在动态内存分配动态内存函数有哪些常见的动态内存错误一些关于内存分配的练习题以及柔性数组的相关知识。 文章目录 本篇概要1.为什么存在动态内存分配1.1为什么要动态分配内存1.2内存结构 2.常用的动态内存函数2.1malloc函数2.2calloc函数2.3calloc函数与malloc的区别 3.其他动态内存函数3.1 realloc函数3.2 free函数 4.常见的动态内存错误4.1 对NULL指针的解引用操作4.2 对动态开辟空间的越界访问4.3 对非动态开辟内存使用free释放4.4 使用free释放一块动态开辟内存的一部分4.5 对同一块动态内存多次释放4.6 动态开辟内存忘记释放内存泄漏 5.几个经典的动态内存笔试题5.1 题目一5.2 题目二5.3 题目三5.4 题目四 6.柔性数组6.1柔性数组的特点6.1柔性数组的使用及优势 1.为什么存在动态内存分配
1.1为什么要动态分配内存
平时我们创建内存如下
int a 20;//在栈空间上开辟四个字节
char arr[10] {0};//在栈空间上开辟10个字节的连续空间但是上述的开辟空间的方式有两个缺点 空间开辟大小是固定的。数组在申明的时候必须指定数组的长度它所需要的内存在编译时分配。 但是对于空间的需求不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了 1.2内存结构 栈区stack在执行函数时函数内局部变量的存储单元都可以在栈上创建函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中效率很高但是 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返 回地址等。堆区heap一般由程序员分配释放 若程序员不释放程序结束时可能由OS回收 。分 配方式类似于链表。数据段静态区static存放全局变量、静态数据。程序结束后由系统释放。代码段存放函数体类成员函数和全局函数的二进制代码。 2.常用的动态内存函数
2.1malloc函数 这个函数向内存申请一块连续可用的空间并返回指向这块空间的指针。 1.如果开辟成功则返回一个指向开辟好空间的指针。 2.如果开辟失败则返回一个NULL指针因此malloc的返回值一定要做检查。 3.返回值的类型是 void* 所以malloc函数并不知道开辟空间的类型具体在使用的时候使用者自己来决定。 4.如果参数 size 为0malloc的行为是标准是未定义的取决于编译器。 使用格式
void* malloc (size_t size);
int* p malloc(10 * sizeof(int));2.2calloc函数 这个函数的功能如下 1.功能是为 num 个大小为 size 的元素开辟一块空间并且把空间的每个字节初始化为0。 2.与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。 使用格式
void* calloc (size_t num, size_t size);
int* p calloc(10, sizeof(int));2.3calloc函数与malloc的区别
** malloc没有初始化malloc函数申请好空间后不会将空间初始化。 **
int main()
{int* p malloc(10 * sizeof(int));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;return 0;
}** calloc有初始化calloc函数申请好空间后会将空间初始化为0。 **
int main()
{int* p calloc(10, sizeof(int));if (p NULL){perror(calloc);return 1;}int i 0;for (i 0; i 10; i){printf(%d\n, *(p i));}free(p);p NULL;return 0;
}3.其他动态内存函数
3.1 realloc函数 1.realloc函数的出现让动态内存管理更加灵活。 2.有时会我们发现过去申请的空间太小了有时候我们又会觉得申请的空间过大了那为了合理的时候内存我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。 通俗的说也就是刚开始分配的空间不够时可以使用relloc函数重新调整空间。
使用格式
void* realloc (void* ptr, size_t size);
int* ptr (int*)realloc(p, 2000*sizeof(int));1.ptr 是要调整的内存地址 2.size 调整之后新大小 3.返回值为调整之后的内存起始位置。 4.这个函数调整原内存空间大小的基础上还会将原来内存中的数据移动到新的空间。 提示realloc在调整内存空间的是存在两种情况
情况1原有空间之后有足够大的空间 情况1 当是情况1 的时候要扩展内存就直接原有内存之后直接追加空间原来空间的数据不发生变化。 ———————————————————————————————————————————
情况2原有空间之后没有足够大的空间 情况2 当是情况2的时候原有空间之后没有足够多的空间时扩展的方法是在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。 提示 realloc(NULL,40)malloc(40)
3.2 free函数 C语言提供了另外一个函数freefree函数用来释放动态开辟的内存。 1.如果参数 ptr 指向的空间不是动态开辟的那free函数的行为是未定义的。 2.如果参数 ptr 是NULL指针则函数什么事都不做。 使用格式
void free (void* ptr);
free(p);4.常见的动态内存错误
4.1 对NULL指针的解引用操作
int main()
{int *p (int*)malloc(40);//不做返回值判断就可能使用NULL指针解引用*p 20;free(p);return 0;
}不做返回值判断就可能使用NULL指针解引用就会出问题。 所以我们一定要像上面的代码一样去判断是否为空指针 if (p NULL) { perror(“calloc”); return 1; } 4.2 对动态开辟空间的越界访问
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);
}当i是10的时候越界访问 4.3 对非动态开辟内存使用free释放
void test()
{
int a 10;
int *p a;
free(p);
pNULL;
}a是在栈上申请的没有使用malloc和calloc使用free程序会崩溃。 4.4 使用free释放一块动态开辟内存的一部分
int main()
{int* p calloc(10, sizeof(int));if (p NULL){perror(calloc);return 1;}int i 0;for (i 0; i 5; i){*p i;p;}//0 1 2 3 4 0 0 0 0 0 free(p);p NULL;return 0;
}当指针指向第六个元素即4后面的0时突然释放内存程序会崩溃。 释放内存p必须指向起始位置。 4.5 对同一块动态内存多次释放
void test()
{
int *p (int *)malloc(100);
free(p);
free(p);//重复释放
}重复释放程序会崩溃。 但是在第一个free§后面另p为空指针那么后面再free§就不会出现问题。所以我们要养成好习惯释放完内存将其置空。 4.6 动态开辟内存忘记释放内存泄漏
void test()
{
int *p (int *)malloc(100);
if(NULL ! p)
{
*p 20;
}
}
int main()
{
test();
while(1);
}忘记释放不再使用的动态开辟的空间会造成内存泄漏电脑资源被浪费。 5.几个经典的动态内存笔试题
5.1 题目一
请问运行Test 函数会有什么样的结果
void GetMemory(char *p)
{
p (char *)malloc(100);
}
void Test(void)
{
char *str NULL;
GetMemory(str);
strcpy(str, hello world);
printf(str);
}这段代码有两个问题 1.GetMemory函数结束后因为没有free所以开辟的空间还在但是p没了p是个形式参数后面找不到开辟空间的地址了内存泄露。 2.GetMemory(str)后str为一个空指针strcpy(str, “hello world”);是对空指针的解引用。 5.2 题目二
请问运行Test 函数会有什么样的结果
char *GetMemory(void)
{
char p[] hello world;
return p;
}
void Test(void)
{
char *str NULL;
str GetMemory();
printf(str);
}GetMemory中创建了一个数组数组p指向hello world的首元素地址当函数return p后数组中的hello world消失了但是仍然指向那个首元素的地址这时候str是野指针。printf(str);非法访问内存。 5.3 题目三
请问运行Test 函数会有什么样的结果
void GetMemory(char **p, int num)
{
*p (char *)malloc(num);
}
void Test(void)
{
char *str NULL;
GetMemory(str, 100);
strcpy(str, hello);
printf(str);
}这段代码没有free释放内存。 5.4 题目四
请问运行Test 函数会有什么样的结果
void Test(void)
{
char *str (char *) malloc(100);
strcpy(str, hello);
free(str);
if(str ! NULL)
{
strcpy(str, world);
printf(str);
}
}free(str);过后内存释放hello没了但是str依然指向刚才hello的首元素地址这是str不算是NULL因为它依然指向一个地址然后进入if语句时strcpy时就是一个野指针了。非法访问内存。
6.柔性数组 也许你从来没有听说过柔性数组flexible array这个概念但是它确实是存在的。C99中结构体中的最后一个元素允许是未知大小的数组这就叫做『柔性数组』成员。 例如
struct S
{
char c;
int i;
int arr[0];
}这便是一个柔性数组int arr[0];未知大小的数组即柔性数组。
6.1柔性数组的特点 1结构中的柔性数组成员前面必须至少一个其他成员。 2.sizeof 返回的这种结构大小不包括柔性数组的内存。 3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小。 struct S
{char c;int i;int arr[0];//未知大小的数组 - 柔性数组成员
};int main()
{
printf(%d\n,sizeof(struct S));
return 0;
}此代码输出该结构体的大小为8为什么是8呢因为涉及到结构体的内存对齐不熟悉这一部分的人可以看我之前写过的博客 链接: 结构体知识点-1.5是内存对齐 ①从而没有计算arr[0]的大小体现了第二点。 ②这也侧面反映了第一点因为前面如果没有的话结构体大小就为0了会出现问题。 ③至于第三点虽然结构体大小为8字节但是开辟空间会开辟更大的空间比如812个字节后面的12个字节是为arr[0]预留的。 6.1柔性数组的使用及优势
struct S
{char c;//1//3int i;//4int arr[0];//未知大小的数组 - 柔性数组成员
};int main()
{struct S* ps (struct S*)malloc(sizeof(struct S) 20);if (ps NULL){perror(malloc);return 1;}ps-c w;ps-i 100;int i 0;for (i 0; i 5; i){ps-arr[i] i;}//打印for (i 0; i 5; i){printf(%d , ps-arr[i]);}//空间不够了struct S* ptr (struct S*)realloc(ps, sizeof(struct S)40);if (ptr ! NULL){ps ptr;}else{perror(realloc);return 1;}//增容成功后继续使用//释放free(ps);ps NULL;return 0;
}我们可以看出此代码开始后面的20个字节是放arr[]中的5个整形数据要是如果我么还想继续放arr[]中的数据那么我们就可以直接使用realloc进行调整将为arr准备的20个字节增大为40个字节十分方便。 那么柔性数组的优势怎么体现呢我们再来看一段代码
struct S
{char c;int i;int* data;
};int main()
{struct S* ps (struct S*)malloc(sizeof(struct S));if (ps NULL){perror(malloc1);return 1;}ps-c w;ps-i 100;ps-data (int*)malloc(20);if (ps-data NULL){perror(malloc2);return 1;}int i 0;for (i 0; i 5; i){ps-data[i] i;}for (i 0; i 5; i){printf(%d , ps-data[i]);}//空间不够了增容int*ptr (int*)realloc(ps-data, 40);if (ptr NULL){perror(realloc);return 1;}else{ps-data ptr;}//增容成功就使用//...//释放free(ps-data);ps-data NULL;free(ps);ps NULL;return 0;
}上述 代码1 和 代码2 可以完成同样的功能但是 方法1 的实现有两个好处 第一个好处是方便内存释放 如果我们的代码是在一个给别人用的函数中你在里面做了二次内存分配并把整个结构体返回给用户。用户调用free可以释放结构体但是用户并不知道这个结构体内的成员也需要free所以你不能指望用户来发现这个事。所以如果我们把结构体的内存以及其成员要的内存一次性分配好了并返回给用户一个结构体指针用户做一次free就可以把所有的内存也给释放掉。 第二个好处是这样有利于访问速度. 连续的内存有益于提高访问速度也有益于减少内存碎片。其实我个人觉得也没多高了反正你跑不了要用做偏移量的加法来寻址