目录
一, 计算机中的内存
二,动态内存申请函数
2.1 头文件
2.2 malloc函数
2.3 free函数
2.3 calloc函数
2.4 realloc函数——调整空间函数
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
2.5 经典笔试题
1.
2.
三,柔性数组
结语
一, 计算机中的内存
我们知道目前内存有,栈区,堆区,静态区。
C/C++程序内存分配的几个区域:
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些 存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收 。分配方式类似于链表。
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
目前我们有这些开辟内存空间的方法:
int h = 100; // 在静态区创建全局变量
int main()
{
static z = 10; // 变量储存在静态区
int val = 20; //在栈空间上开辟四个字节
char arr[10] = {0}; //在栈空间上开辟10个字节的连续空间
return 0;
}
但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编 译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。
二,动态内存申请函数
2.1 头文件
#include<stdlib.h>
2.2 malloc函数
void* malloc (size_t size); // szie 字节数的意思,可以直接填数字
C语言提供了一个动态内存开辟的函数:
这个函数向内存堆区申请一块连续可用的随机空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- malloc可以申请0字节的空间,会返回一个没有空间的指针,不能访问。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
运用:
int * a = (int *)malloc(4); // 4可以改成 sizeof(int)
if (a == NULL)
{
perror("malloc"); // 开辟失败打印原因
}
2.3 free函数
void free (void* ptr);
free函数用来释放动态开辟的内存。
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
- 当我们不释放动态申请的内存时,如果持续申请内存,那么电脑内存不断变少,没有内存时就会死鸡,当程序结束时,操作系统自动回收内存。
- 如果程序不结束,那么不回收的内存,会越来越多,这就是出现内存泄露问题。
2.3 calloc函数
void* calloc (size_t num, size_t size);
参数解析:
- num: 创建数据类型的个数。
- size: 每个数据类型所占的字节数。
特点:
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* a = (int*)malloc(8); // 2个整型
int* b = (int*)calloc(2,sizeof(int)); // sizeof(int) 可以是 4,反正表示字节数
return 0;
}
查看内存验证:
2.4 realloc函数——调整空间函数
void* realloc (void* ptr, size_t size);
参数解析:
- ptr :是要调整的内存地址。(如果为空指针,那么同malloc功能类似。)
- size :调整之后新大小
- 返回值 : 为调整之后的内存起始位置。
realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存, 我们一定会对内存的大小做灵活的调整。
realloc 函数就可以做到对动态开辟内存大小的调整。 函数原型
如下:
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
-
情况1:原有空间之后有足够大的空间
-
情况2:原有空间之后没有足够大的空间
看以下代码,思考代码那里不合理:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int* p = &a;
p = (int*)realloc(p, sizeof(int));
printf("%d", *p);
return 0;
}
我们可以看出,这里没有判断realloc是否成功,如果申请失败,返回NULL,那么p就会丢掉原有的地址,这是不合理的,因此我们需要进行判断,所以正确的代码是:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int* p = &a;
int *tmp= (int*)realloc(p, sizeof(int));
if (tmp == NULL)
{
perror("realloc");
return -1;// 或者exit(-1);
}
p = tmp;
printf("%d", *p);
return 0;
}
2.5 经典笔试题
1.
思考:程序结果
char* GetMemory(void)
{
char p[] = "hello world"; // 栈区开辟,函数结束内存收回
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory(); // 非法访问。访问被系统收回的内存
printf(str); // 无法打印,已被覆盖
}
int main()
{
Test();
return 0;
}
2.
程序中存在的问题
void Test(void)
{
char* str = (char*)malloc(100); // 缺少对malloc返回值检查
/*加上:if(str == null)
{
perror("malloc");
exit(-1);
}*/
strcpy(str, "hello");
free(str);
// 加上 str = null;
if (str != NULL) // str还存有原先内存的地址,出现野指针
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
三,柔性数组
也许你从来没有听说过柔性数组(flflexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最 后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
例如:
typedef struct st_type
{
int i;
int a[]; //柔性数组成员
}type_a;
特点:
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
例如:
typedef struct mystruct
{
double a;
int c[];
}MS;
int main()
{
printf("%d\n", sizeof(MS)); // 8
return 0;
}
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
如下:
typedef struct mystruct
{
double a;
int c[];
}MS;
int main()
{
MS * p1 = (MS* )malloc(sizeof(MS) + 40); // 那就是48个字节,40就是对柔性数组申请的 //预期空间
return 0;
}
也可以这样:
代码2
typedef struct mystruct
{
double a;
int *c;
}MS;
int main()
{
MS * p1 = (MS* )malloc(sizeof(MS));
if (p1 == NULL)
{
perror ( "malloc");
exit(-1);
}
p1->c = (int *)realloc(c, 40);
return 0;
}
代码1相较于代码2的优势:
- 方便释放内存。代码2需要2次释放内存。
- 有利于提高数据命中率,提升运行效率,减少内存碎片。
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。