首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C语言动态内存管理

C语言动态内存管理

作者头像
趙卋傑
发布2026-01-12 14:44:44
发布2026-01-12 14:44:44
910
举报

动态内存管理

全局变量——在静态区开辟内存

局部变量——在 栈区开辟内存

内存使用情况:

1.动态内存分配的原因

代码语言:javascript
复制
int val =20;         //在栈空间上开辟4个字节
char arr[10]={0};   //在栈空间上开辟10个字节的连续空间

空间开辟⼤⼩是固定的

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

所以抛出问题,我们需要的空间⼤⼩在程序运⾏的时候才能知道,那数组的编译时开辟空间的⽅式就不能满⾜了,所以就有了动态内存开辟的概念

2.动态内存函数的介绍

malloc和free都声明在 stdlib.h 头⽂件中。

2.1 malloc函数介绍

函数的功能: 开辟一个内存块

函数的参数: 开辟空间的字节大小

函数的返回类型: void * 空指针类型

• 如果开辟成功,则返回⼀个指向开辟好空间的指针。

• 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。

• 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃ ⼰来决定。

• 如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。

2.2 free函数介绍

free函数是专⻔⽤来做动态内存的释放和回收的,当我们使用malloc函数申请空间不再使用的时候,就应该还给操作系统

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

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

代码语言:javascript
复制
free(p);
p=NULL;

例:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int* p = (int*)malloc(40);
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *(p + i) = i;
    }
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }
    free(p);//释放ptr所指向的动态内存
    p = NULL;//是否有必要?
    return 0;
}

我们通过调试发现通过free函数将malloc函数开辟的空间释放后,p依然能找到这块空间,为了避免这块空间的错误使用,我们将p=NULL,赋成空指针,这样就p指针就真正的断开了与这块空间的联系。

2.3 calloc函数的介绍

函数功能: 开辟一个内存中连续的空间并将空间里的每个字节初始化为0

函数参数 第一个参数:元素的个数 第二个参数 :每个元素的字节大小

函数的返回类型: void * 空指针类型

• 函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

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

例:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int* p = (int*)calloc(10, sizeof(int));//开辟10个int类型的空间,40个字节
    if (NULL != p)
    {
        int i = 0;
        for (i = 0; i < 10; i++) //没有赋值,直接对内容进行打印
        {
            printf("%d ", *(p + i));
        }
    }
    free(p);
    p = NULL;
    return 0;
}
2.4 realloc的使用

当我们开辟一块空间后,发现空间不够用,想要调整更大的空间

函数功能: 重新分配内存空间,调整之前开辟动态内存空间的大小

函数参数: 第一个参数 是之前开辟内存块的地址 ,第二个参数 是调整后空间的字节大小。

函数返回类型: void * 空指针类型

• ptr 是要调整的内存地址

• size 调整之后新⼤⼩

• 返回值为调整之后的内存起始位置。

• 这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到 新 的空间。

realloc调整空间失败,会返回空指针

• realloc在调整内存空间成功存在两种情况:

代码语言:javascript
复制
◦ 情况1:原有空间之后有⾜够⼤的空间 
    直接进行扩大,返回旧的空间的起始地址
代码语言:javascript
复制
◦ 情况2:原有空间之后没有⾜够⼤的空间
realloc函数会在内存堆区重新找一块空间(满足新空间的大小需求),同时会把旧的数据拷贝到新的空间中,然后释放旧的空间,同时返回新空间的起始地址

例1:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int* ptr = (int*)malloc(100);
    if (ptr != NULL)
    {
        //业务处理 
    }
    else
    {
        return 1;
    }
    //扩展容量 
​
    //代码1 - 直接将realloc的返回值放到ptr中 
    ptr = (int*)realloc(ptr, 1000);//error 
​
    //代码2 - 先将realloc函数的返回值放在p中,不为NULL,再放ptr中 
    int* p = NULL;
    p = realloc(ptr, 1000);
    if (p != NULL)
    {
        ptr = p;
    }
    //业务处理 
    free(ptr);
    return 0;
}
代码语言:javascript
复制
    ptr = (int*)realloc(ptr, 1000);//如果调整失败,原本开辟的空间也找不到,所以不能直接将realloc的返回值放到ptr中 

例2:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int * p = (int *)malloc(10 * sizeof(int));

	if (p == NULL)
	{
		printf("内存开辟空间失败\n");
	}
	else
	{
		int i = 0;
		for (i = 0; i<10; i++)
		{
			p[i] = i;
		}
		for (i = 0; i<10; i++)
		{
			printf("%d ", p[i]);
		}
	}

	  //这里在堆区开辟了一个40字节的空间

	  //假如内存空间不够用,我们想调整的更大一些,调整成80个字节的空间大小

	int* p2 = (int *)realloc(p,20*sizeof(int));

	if (p2 == NULL)
	{
		printf("内存开辟失败\n");
	}

	else
	{
		int i = 0;
		for (i = 10; i < 20; i++)
		{
			p[i] = i;
		}
		for (i = 10; i < 20; i++)
		{
			printf("%d ", p[i]);
		}

	}

	free(p2);
	p2 = NULL;

	return 0;
}

例3:

代码语言:javascript
复制
int main()
{
    int* p = (int*)realloc(NULL,40);//等价于malloc函数
    free(p);
    p = NULL;
    return 0;
}

3.常见动态内存的错误

3.1对NULL指针的解引用操作
代码语言:javascript
复制
void test()
 {
 	int *p = (int *)malloc(100);
 	*p = 20;//如果p的值是NULL,就会有问题 
 	free(p);
 }
