【C语言】内存你知多少?详解C语言动态内存管理

目录

一, 计算机中的内存

二,动态内存申请函数

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次释放内存。
  • 有利于提高数据命中率,提升运行效率,减少内存碎片。

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

猜你喜欢

转载自blog.csdn.net/qq_72112924/article/details/131343876