当前位置: 首页 > news >正文

C语言动态内存管理

C语言中提供了很多数据类型,有基本数据类型、指针类型、自定义类型和空类型。我们可以根据需要选择合适的数据类型使用,当需要较大空间时我们可以选择数组、结构体等类型。但这些并不能够满足编程需求,例如在不知道具体所需内存大小的情况下直接定义变量分配内存就有可能造成内存浪费。在C语言中,动态内存管理就可以很好的解决这一问题。使用动态内存管理函数,程序员可以根据需求随时向系统分配申请空间,当这块空间用完时在及时释放归还给系统。

1.为什么存在动态内存分配

当不知道需要多大内存或者需要动态申请内存空间时,可以使用动态内存分配解决一次性分配太多内存造成内存浪费的问题。

2.动态内存函数的介绍

动态内存函数使用时,都需要包含头文件。动态内存函数的头文件为:#include<stdlib.h>或者#include<malloc.h>

malloc()和free()

void* malloc (size_t size);

Allocates a block of size bytes of memory, returning a pointer to the beginning of the block.If size is zero, the return value depends on the particular library implementation (it may or may not be a null pointer), but the returned pointer shall not be dereferenced.

//向内存申请size个字节的空间,并且返回这块空间的首地址。如果所给参数为0,则返回值取决于编译器。

void free (void* ptr);

A block of memory previously allocated by a call to malloc, calloc or realloc is deallocated, making it available again for further allocations.

//释放由malloc、calloc、realloc函数申请的空间

注意:如果申请空间失败,会返回NULL,所以使用malloc函数时必须要检查是否申请成功。而使用malloc函数时,具体要申请的空间的类型是由使用者自己决定的。

注意:由于malloc函数返回值为void*类型,在使用时需要根据自己所申请的类型进行强制类型转换

举个例子说明一下malloc函数和free函数的使用: 

从图中,我们可以看到,给ptr申请了五个int型的空间并且都赋初值为0,从监视窗口就可以看到我们申请的五个空间已经全部被赋值为0,而第6个是随机值。使用完后,将空间进行释放。那么,释放完成后有必要将指针置空吗?

如果不置空:

没有将指针置空,我们可以看到五个int型空间的值已经是随机值,不在是我们赋的0.但是,仔细观察我们可以看到,虽然ptr已经被释放,但是ptr依然指向之前分配的那块空间的地址。这样就会存在这样一种可能:系统将这块空间分配给其他程序,但ptr还指向这块空间,如果错误使用ptr就会造成这块空间被污染,导致一些难以预料的后果。如果置空,ptr不在指向这块空间,就不会造成这个问题。

calloc函数

void* calloc (size_t num, size_t size);

calloc函数的功能

函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

举个例子:

int main()
{
     int *p = calloc(10, sizeof(int));
     if(NULL != p)
     {
         //使用空间
     }
    free(p);
    p=NULL;
    return 0;
}

ralloc函数

void* realloc (void* ptr, size_t size);

//ptr为要调整的空间的地址,size为要调整的大小

realloc函数的出现让动态内存分配变得更加灵活,realloc的函数的作用是对动态分配好的内存空间的大小进行调整。当我们一次性开辟的内存不够时,可以利用这个函数进行二次分配,最主要的是realloc函数还会将原来的内存空间中的值进行复制。realloc函数的使用有以下几种情况:

注意:realloc函数必须是对malloc函数或者calloc函数申请的空间进行修改

realloc函数使用演示:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	char* ptr = (char*)calloc(5,sizeof(char));
	if (ptr != NULL)
	{
		realloc(ptr,8);
	}
	free(ptr);
	ptr = NULL;
	return 0;
}

3.常见的动态内存错吴

对空指针的解引用操作

对空指针进行解引用操作可能会出现下面问题:

可能会出现空指针解引用的示例:

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

上面代码没有对使用malloc函数申请的空间进行判断,如果申请空间失败,就会返回空指针,这时在进行解引用就会出现问题。 

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

动态开辟空间的越界访问是非法访问指在开辟的空间的范围外的空间。如下代码:

//动态开辟空间的越界访问
int main()
{
	char* ptr = (char*)malloc(10);
	if (ptr == NULL)
	{
		printf("开辟空间失败\n");
		exit(-1);
	}
	ptr[11] = 20;
    free(ptr);
	return 0;
}

虽然说动态开辟空间的越界访问不一定会报错,但实际写代码时是坚决不允许对空间越界访问的。 

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

使用free释放对动态开辟的内存的一部分

int main()
{
    char* ptr = (char*)malloc(10);
    ptr++;//此时ptr不在是动态开辟内存的起始地址
    free(ptr);
    return 0;
}

对同一块内存释放多次

void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
}

对动态开辟的内存忘记释放

动态开辟的内存忘记释放会造成内存泄露,因此动态开辟的内存一定要使用free进行释放,并且正确释放(释放多次、释放一部分等都是不正确的释放)

4.柔性数组

