首页 > 系统相关 >动态内存分配——C语言

动态内存分配——C语言

时间:2024-09-29 11:48:03浏览次数:10  
标签:malloc 函数 int free C语言 内存 动态内存 NULL 分配

本篇文章是对动态内存分配部分内容的学习分享,包含了四个内存函数的接受奥与使用以及常见的一些错误

那咱们废话不多说,直接开始吧!

1. 动态内存功能存在的意义

说到内存开辟也许我们并不能马上做出反应且清楚得知道指的是什么

事实上,我们已经掌握了内存开辟方法了只是我们对这个概念还不是很清晰:

int a = 10;
int b[10] = {0};
char c = 'A';
......

以上都是内存开辟的形式,只要我们创建了变量,实际上也就是开辟了内存,因为我们需要这部分内存来存放创建的变量值。

但上述的内存开辟方法有两个特点:

- 空间开辟⼤⼩是固定的

- 数组在申明的时候必须指定其⻓度,⼀旦确定了⼤⼩便不能调整

如此一看,感觉这种内存开辟的方法的确是缺少了一点灵活的感觉,并且有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,那上述数组的编译时开辟空间的⽅式就无法满⾜我们的需求了。

于是C语⾔引⼊了动态内存开辟,这样程序员就可以自行申请和释放空间,灵活度相对也就提高了不少。

2. 内存管理函数

在开始讲内存函数前我们需要知晓它们都被包含在了头文件stdlib.h中

2.1 malloc

在cplusplus.com上我们能够大体了解到这个函数的一些基本属性:

(点击这个链接cplusplus.com/reference/cstdlib/malloc/跳转网页)

2.1.1 函数形式

void* malloc (size_t size);

这是一个void*类型的函数,也就是说它会返回任意的一个指针类型

后面的参数则是需要开辟的内存空间(单位:字节)

2.1.2 函数的特性与使用

这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。 - 如果开辟成功,则返回⼀个指向开辟好空间的指针。 - 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。 - 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃   ⼰来决定。 - 如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器 在了解完了函数所具有的一些特性之后,我们便可以开始试着用一下malloc函数了:
#include<stdio.h>
#include<stdlib.h>

int main()
{
    int n=0;
    scanf("%d",&n);

    //分配n个整型大小的内存
	int * p=(int *)malloc(n * sizeof(int));

	//判断内存是否分配成功
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	else
		printf("内存分配成功!");
		
	return 0;
}

运行效果:

分配成功

分配失败

注意:

因为malloc是void*类型的函数,因此在使用的时候需要视情况而定要将其强制类型转换成什么类型,像我这里是用的 int *p 指针接收的,那么就需要将函数转成 int * 类型

2,2 free

对于这个函数,cplusplus.com上是这么说的(请忽略“自由”、“无空”两处无脑翻译...):

(点击链接cplusplus.com/reference/cstdlib/free/跳转网页)

2.2.1 函数形式

void free (void* ptr);
C语⾔提供了另外⼀个函数free,是专门⽤来做动态内存的释放和回收的,这也就解释了为什么后面的参数是void*类型,因为它需要接收指向不同类型的内存的指针。

2.2.2 函数的特性与使用

free函数⽤来释放动态开辟的内存。 - 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。 - 如果参数 ptr 是NULL指针,则函数什么事都不做。
#include <stdio.h>
#include <stdlib.h>

int main()
{
     int num = 0;
     scanf("%d", &num);

     int arr[num] = {0};
     int* ptr = NULL;
     ptr = (int*)malloc(num*sizeof(int));

    printf("%p\n", ptr);

    if(NULL != ptr)//判断ptr指针是否为空
     {
         int i = 0;
         for(i=0; i<num; i++)
         {
             *(ptr+i) = 0;
         }
     }
     free(ptr);//释放ptr所指向的动态内存
    printf("%p\n", ptr);
     ptr = NULL;//是否有必要?
    printf("%p\n", ptr);
     return 0; 
}