3.2对动态开辟空间的越界访问
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int *p = (int *)malloc(40);
	if (p == NULL)
	{
		printf("开辟内存空间失败\n");
	}
	else
	{
		int i = 0;
		for (i = 0; i<=10; i++)
		{
			p[i] = i;//当循环到第11次的时候就越界访问了
            //*(p + i) = i;
		}
	}
	return 0;
}
3.3对非动态开辟内存使用free释放

malloc,calloc,realloc函数申请的空间如果不主动释放,出了作用域是不会销毁的

释放的方式:1.free主动释放 2.程序结束,操作系统回收

free函数只能对我们开辟的动态内存进行释放操作

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a=10;
    int *p=&a;//p指向的空间不再是堆栈上的地址
    free(p);
    p=NULL;
    return 0;
}

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

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int *p = (int *)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		printf("内存开辟失败\n");
	}
	p++;
    
	free(p);//释放空间的p不是起始地址
	p = NULL;
	return 0;
}

我们最后free(p)时p指向的不是内存的初始位置,所以我们开辟的这块空间并不能完全释放,会造成内存泄漏。

3.5对同⼀块动态内存多次释放
代码语言:javascript
复制
#include <stdio.h>

int main()
{
	int *p = (int *)malloc(100);
	free(p);
	free(p);   //重复释放
	p=NULL;
	return 0;
}

注意:同一块内存空间只能释放一次

3.6动态开辟内存忘记释放(会造成内存泄漏)
代码语言:javascript
复制
#include <stdio.h>

int main()
{
	int *p = (int *)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	while (1);
	return 0;
}

注意:忘记释放不再使用的动态开辟的空间会造成内存泄漏。一定要正确释放

动态内存经典笔试题

练习一
代码语言:javascript
复制
#include <stdlib.h>
#include <string.h>

void GetMemory(char *p)
{
	p = (char *)malloc(100);
}
void Test(void)
{
	char *str = NULL;
	GetMemory(str);
	strcpy(str, "helloworld");
    //此处str依然是空指针,对空指针解引用操作符会造成程序崩溃,将 “hello world ”放入空指针指向的空间,最后程序必然会崩溃。
	printf(str);
}
int main()
{
	Test();
	return 0;
}

GetMemory(str)把str空指针传到该函数中的p,在函数内部开辟了100个字节空间的大小,p是GetMemory函数的形参,只能在函数内部有效,等GetMemory函数返回之后,动态开辟内存尚未释放并且无法找到,所以会造成内存泄漏

改正:

代码语言:javascript
复制
#include <stdlib.h>
#include <string.h>

void GetMemory(char **p)
{
	*p = (char *)malloc(100);
}
void Test(void)
{
	char *str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

char* GetMemory(char *p)
{
	p = (char *)malloc(100);
    return p;//因为p的地址在堆区,所以返回首地址还能找到之前开辟的空间
}
void Test(void)
{
	char *str = NULL;
	str = GetMemory(str);
	strcpy(str, "helloworld");
	printf(str);
}
int main()
{
	Test();
	return 0;
}
练习二(返回栈地址的问题)
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
char *GetMemory(void) 
{
   char p[] = "hello world";
   return p; 
}

void Test(void) 
{
   char *str = NULL;
   str = GetMemory();//str此时是野指针
   printf(str);
}
int main()
{
   Test();
   return 0;
}

char p[]是局部变量,出了该函数内存就会还给操作系统,当返回数组首地址并赋值到str上是没有意义的,当我们打印的时候就会出现问题,找不到数组的内容

练习三
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

void GetMemory(char **p, int num)
{
    *p = (char *)malloc(num);
}
void Test(void) 
{
   char *str = NULL;
   GetMemory(&str, 100);
   strcpy(str, "hello");
   printf(str);
}
int main()
{
   Test();
   return 0;
}

忘记释放动态开辟的内存,导致内存泄漏了

改正:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

void GetMemory(char **p, int num)
{
    *p = (char *)malloc(num);
}
void Test(void) 
{
   char *str = NULL;
   GetMemory(&str, 100);
   strcpy(str, "hello");
   printf(str);
   free(p);
   p=NULL;
}
int main()
{
   Test();
   return 0;
}
练习四(非法访问内存)
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

void Test(void)
{
  char *str=(char *)malloc(100);
  strcpy(str,"hello");
  free(str);
  if(str!=NULL)
  {
      strcpy(str,"world");
      printf(str);
  }
}

int main()
{
   Test();
   return 0;
}

在free释放空间后,没有对str指针置空(NULL),所以操作系统依然记着所开辟空间的地址,但是这块内存已经不属于我们了,,当对该地址再次使用时就造成了非法访问的问题

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 动态内存管理
    • 1.动态内存分配的原因
    • 2.动态内存函数的介绍
      • 2.1 malloc函数介绍
      • 2.2 free函数介绍
      • 2.3 calloc函数的介绍
      • 2.4 realloc的使用
    • 3.常见动态内存的错误
      • 3.1对NULL指针的解引用操作
      • 3.2对动态开辟空间的越界访问
      • 3.3对非动态开辟内存使用free释放
      • 3.5对同⼀块动态内存多次释放
      • 3.6动态开辟内存忘记释放(会造成内存泄漏)
  • 动态内存经典笔试题
    • 练习一
    • 练习二(返回栈地址的问题)
    • 练习三
    • 练习四(非法访问内存)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档