C99 中,结构中的最 后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。例如:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;

柔性数组的特点:

结构中的柔性数组成员前面必须至少一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存。

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应 柔性数组的预期大小。

5.动态内存开辟的经典笔试题

笔试题1

我们发现,这个程序并没有输出“hello word”这是为什么呢?因为在使用GetMemory函数时,参数传的是一个指针变量,而不是指针变量的地址,因此并不会修改ptr中的内容。正确代码如下:

笔试题2

char *GetMemory(void) 
{
     char p[] = "hello world";
     return p; 
}
void Test(void) 
{
     char *str = NULL;
     str = GetMemory();
     printf(str);
}

执行上述代码,会输出一些随机值。这是因为p是局部变量,在GetMemory函数执行完后p就会被释放,再次访问就会输出一些随机值。

笔试题3

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

这个程序的云顶结果是:hello。没有任何问题,这个程序是对上面两个程序的综合考察,这里既不存在局部变量问题也不存在传地址问题。

笔试题4

int main()
{
	char* p = (char*)malloc(100);
	strcpy(p,"hello");
	free(p);
	if (p != NULL)
	{
		printf("%s\n",p);
	}
	return 0;
}

实际上这个代码是有问题的,首先这个代码对已经释放的空间再次访问。因为,前面我们讲过,释放后如果指针不置空指针还将指向这块空间,再次访问会继续返回这块空间中的值,也就是说这个程序会输出一个随机值。

动态内存管理的一个使用误区:检查下面程序存在的问题

char * test()
{
	char* str = (char*)calloc(10, sizeof(char));
	free(str);
	return str;
}
int main()
{
	char* p = test();
	return 0;
}

结果分析:

观察上图可以发现,程序执行到最后一步时指针p指向了test函数中的局部变量str的地址,而且str已经不存在。str不存在是因为,str是局部变量,当test函数执行完时,str就会归还给内存。之所以指针p能指向str,是因为在str被归还给内存之前我们将str的地址返回给了指针p(这里所说的归还不同于动态开辟的内存释放后的归还)。但是,我们发现calloc开辟的空间应该都被初始化为0了,但是p的内容全都为随机值,这是为什么呢?

前边,我们讲过动态开辟的内存都要进行释放,否则会造成一系列问题。这里,在将str返回前已经将str释放,看似没问题,实则返回的是一块已经被释放的地址。那么,局部变量动态开辟的内存需要返回的情况下该如何进行释放呢?

观察下面代码:

我们发现,这里test 函数调用完后str就被归还给内存,而p在释放之前得到的内存中的值也全为0,释放之后会变成随机值。刚好满足我们的要求,既对动态开辟的内存进行了释放也满足了我们的使用需求。

结论:局部变量使用动态内存管理申请的空间,如果在其他函数中需要使用大该空间,可以不直接释放该局部变量,等到使用完后在利用指向该空间的地址的指针进行释放。例如:这列的str可以不直接使用str进行释放,使用完后利用指针p进行释放。

相关文章:

  • C语言文件操作
  • C语言程序的编译和链接
  • nowcoder---统计子串个数(C语言)
  • LeetCode---搜索二维矩阵(c语言)
  • C语言编程过程中与到的问题(不定时更新)
  • 计算机中加减乘除的实现
  • Linux基本指令+使用举例、权限的认识和相关指令、Linux添加删除用户、shell的理解(超详细)
  • Linux连接网络、修改账户密码
  • LeetCode---删除排序数组中的重复项(C语言)
  • LeetCode---合并两个有序数组
  • leetcode---返回链表的中间节点
  • 学生成绩管理系统---数据结构、C语言课程设计
  • 数据结构课程设计---学生成绩管理系统
  • leetcode---环形链表(快慢指针证明)
  • leetcode---环形链表II
  • ----------
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • 【comparator, comparable】小总结
  • Apache Pulsar 2.1 重磅发布
  • co.js - 让异步代码同步化
  • ES6 学习笔记(一)let,const和解构赋值
  • gitlab-ci配置详解(一)
  • JavaScript-Array类型
  • jquery ajax学习笔记
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • laravel5.5 视图共享数据
  • linux学习笔记
  • Netty源码解析1-Buffer
  • quasar-framework cnodejs社区
  • SpiderData 2019年2月23日 DApp数据排行榜
  • 基于Mobx的多页面小程序的全局共享状态管理实践
  • ------- 计算机网络基础
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 前端路由实现-history
  • 如何利用MongoDB打造TOP榜小程序
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 为什么要用IPython/Jupyter?
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • 再谈express与koa的对比
  • 在Unity中实现一个简单的消息管理器
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • (175)FPGA门控时钟技术
  • (C语言)字符分类函数
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (独孤九剑)--文件系统
  • (附源码)python旅游推荐系统 毕业设计 250623
  • (新)网络工程师考点串讲与真题详解
  • (转)EOS中账户、钱包和密钥的关系
  • (转)Mysql的优化设置