初识动态内存分配

news2024/11/28 3:51:02

目录

为什么会存在动态内存分配:

malloc:

free:

calloc:

realloc:

注意事项:

攻破经典易错题:

题目一:

 存在以下两种方式进行修改:

1.利用二级指针进行修改:

2.利用返回指针的方式进行修改:

题目二:

题目三:

题目四:

 柔性数组:

柔性数组的优势:

 总结:


为什么会存在动态内存分配:

动态内存分配可以提高程序的灵活性和效率。使用动态内存分配可以在程序运行时根据需要分配和释放内存空间,而不是在程序编译时分配固定的内存空间。这样可以避免浪费内存空间和提高内存利用率。动态内存分配还可以支持动态数据结构,例如链表和树等,这些数据结构的大小不确定,可以在运行时根据实际需要调整大小。此外,动态内存分配还可以避免栈溢出和内存泄漏等问题。因此,动态内存分配在编写大型程序时非常有用。

目前我们学习有2种申请内存的方式

int a = 10;
int arr[] = {1, 2, 3, 4, 5};

通过上述两种方法实现的内存申请,申请到的内存空间固定,不够灵活。

接下来我们就来介绍介绍实现动态内存开辟的一系列函数:

malloc:

malloc()函数是C语言中用于分配动态内存的函数,具有以下原型:

void *malloc(size_t size);

该函数返回一个指向分配内存的指针,如果没有足够的内存空间,则返回NULL。参数size表示要分配的内存大小(以字节为单位),可以是任意整数或表达式。

使用malloc()函数时,需要注意以下几点:

  1. malloc()函数返回的指针指向的是一块连续的、未初始化的内存空间,可以通过类型转换将其转换为所需类型的指针。
  2. 在使用malloc()函数申请内存后,需要在不需要使用该内存时将其释放掉,否则会造成内存泄漏。
  3. 释放内存时使用free()函数,其原型为:
void free(void *ptr);

其中ptr是之前使用malloc()函数返回的指针,如果该指针为NULL,则free()函数不执行任何操作。

下面是一个使用malloc()函数分配和释放内存的示例代码:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p;
    p = (int*)malloc(sizeof(int) * 10); // 分配10个整数的内存空间
    if (p == NULL) {
        printf("Error: failed to allocate memory.\n");
        return 1;
    }
    for (int i = 0; i < 10; i++) {
        *(p + i) = i; // 对内存空间进行赋值
    }
    for (int i = 0; i < 10; i++) {
        printf("%d ", *(p + i)); // 输出内存空间的值
    }
    free(p); // 释放内存空间
    return 0;
}

在我们释放malloc开辟的空间时,我们存在通过free来释放——主动释放

也有通过程序退出后,malloc申请的空间也会被操作系统收回——被动释放。

因此为了安全起见,我们最好首选主动释放。

free:

free函数是一种用于释放在堆上分配的内存块的函数。该函数接受一个指向已分配内存块的指针作为参数,并将该内存块的使用计数减少1。如果减少后计数为0,则释放该内存块。这个函数是C库函数,定义在stdlib.h头文件中。在使用动态内存分配函数malloc或calloc分配内存后,必须使用free函数来释放内存,以避免内存泄漏。

如果我们需要释放掉一块我们自己开辟出来的空间,

我们可以进行一下操作:

free(p);
p = NULL;

要注意的是,free(p)之后的p = NULL必不可少!!!

calloc:

calloc() 函数是 C 语言标准库中的函数,用于动态分配内存空间,并将内存空间的每个字节初始化为0。

calloc() 函数的声明如下:

void* calloc(size_t num, size_t size);

其中,第一个参数 num 表示需要分配的元素个数,第二个参数 size 表示每个元素的大小。calloc() 函数返回指向分配的内存空间的指针,如果分配失败则返回 NULL。

calloc() 函数与 malloc() 函数类似,但是 calloc() 在分配内存空间时会将空间中的每个字节都初始化为0,而 malloc() 函数不会做这个操作。因此,如果需要使用一块全为0的内存空间,可以使用 calloc() 函数来分配内存。

使用 calloc() 函数的例子如下:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *ptr;
    int i, n;

    printf("Enter number of elements: ");
    scanf("%d", &n);

    ptr = (int*)calloc(n, sizeof(int));

    if (ptr == NULL)
    {
        printf("Memory not allocated.\n");
        exit(0);
    }
    else
    {
        printf("Memory successfully allocated using calloc.\n");

        for (i = 0; i < n; ++i)
        {
            printf("Enter element %d: ", i+1);
            scanf("%d", &ptr[i]);
        }

        printf("The elements you entered are: ");
        for (i = 0; i < n; ++i)
        {
            printf("%d ", ptr[i]);
        }
    }

    free(ptr);
    return 0;
}

