动态内存分配的由来
我们已经掌握的内存开辟的方式:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
- 上面开辟的空间大小是固定不可变的
- 数组申请的大小也是确定了之后就不能改变
这里就引入了动态内存的开辟,可以根据内存的需要进行更改
动态内存函数的介绍
malloc和free
void* malloc (size_t size);
- malloc的功能是申请size个连续可用size_t类型的字节空间,并返回指向这块空间的void*类型的指针
- 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃⼰来决定。
- 如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。
在稍微了解了malloc后还不能直接上代码,因为动态内存申请后需要进行释放,我们先学习一下free
free
void free(void* ptr);
- free函数⽤来释放动态开辟的内存。
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
- malloc和free都声明在 stdlib.h 头⽂件中。
看一段代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
//开辟40个字节/10个整形大小空间
//int* p = (int *)malloc(40);
int* p = (int*)malloc(10*sizeof(int));
//检查空间是否开辟成功
if (p == NULL)
{
perror("malloc");
return 1;//异常退出
}
//打印出来
for (int i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
free(p);//释放p指向的动态内存
p=NULL;//内存释放掉了但是还是指向这块空间
return 0;
}
结果如下
- 发现结果是随机值
- 调试查看p的地址,发现free前后p的地址并没有发生改变,p还是指向那个地址,所以释放后这里p就是野指针,在进行解引用就是非法访问了,结论就是free之后我们还需要手动将p置为空指针
对于melloc和free总结为
1.malloc为你申请一定大小的空间,成功则返回void*的指针,失败则返回空指针;
2.由于返回的是void*,所以需要强制转换一下才可以使用;
3.在使用完之后要使用free手动释放空间;
4.释放后要将旧指针置为空指针;
calloc
void* calloc (size_t num, size_t size);
- 函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。malloc是直接申请所有字节。
- calloc与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0,所以如果申请内存空间要求初始化,那么使用calloc是很好的选择。
举个例子
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, 4);
if (p == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
free(p);
p = NULL;
return 0;
}
程序结果如下
realloc
void* realloc (void* ptr, size_t size);
- 如果在用malloc或者calloc时,发现空间大了或者小了,为了合理分配内存会使用realloc来修改内存大小。
- ptr是需要调整的动态内存首元素地址,size是调整后新的大小。
- 返回是void*类型,会返回修改后的内存起始地址。
- 需要注意接受修改后的地址不能用原来的ptr接受,如果申请失败会把修改之前的数据也丢失变成NULL。需要创建一个新的指针变量
- realloc在调整内存空间的是存在两种情况:
◦ 情况1:原有空间之后有⾜够⼤的空间,直接在后面加上size个字节,返回指向原有空间起始位置的指针。
◦ 情况2:原有空间之后没有⾜够⼤的空间,会在内存中申请一块新的地方,将旧空间里的数据复制到新的空间,并在新空间的末尾加上size个字节,返回指向新空间起始位置的指针。(可以通过调试修改size大小对比发现区别)
举个例子
#include <stdio.h>
#include <stdlib.h>
int main()
{
//开辟10个整型空间
int* p = (int*)calloc(10, sizeof(int));
//如果开辟失败打印错误信息
if (p == NULL)
{
perror("calloc");
return 1;
}
//假设我们要扩容到20个整型空间
int* str = (int*)realloc(p, 20 * sizeof(int));
//执行任务
if (str != NULL)
{
//不为空指针才使用,不然会内存泄漏
p = str;
str = NULL;
}
free(p);
p = NULL;
return 0;
}
关于realloc还有一点需要补充的是如果给realloc传递的指针为NULL,那么此时realloc的功能和malloc一样。此时realloc相当于malloc。
例如:
int* p=(int* )realloc(NULL,10*sizeof(int));
文章来源地址https://www.toymoban.com/news/detail-835215.html
典型的动态内存错误
对NULL指针解应用
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
对动态开辟空间越界访问
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//error
}
使用free释放部分动态空间
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
对一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
忘记释放动态内存
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
动态内存经典笔试题分析
题目1
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
这段代码中的问题是在GetMemory
函数中,试图分配内存给指针p
,但它实际上并未改变传入的指针str
的值。因为C语言中的函数参数传递是按值传递的,所以当你传递一个指针给函数时,实际上是传递了这个指针的拷贝。这意味着在GetMemory
函数内部,你修改的是拷贝的指针,而不是原始的str
指针
为了解决这个问题,你可以使用指针的指针。这样,你就可以在函数内部修改指针的值,从而影响到函数外部的指针。以下是修改后的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void GetMemory(char **p)
{
*p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str); // 注意这里使用了 & 运算符来传递 str 的地址
strcpy(str, "hello world");
printf("%s\n", str);
free(str); // 不要忘记释放内存!
}
int main()
{
Test();
return 0;
}
题目2
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
在GetMemory
函数中,创建了一个字符数组p
,并试图返回其地址。然而这个数组是在函数内部创建的,所以当函数返回时,该数组的内存将被释放,返回的指针将变得无效。这会导致未定义的行为。所以str接收到的是野指针修改就是在static char p[]="hello world";延长生命周期。
题目3
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
题目4
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
柔性数组
C99中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做【柔性数组】成员。
typedef struct st_type
{
int i;
int a[];//或者a[0] 柔性数组成员
}type_a;
int main()
{
printf("%d\n", sizeof(type_a));//输出的是4
//sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
return 0;
}
柔性数组的特点:
• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
• sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
• 包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结 构的⼤⼩,以适应柔性数组的预期⼤⼩。
柔性数组的使用
int main()
{
type_a* p = (type_a*)malloc(sizeof(type_a) + sizeof(int) * 10);
//这样相当于在原来基础上加了40个字节在柔性数组上
p->a = 10;
//业务处理
for (int i = 0; i < 10; i++)
{
p->arr[i] = i;
}
printf("%d \n", p->a);
for (int i = 0; i < 10; i++)
{
printf("%d ", p->arr[i]);
}
return 0;
}
柔性数组的优势
typedef struct st_type
{
int a;
int * arr;
}type_a;
int main()
{
type_a* p = (type_a*)malloc(sizeof(int));
p->a = 10;
p->arr = (int*)malloc(10*sizeof(int));
for (int i = 0; i < 10; i++)
{
p->arr[i] = i;
}
printf("%d \n", p->a);
for (int i = 0; i < 10; i++)
{
printf("%d ", p->arr[i]);
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;
return 0;
}
第⼀个好处是:⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。
第⼆个好处是:这样有利于访问速度
连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你跑不了要⽤做偏移量的加法来寻址
c/c++中内存函数的分配
C/C++程序内存分配的⼏个区域:
1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。
2. 堆区(heap):⼀般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配⽅式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。 文章来源:https://www.toymoban.com/news/detail-835215.html
到了这里,关于动态内存管理、柔性数组的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!