上面这段代码便是malloc与free的用法,但内存都已经被释放了后面的ptr=NULL还有必要写吗?

我们分别在刚给ptr分配完内存、free函数使用后、ptr=NULL后三个地方都加上个打印地址代码

运行一下观察效果:

能够发现,在ptr=NULL执行前,ptr一直都指向那个内存

就好像是和ta分手了,但是手机里还一直存着它ta的手机号码,但你再打过去ta也不会再接了...

既然如此还有什么必要继续留着呢?(今天,你痛了吗...)

因此,在最后加上个指针初始化操作还是很有必要的。

2.3 calloc

对于这个函数,cplusplus上是这么说的:

(点击链接cplusplus.com/reference/cstdlib/calloc/跳转网页)

2.3.1 函数形式

void* calloc (size_t num, size_t size);
C语⾔还提供了⼀个函数叫 calloc , calloc 函数也⽤来动态内存分配 与malloc函数一样的void*类型,注定了需要我们为它做出强制类型转换的操作 后面的两个参数,第一个是要开辟的数量num,第二个是指开辟一个大小为size的空间

2.3.2 函数的特性与使用

函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
#include<stdio.h>
#include<stdlib.h>

int main()
{
	int* p = (int*)calloc(10, sizeof(int));

	if (p == NULL)
	{
		perror("calloc");
	}
	else
	{
		for (int i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	free(p);
	p = NULL;
	
	return 0;
}

用calloc函数开辟了10个大小为int的内存,运行观察结果:

的确是将内存初始化为0了

所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使⽤calloc函数来完成任务!

2.4 realloc

这第四个内存函数,cplusplus.com上是这么说的:

(点击链接cplusplus.com/reference/cstdlib/realloc/跳转网页)

2.4.1 函数形式

void* realloc (void* ptr, size_t size);
返回值是 调整之后的内存起始位置 参数部分,第一个是一个指向需要修改内存大小的地址,第二个是需要的新的内存大小

2.4.2 函数的特性与使用

有时会我们发现过去申请的空间太⼩或太大了,为了合理且方便地使⽤与修改内存,realloc的出现就显得无比恰当。也正是它的出现让动态内存管理更加灵活。 这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。 realloc在调整内存空间的是存在两种情况: - 情况1:原有空间之后有⾜够⼤的空间 - 情况二 : 原有空间之后没有⾜够多的空间 情况三:内存修改失败 函数将会返回 NULL,因此为了原来的内存被NULL覆盖掉,我们需要创建一个新的指针去接收,若是检测分配成功,在将新的地址赋给旧的指针 接下来我们一起来看一段代码:
#include<stdio.h>
#include<stdlib.h>

int main()
{
	int* p = (int*)calloc(10,sizeof(int));

	printf("开辟的内存地址:%p\n", p);

	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}

	int* ptr = realloc(p, 80);

	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		p = ptr;
		printf("新的内存地址为:%p \n", p);
	}

	//free(p);
	free(ptr);

	//p = NULL;
	ptr = NULL;

	return 0;
}

我们先用calloc函数开辟一块内存空间,再用realloc扩大这块空间

将刚开辟好的、扩大之后的内存空间地址打印出来观察结果:

不难看出,系统为我们找了一块新的空间开辟目标大小的内存,使得两次地址发生了变化

这时候问题来了:

先后创建的两个指针 p 与 ptr 都要free()掉吗?

答案是:不能!坚决不能!这样会造成内存重复释放错误!

观察代码我们能够知道新的内存有一个新的指针ptr来指向,在确定没问题后才赋给了p指针,此时两个指针指向的是同一块内存空间,无论我们free哪一个,这块空间都已经被释放了,此时我们再free另外一个指针便会导致内存重复释放错误

(像这样...)

但将两个指针都赋为NULL的操作是可以一起进行的~

3. 常见的动态内存错误

3.1 对NULL指针的错误引用