在上述示例中,我们使用 calloc() 函数来分配一段大小为 n * sizeof(int) 的内存空间,并将其赋值给 ptr 指针。然后,我们输入 n 个整数,将其存储到分配的内存空间中,并输出这些整数,最后释放已分配的内存空间。

realloc:

C语言中的realloc函数是动态内存分配函数之一,其功能是用于重新分配已分配内存的大小,实现动态内存管理。

函数定义:

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

参数说明:

  • ptr:需要重新分配内存的指针地址。
  • size:重新分配的内存大小(字节长度)。

函数返回值:

  • 若重新分配成功,则返回新分配内存的指针,且原有指针指向的内存已经被释放;
  • 若重新分配失败,则返回NULL,原有指针所指向的内存不会被释放。

使用realloc函数时需要注意以下几点:

  • 如果ptr为NULL,则realloc的操作效果等价于malloc(size),即它将分配一个新的内存块并返回其指针;
  • 如果size为0,则realloc的操作效果等价于free(ptr),即它将释放原有的内存块;
  • 如果ptr不为NULL且size不为0,则realloc的操作效果是将原有内存块中的数据载入新分配的内存块中,原有内存块将被释放。
  • 如果realloc返回NULL,则说明重新分配内存失败,应该停止程序运行并进行相关的错误处理。

使用realloc函数时应该注意安全性和合理性,避免出现内存泄漏、内存重叠等问题。

 这里要注意的一点是:

我们在实现realloc开辟空间时,要清楚的知道realloc开辟空间也会失败,失败则会返回NULL,

所以我们应当创建一个临时指针变量,并作出判断再讲临时指针变量赋值到原指针处。

如果我们在使用realloc的时候,遇到“后面的空间被占用了”这种情况时。

此时realloc函数就会去找一块新的并且足够的空间,一次性将空间直接开辟好。

并将旧空间里的数据直接拷贝到新的空间,并且释放掉旧空间的数据和空间本身,

再返回新的空间的地址。 

注意事项:

在我们使用上述所讲的函数时,要注意一下:

1.注意千万不要对NULL直接进行解引用操作。

2.注意一定不要越界访问。

3.注意不可以仅仅使用free释放掉一部分。

4.对同一块空间多次释放。

5.不可以不释放空间。

6.注意不可以对非动态内存进行释放。

攻破经典易错题:

题目一:

//T1
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}

void test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

以上代码存在以下问题:

1.未对str进行free释放。

2.存在内存泄漏。

3.程序对NULL直接进行解引用操作,程序崩溃!

 存在以下两种方式进行修改:

1.利用二级指针进行修改:
//修改:
void GetMemory(char**p)
{
	*p = (char*)malloc(100);
}

void test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");

	printf(str);

    //释放str开辟的空间
	free(str);
	str = NULL;
}
2.利用返回指针的方式进行修改:
char* GetMemory(char* p)
{
	p = (char*)malloc(100);
	return p;
}

void test(void)
{
	char* str = NULL;

	str = GetMemory(str);
	strcpy(str, "hello world");

	printf(str);
	
	//释放str开辟的空间
	free(str);
	str = NULL;
}

以上的输出结果都为:

题目二:

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

void test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

以上存在以下问题:

函数吊桶结束后,str变为野指针。

此时str指向的空间已被释放 

那么此时返回栈空间出现问题!

题目三:

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}

void tests(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

以上存在的问题主要未使用free进行释放,

因此存在内存泄漏的问题。

题目四:

 

//题目四
void test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcat(str, "world");
		printf(str);
	}
}

这里存在的问题主要是 

1.对内存的非法访问。

2.free完后未将str设为NULL。

修改后应当如下:

 

//题目四
void test(void)
{
	char* str = (char*)malloc(100); 
	if (str != NULL)
	{
		strcpy(str, "hello");
		strcat(str, "world");
		printf(str);
	}
	else
	{
		perror("str->malloc");
		return;
	}
	free(str);
	str = NULL;
}

 柔性数组:

C语言中的柔性数组指的是一种特殊的数组,它可以在定义时不指定数组大小,在运行时动态分配内存。它的定义格式为:

struct my_struct {
    int size;
    int data[]; // 柔性数组
};

在这个定义中,data[]就是一个柔性数组,它没有指定大小,因此可以在运行时根据需要动态分配内存。注意,在柔性数组后面不能再有其他成员。

