c语言动态申请内存

前言:不管是c还是c++都会大量使用,使用c/c++的数据结构的时候也会使用的动态内存

目前我们申请内存就只有两种方式,一种是创建变量,一种是创建数组

复制代码
//创建变量
int a=0;
char c='w'; 

//创建数组
int arr1[100];
char arr2[10];

上面的两种申请空间方式一单申请号就无法改变,所以就引入了今天我们学习的四个函数

malloc

复制代码
void* malloc (size_t size);
  1. 这个函数向内存申请⼀块连续可用的空间,并返回指向这块空间的指针
  2. 如果开辟成功,则返回⼀个指向开辟好空间的指针(返回的是空间的起始地址)
  3. 如果开辟失败,则返回⼀个返回值的类型是 NULL 指针,因此malloc的返回值⼀定要做检查
  4. 返回值的类型是void*(因为我只要编译器开辟好空间,但放什么类型的数据编译器就不知道了),所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己决定
  5. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译

举个例子:这里我们申请20个字节来存放5个整型数据

复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int * p=(int*)malloc(20);
	if (p == NULL)
	{
		perror("malloc");//perror将系统错误码(errno)翻译成人类可读的错误信息
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;
	}
	return 0;
}

这里申请20个字节来存放整型数据就应该要用整形的指针接收,而把malloc的类型转化为整型指针是因为void*是通用指针,可以返回任何类型的指针

而动态申请的内存空间统一都在内存的堆区中(需要手动释放),我们的内存中有栈区,堆区,静态库

free

C语言提供了另外⼀个函数free,专门是用来做动态内存的释放和回收的

复制代码
void free (void* ptr);
  • free函数用来释放动态开辟的内存

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的

  • 如果参数 ptr 是NULL指针,则函数什么事都不做

  • malloc和free都声明在 stdlib.h 头文件中

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    int* p = (int*)malloc(20);
    if (p == NULL)
    {
    perror("malloc");
    return 1;
    }
    int i = 0;
    for (i = 0; i < 5; i++)
    {
    *(p + i) = i + 1;
    }
    free(p); //手动释放空间
    return 0;
    }

可以看到所有的空间的数据全部清空,但地址依旧没变,说明只是将空间的使用权限还给了操作系统,但p指向的指针还是原来的起始位置,这个时候p就指向的是野指针,而我们还要把野指针转化成空指针

复制代码
free(p);
p=NULL;

如果我们把"*(p + i) = i + 1;"这行代码换成"*p=i+1; p++",这就会改变p指向的原始位置,将p置为空后就不再是申请空间的起始地址了

calloc

复制代码
void* calloc (size_t num, size_t size);

函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0

与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0

例如:向内存申请五个整型类型的空间

复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(5, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	return 0;
}

这个时候每个字节里面的初始值都是0

那我们打印出来看一下

复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(5, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
	}
      free(p);
      p=NULL;
	return 0;
}

realloc

realloc函数的出现让动态内存管理更加灵活

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使 用内存,我们⼀定会对内存的大小做灵活的调整,那realloc函数就可以做到对动态开辟内存大小的调整

复制代码
void* realloc (void* ptr, size_t size);
  • ptr 是要调整的内存地址
  • size 调整之后新大小
  • 返回值为调整之后的内存起始位置
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
  • realloc在调整内存空间的是存在两种情况

情况1:原有空间之后有足够大的空间 。 情况2:原有空间之后没有足够大的空间

例如:将原来的20个字节的空间再加上20个字节,这时候就会出现我们上面的两种情况

情况1:这是剩余的空间够用,直接扩大就行

情况2:这就是剩余的空间不够扩大,这时就会在堆区中重新找一块空间开辟20个字节,然后将旧的数据拷贝一份过来在这块新开辟的空间前面,再释放掉旧的空间返回新的空间的起始位置,而之前旧的空间不需要手动释放,realloc会自动释放,如果最后开辟空间失败就会返回NULL

复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int * p=(int*)malloc(20);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;
	}
       p= realloc(p,40);
	return 0;
}

这个时候这个代码就出问题了,我开辟40个字节,这时候就差20个字节,如果开辟失败就会把p置为NULL,那我p之前指向的20个字节也就变成了空,就会出现问题

复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int * p=(int*)malloc(20);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;
	}
      int ptr=(int*)realloc(p,40);
        if(ptr !=NULL)
        {
            ptr=p;
        }
        else
        {
            perror("realloc");
        }
          free(p);
          p=NULL;
	return 0;
}

这才是正确的写法,将创建好的40个字节的空间先暂时给ptr,然后判断ptr是否为NULL,如果不是NULL就将p赋值给ptr

常见的动态内存的错误

对NULL解引用操作

复制代码
void test()
{
int *p = (int *)malloc(INT_MAX);
*p = 20;//如果p的值是NULL,就会有问题 
free(p);
}

此时我申请了一块空间,但我没有对p进行判断是否为NULL,这个时候就很危险了,此时编译器就会出现警告,他怀疑p的地址为NULL

在这里我们断言判断p是否为NULL

复制代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
	int* p = (int*)malloc(INT_MAX);
	assert(p);
	*p = 20;
	free(p);
    p=NULL;
}

越界访问

复制代码
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);  //对非动态内存的空间进行释放,创建的变量都是在栈区
}

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

复制代码
void test()
{
    int *p = (int *)malloc(100);
    p++;  //这个时候p指向的地址就不再是申请空间的起始位置
    free(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);
}

这个代码有几个错误,第一个就是我创建的地址放在了局部变量里面,局部变量出了test()函数就消失了,想释放都来不及了,while也会产生死循环

忘记释放不再使用的动态开辟的空间会造成内存泄漏, 切记:动态开辟的空间⼀定要释放,并且正确释放

相关推荐
AC赳赳老秦2 小时前
pbootcms模板后台版权如何修改
java·开发语言·spring boot·postgresql·测试用例·pbootcms·建站
悟能不能悟2 小时前
list<string> 和String[],转化为jsonstr是不是一样的
数据结构·windows·list
代码or搬砖2 小时前
Collections和Arrays
java·开发语言
吴名氏.2 小时前
电子书《Java程序设计与应用开发(第3版)》
java·开发语言·java程序设计与应用开发
于慨2 小时前
dayjs处理时区问题、前端时区问题
开发语言·前端·javascript
listhi5203 小时前
基于MATLAB的LTE系统仿真实现
开发语言·matlab
ss2733 小时前
ScheduledThreadPoolExecutor异常处理
java·开发语言
TechNomad3 小时前
二叉堆&大根堆&小根堆的介绍和使用
数据结构
ejjdhdjdjdjdjjsl3 小时前
Winform初步认识
开发语言·javascript·ecmascript