int main()
{
int n=0;
scanf("%d ",&n);

int * p=(int *)malloc(n*sizeod(int));

*p = 20;

free(p);
}

如果分配内存失败了变回返回NULL给到指针p,此时再对p进行解引用的操作就会出错。

因此,我们需要及时对p进行检查

int main()
{
int n=0;
scanf("%d ",&n);

int * p=(int *)malloc(n*sizeod(int));

//加上判断
if(NULL == p)
{
    perror("malloc");
    return 1;
}

*p = 20;

free(p);
}

3.2 对动态开辟空间的越界访问

int* p1 = (int*)malloc(40);
if (p1 == NULL)
{
	perror("malloc");
	return 1;
}

for (int i = 0; i < 10; i++)
{
	* (p1+i) = i;
}
for (int i = 0; i <= 10; i++)//循环了11次,越界访问
{
	printf("%d ", *(p1 + i));
}

free(p1);
p1 = NULL;

在for循环中循环次数多了一次,造成了越界访问

这种注意一下下标一循环次数就可以了,问题不大

3.3 对非动态开辟内存使用free释放

	int* p2 = (int*)malloc(40);
	int arr[10] = { 0 };
	p2 = arr;
	free(p2);

这里将非动态内存的arr地址给p2,再对p2进行释放内存操作就会发生错误

3.4 使⽤free释放⼀块动态开辟内存的⼀部分

	int* p3 = (int*)malloc(40);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p3 = i;
		p3++;
	}

    //此时的p3所指向的位置已经不是这块内存的开头位置,便无法正常释放这块内存
	free(p3);

此时的p3的位置是不对劲的

                     

此时再去释放内存便会出错

因此我们一般需要在创建一个指针当做可移动的赋值指针,另一个则用来销毁:

	int* p3 = (int*)malloc(40);

    //再创建一个指针p4
    p4=p3;

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p4 = i;
		p4++;
	}

    //这样就可以正常释放内存了
	free(p3);

3.5 对同⼀块动态内存多次释放

这部分具体见上面的realloc部分

3.6 动态开辟内存忘记释放(内存泄漏)

这个部分十分容易被忽略,只要我们不小心忘记了释放分配好的空间便会出现这个问题

在主函数中:

	int* p4 = (int*)malloc(40);
	while (1);

当然也有可能出现在封装的函数中:

void test()
{
	int* p = (int*)malloc(100);
	if (p != NULL)
	{
		*p = 1;
	}

}

int main()
{
	test();
	while (1);

    return 0;
}

也许你会想那我们直接在主函数中对p进行释放不就可以了

事实上,p仅仅是函数中的一个局部变量,等我们在主函数中再想去找p指向的那块地址时已经找不到了

因此,我们需要在函数中便将p释放

void test()
{
	int* p = (int*)malloc(100);
	if (p != NULL)
	{
		*p = 1;
	}

free(p);

p=NULL;

}

int main()
{
	test();
	while (1);

    return 0;
}

或者,将函数返回值类型改为指针类型,再在主函数中创建一个指针去接收它,然后就可以在主函数中释放了

int * test()
{
	int* p = (int*)malloc(100);
	if (p != NULL)
	{
		*p = 1;
	}
return p;

}

int main()
{
	int * p2=test();
    p2=NULL;

    return 0;
}

以上便是本次内容分享的所有内容了,感谢你能看到这里,希望本次的分享也能够对你有所帮助

如果可以的话也麻烦来个三连,对我来说将会是莫大的鼓舞~

那就让我们,下次再见!

标签:malloc,函数,int,free,C语言,内存,动态内存,NULL,分配
From: https://blog.csdn.net/2301_80029060/article/details/142566435