在使用柔性数组时,我们可以通过以下方式进行动态内存分配:

struct my_struct *p = malloc(sizeof(struct my_struct) + n * sizeof(int));
p->size = n;

这里我们通过malloc函数动态分配了一个结构体my_struct和一个大小为n的int数组,p指向结构体的首地址。由于柔性数组不占据结构体内存空间,因此可以使用sizeof(struct my_struct) + n * sizeof(int)来计算需要分配的内存大小。通过p->size来记录数组的大小。

在使用柔性数组时,我们可以像普通数组一样访问其中的元素,例如:

p->data[0] = 1;
p->data[1] = 2;
p->data[2] = 3;

需要注意的是,柔性数组只能用于结构体的最后一个成员,而且在定义时必须省略数组大小。同时,在使用柔性数组时,需要注意内存对齐问题,否则可能会导致程序出错。

 当空间不够时,可以使用以下代码:

struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 40);

if(ptr != NULL)
{    
    ps = ptr;
}
else
{
    perror("relloc");
    return 1;
}

 

柔性数组的优势:

  1. 更灵活的内存管理:柔性数组始终分配恰好足够的内存空间,不会浪费内存。同时,由于可以在运行时动态分配内存空间,可以避免在编译时限制数组长度。

  2. 更高效的代码:使用柔性数组可以减少代码中的重复代码和计算,因为只需要计算数组的实际长度一次,并且可以像普通数组一样方便地访问和操作。

  3. 更方便的代码编写:使用柔性数组可以使代码更加简洁和易于理解,因为不需要手动处理内存分配和释放,也不需要手动处理数组长度。

总的来说,柔性数组是一种方便、高效和灵活的内存管理工具,特别适用于需要动态分配数组空间的情况。

 总结:

以上就是关于动态内存的内容了,学习完后下来可以自己动手在VS中使用动态内存创建创建空间,熟练使用它们。

在接下来的blog中我们将会实现通讯录的创建运用动态内存管理。

所以在此基础上,我们最好可以进行复习结构体和动态内存的相关知识。

记住:

“坐而言不如起而行”

Action speak louder than words!

以下是我的Gitee:

dynamic_memory_question_CSDN/dynamic_memory_question_CSDN/test.c · 无双/test_c_with_X1 - Gitee.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1055776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

加拿大人工智能数据搜索平台【Secoda】完成1400万美元A轮融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于加拿大多伦多的人工智能数据搜索平台【Secoda】今日宣布已完成1400万美元A轮融资。 本轮融资由Craft Ventures领投&#xff0c;参与投资的投资机构有Abstract Ventures、现有投资者YCombi…

代码随想录算法训练营第五十天 |123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

一、123.买卖股票的最佳时机III 题目链接/文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;动态规划&#xff0c;股票至多买卖两次&#xff0c;怎么求&#xff1f; | LeetCode&#xff1a;123.买卖股票最佳时机III_哔哩哔哩_bilibili 思考&#xff1a; 至多买卖两次&…

【算法挨揍日记】day09——35. 搜索插入位置、69. x 的平方根

35. 搜索插入位置 35. 搜索插入位置 题目描述&#xff1a; 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 解题思…

德国云安全协作软件提供商【Rencore】完成800万美元融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于德国慕尼黑的云安全协作软件提供商Rencore今日宣布已完成800万美元融资。 本轮融资由UVC Partners领投。 该公司打算利用这笔资金进一步投资于其云协作治理产品的增长。 Rencore由Matthi…

机器学习7:逻辑回归

一、说明 逻辑回归模型是处理分类问题的最常见机器学习模型之一。二项式逻辑回归只是逻辑回归模型的一种类型。它指的是两个变量的分类&#xff0c;其中概率用于确定二元结果&#xff0c;因此“二项式”中的“bi”。结果为真或假 — 0 或 1。 二项式逻辑回归的一个例子是预测人…

公众号留言小程序有哪些?要免费的

为什么公众号没有留言功能&#xff1f;2018年2月12日之后直到现在&#xff0c;新注册公众号的运营者会发现一个问题&#xff1a;无论是个人还是企业的公众号&#xff0c;在后台都找不到留言功能了。这对公众号来说绝对是一个极差的体验&#xff0c;少了一个这么重要的功能&…

掌握这些技巧,让Excel批量数据清洗变得简单高效!

什么是数据清洗 数据清洗是指在数据处理过程中对原始数据进行筛选、转换和修正&#xff0c;以确保数据的准确性、一致性和完整性的过程。它是数据预处理的一部分&#xff0c;旨在处理和纠正可能存在的错误、缺失值、异常值和不一致性等数据质量问题。 为什么要数据清洗 Exce…