相关文章

  • 【C语言】qsort库函数
    使用qsort排数组升序:代码:#include<stdio.h>#include<stdlib.h>intcmp_int(constvoid*e1,constvoid*e2){ return*(int*)e1-*(int*)e2;}//使用qsort排升序voidtest1(){ intarr[]={9,8,7,6,5,4,3,2,1,0}; intsz=sizeof(arr)/sizeof(arr[0]); ......
  • C语言计算程序运行的时间长度
    C语言计算程序运行的时间长度也就是求一段代码的运行结束后耗时多长时间的问题!!!求100以内的质数的代码,加上计数和计时功能clock_tstartend取起始时间和终止时间,计算两者之差,得出代码运行所用时间!!!cpu_time_used双精度,保存时间CLOCKS_PER_SEC宏,每秒的clock数clock_t,C......
  • 【C语言】字符函数和字符串函数(1)
    文章目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现四、strcpy的使用和模拟实现五、strcat的使用和模拟实现六、strcmp的使用和模拟实现一、字符分类函数  C语⾔中有⼀系列的函数是专⻔做字符分类的,也就是⼀个字符是属于什么类型的字符的,这些......
  • 【C语言】手把手带你拿捏指针(完)(指针笔试、面试题解析)
    文章目录一、sizeof和strlen的对⽐1.sizeof2.strlen3.sizeof与strlen对比二、数组和指针笔试解析1.一维数组2.字符、字符串数组和字符指针代码1代码2代码3代码4代码5代码63.二维数组4.总结三、指针运算笔试题解析代码1代码2代码3代码4代码5代码6一、sizeof和strl......
  • 华为OD机试2024年E卷-转骰子[200分]( Java | Python3 | C++ | C语言 | JsNode | Go )实
    题目描述骰子是一个立方体,每个面一个数字,初始为左1,右2,前3(观察者方向),后4,上5,下6,用123456表示这个状态,放置在平面上,可以向左翻转(用L表示向左翻转1次),可以向右翻转(用R表示向右翻转1次),可以向前翻转(用F表示向前翻转1次),可以向后翻转(用B表示向后翻转1次),可以逆时针旋转(......
  • 华为OD机试2024年E卷-矩阵匹配[200分]( Java | Python3 | C++ | C语言 | JsNode | Go )
    题目描述从一个N*M(N≤M)的矩阵中选出N个数,任意两个数字不能在同一行或同一列,求选出来的N个数中第K大的数字的最小值是多少。输入描述输入矩阵要求:1≤K≤N≤M≤150输入格式:NMKN*M矩阵输出描述N*M的矩阵中可以选出M!/N!种组合数组,每个组合......
  • C语言自定义类型:联合体
    目录前言一、联合体1.1联合体类型的声明1.2联合体的特点1.3相同成员的结构体和联合体对比1.4联合体大小的计算1.5联合体的⼀个练习总结前言前面我讲到C语言中的自定义结构——结构体,其实C语言中的自定义结构不只有结构体,还有枚举和联合体,我们今天就来学习一下......
  • C语言-文件操作这一篇足够
    目录 1.文件是什么2.文件类型 2.1 程序文件 2.2 数据文件  2.3文件名3.文件的打开与关闭 3.1文件指针3.2文件的打开与关闭 3.2.1fopen和fclose 4.文件的顺序读写 4.1fgetc和fputc4.2fgets和fputs 4.3fscanf和fprintf4.4fread和fwrite 5.......
  • C语言-动态内存管理(malloc、calloc、realloc)
    目录1.内存布局2.动态内存函数2.1malloc2.1.1malloc是什么2.1.2如何用​编辑2.2free2.2.1free是什么2.2.2如何用2.3calloc2.3.1calloc是什么2.4realloc2.4.1realloc是什么2.4.2realloc如何使用2.4.3realloc可以实现与malloc同样的功能3.常见的动态......
  • C语言指针plus版
            本篇文章,我们将一起探讨指针高级版一、指针数组、数组指针    1.1指针数组    就是存放指针的数组,因此指针数组是一个数组,不是指针    例如:   int*pa[5];   //整型指针的数组   char*pb[2];  //字符型指针......