南京大学【软件分析】07 Interprocedural Analysis

文章目录 1. Motivation2. Call graph Construction&#xff08;CHA&#xff09;2.1 方法分派Method Dispatch2.2 方法签名method signature2.3 案例&#xff1a;查找Dispatch2.4 CHA2.5 通过CHA构造调用图 3. Interprocedural Control-Flow Graph4. Interprocedural Data-Flow…

DBA数据库运维-MySQL安装篇(glibc,源码)

1. MySQL数据库版本 版本说明社区版: MySQL Community Edition (GPL)1.可以看做是企业版的“广泛体验版(小白鼠版)"&#xff0c;未经各个专有系统平台的压力和性能测试 2.基于GPL协议发布&#xff0c;可以随意下载使用 3.没有任何官方技术支持服务企业版:MySQL Enterpris…

经典循环神经网络(一)RNN及其在歌词数据集上的应用

经典循环神经网络(一)RNN及其在歌词数据集上的应用 1 RNN概述 在深度学习兴起之前&#xff0c;NLP领域一直是统计模型的天下&#xff0c;例如词对齐算法GIZA&#xff0c;统计机器翻译开源框架MOSES等等。在语言模型方向&#xff0c;n-gram是当时最为流行的语言模型方法。n-gr…

YoloV8改进策略:SPD-Conv加入到YoloV8中,让小目标无处遁形

摘要 SPD-Conv是一种新的构建块,用于替代现有的CNN体系结构中的步长卷积和池化层。它由一个空间到深度(SPD)层和一个非步长卷积(Conv)层组成。 空间到深度(SPD)层的作用是将输入特征图的每个空间维度降低到通道维度,同时保留通道内的信息。这可以通过将输入特征图的每…

关于日志系统

目录 日志落地类&#xff08;工厂模式&#xff09;双缓冲区异步处理器缓冲区异步工作器 日志器类同步日志器异步日志器构造日志器构造局部日志器构造全局日志器日志器管理器&#xff08;单例模式&#xff09; 日志宏&全局接口&#xff08;代理模式&#xff09; 源码&#x…

使用 cURL 发送 HTTP 请求: 深入探讨与示例

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Vue3之Suspense

<Suspense> 是一个内置组件&#xff0c;用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成&#xff0c;并可以在等待时渲染一个加载状态。 我们可以看到官网并不推荐我们使用它&#xff0c;目前仍处于测试中。 他用于加…

【算法系列篇】哈希表

文章目录 前言1. 两数之和1.1 题目要求1.2 做题思路1.3 Java代码实现 2. 判断是否为字符重排2.1 题目要求2.2 做题思路2.3 Java代码实现 3. 存在重复元素3.1 题目要求3.2 做题思路3.3 Java代码实现 4. 存在重复元素II4.2 题目要求4.2 做题思路4.3 Java代码实现 5. 字母异位词分…

Guitar Pro 8 .1全新功能介绍及2023官方特惠优惠券

《中国好声音》节目诞生10年多热度不减&#xff0c;每一季都有籍籍无名的学员成为万众瞩目的新星。怎么像他们一样把爱好变成事业&#xff1f;带着这个问题在不断的探寻中找到了答案&#xff0c;那就是要在有限的时间里比别人做效率更高的事。所谓“工欲善其事&#xff0c;必先…

超百家上市公司抛出回购、增持计划

9月以来&#xff0c;多家上市公司披露回购方案或增持计划&#xff0c;持续向市场传递积极信号&#xff0c;以真金白银提振市场信心。 Wind统计显示&#xff0c;截至9月28日&#xff0c;9月以来已有77家上市公司披露回购预案&#xff0c;其中多家公司发布超过亿元的回购计划。 …

WINDOWS与LINUX的文件文件共享

打开VMware: 点击虚拟机->点击设置 出来虚拟机设置&#xff0c;咱们点击选项->有个共享文件夹点击->选择总是启用->点击添加 下一步 此处选择一个windows下与虚拟机共享的一个目录 最后确定就ok了 那怎么在虚拟机Linux中访问共享文件呐 在文件的其他位置选择计…

SpringMVC如何处理表单提交与文件上传

SpringMVC处理表单提交与文件上传 SpringMVC是一个流行的Java框架&#xff0c;用于构建Web应用程序。它提供了强大的功能来处理表单提交和文件上传操作。本文将深入探讨SpringMVC如何处理这些常见的Web任务&#xff0c;以及如何使用示例代码来实现它们。 表单提交处理 表单提…