【C进阶】分析 C/C++程序的内存开辟与柔性数组(内有干货)

news2025/1/23 2:06:54

前言:

        本文是对于动态内存管理知识后续的补充,以及加深对其的理解。对于动态内存管理涉及的大部分知识在这篇文章中 ---- 【C进阶】 动态内存管理_Dream_Chaser~的博客-CSDN博客

        本文涉及的知识内容主要在两方面:

  • 简单解析C/C++程序的内存开辟
  • 分析柔性数组的知识点

目录

前言:

C/C++程序的内存开辟区域📍

1.栈区(stack)

2. 堆区(heap)

3. 数据段(静态区)(static)

4. 代码段

柔性数组💨

柔性数组的特点

柔性数组的使用

柔性数组的优势 


C/C++程序的内存开辟区域📍

1.栈区(stack)

   在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2. 堆区(heap)

        一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
配方式类似于链表。

3. 数据段(静态区)(static)

        存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段

        存放函数体(类成员函数和全局函数)的二进制代码。      
📃内存区域划分图:

        📚有了这幅图,我们就可以更好的理解在C语言初识中讲的 static 关键字修饰局部变量的例子了。
  •         实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁
  •         但是被static修饰的变量存放在数据段(静态区)数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。
        

柔性数组💨

        柔性数组(Flexible Array)是一种在编程语言中用于表示可变长度数组的数据结构。它允许在声明数组时不指定数组的长度,而是在运行时根据需要动态分配内存空间

        柔性数组最常见的应用是在C语言中。在C语言中,柔性数组是一种特殊的结构体成员,其长度可以在结构体实例化之前或之后进行动态调整。

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

啥意思呢,用代码说话

        在vs编译器环境下,以下两种写法均支持

        第一种写法(使用空方括号[ ])是更常见和更符合标准的写法,可以在大多数编译器环境下使用。

struct S
{
	int n;
	char c;
	int arr[];//柔性数组成员
};

        第二种写法(指定大小为0)在某些特定的编译器(vs)扩展中可能有效,但不具有通用性和可移植性

struct S
{
	int n;
	char c;
	int arr[0];//柔性数组成员(指定大小)
};

柔性数组的特点

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

typedef struct st_type
{
 int i;//必须至少一个其他成员
 int a[0];//👈柔性数组成员
}type_a;

错误写法:

struct SA
{
	int arr[];//柔性数组成员
};

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

struct S
{
	int n;
	char c;
	int arr[];//柔性数组成员
};
int main()
{
	printf("%d", sizeof(struct S));
}

🚩8

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

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{
    //arr需要开辟的空间是10个int
    //                     n与c需要开辟的内存           arr数组需要开辟的内存空间
    //                           8                         40
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));


	return 0;
}

图解:

柔性数组的使用

代码实现🎯

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

//柔性数组
struct S
{
	int n;
	char c;
	int arr[];//柔性数组成员
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
	if (ps == NULL)
	{
		printf("%s\n",strerror(errno));
		return 1;
	}
	//使用
	ps->n = 100;
	ps->c = 'w';
	int i = 0;
	for ( i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}

	for (i = 0; i < 10; i++)
	{
		printf("%d\n", ps->arr[i]);
	}
	//调整arr数组的大小(注意这是重新改变大小,不是说在原来空间后面增加,比如说原来是48,那么现在就是88)
	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
	if (ptr == NULL)
	{
		printf("%s\n,sterror(error)");
		return 1;
	}
	else
	{
		ps = ptr;
	}
	//再次使用
	//....

	//释放
	free(ps);
	ps = NULL;

	printf("%d\n", sizeof(struct S));

	return 0;
}

调试一下,看看空间大小如何

malloc的空间,58-30=28(16进制),换成十进制刚好为40,刚好是10int的字节大小

柔性数组的优势 

方案一:柔性数组的方案

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

//柔性数组
struct S
{
	int n;
	char c;
	int arr[];//柔性数组成员
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
	if (ps == NULL)
	{
		printf("%s\n",strerror(errno));
		return 1;
	}
	//使用
	ps->n = 100;
	ps->c = 'w';
	int i = 0;
	for ( i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}

	for (i = 0; i < 10; i++)
	{
		printf("%d\n", ps->arr[i]);
	}
	//调整arr数组的大小(注意这是重新改变大小,不是说在原来空间后面增加,比如说原来是48,那么现在就是88)
	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
	if (ptr == NULL)
	{
		printf("%s\n,sterror(error)");
		return 1;
	}
	else
	{
		ps = ptr;
	}
	//再次使用
	//....

	//释放
	free(ps);
	ps = NULL;

	printf("%d\n", sizeof(struct S));

	return 0;
}

描述:

malloc 1次 ,free 1次

方案二:结构中指针方案

定义一个指针变量指向一块新的区域,像下面这样

图解: 

 代码实现✨

struct S
{
	int n;
	char c;
	int* arr;
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	int* ptr = (int*)malloc(10 * sizeof(int));
	if (ptr == NULL)
	{
		perror("malloc2");
		return 1;
	}
	else
	{
		ps->arr = ptr;
	}
	//使用
	ps->n = 100;
	ps->c = 'w';
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ",ps->arr[i]);
	}
	//扩容 - 调整arr的大小
	ptr = realloc(ps->arr,20*sizeof(int));
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		ps->arr = ptr;
	}
	//使用

	//释放
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;

	return 0;
}

描述: 

malloc 2次,free 2次

        上面的方案一和方案二谁的优势更优呢,显然是方案一

个人的理解:

        从写代码的方面来说,malloc越多,free的越多,空间的维护难度就更高,所以

        方案一实现起来更加简单,空间维护更加简单,容易维护空间,不易出错

        方案二来说,一旦忘记free一次的话,可能会导致内存泄漏等问题,所以维护难度加大,容易出错

        还有区别就是:

        在堆区上申请内存的话,每一次malloc申请的空间,第二次malloc申请的空间跟第一次申请的空间在地址上不一定是连续的,随机性很高,随着malloc申请的数量越多,那么在内存和内存之间留下的空隙就会越多,这种空隙我们叫做为内存碎片

         因为这种内存碎片空间大小比较小一些,那么未来可能被利用到的概率就会比较低一些,所以说,内存碎片越多,那么内存利用率就会越低

总结

①方案一:malloc次数少,内存碎片就会较少,内存的使用率就较高一些

②方案二:malloc次数多,内存碎片就会增多,内存的使用率就下降了

上述 方案 1 和 方案 2 可以完成同样的功能,但是 方法 1 的实现有两个好处:
⛳第一个好处是: 方便内存释放
        如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以, 如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
⛳第二个好处是: 这样有利于访问速度 .
         连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

         本文结束,如有错误,欢迎指正,感谢支持!

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

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

相关文章

CSS宽度问题

一、魔法 为 DOM 设置宽度有哪些方式呢&#xff1f;最常用的是配置width属性&#xff0c;width属性在配置时&#xff0c;也有多种方式&#xff1a; widthmin-widthmax-width 通常当配置了 width 时&#xff0c;不会再配置min-width max-width&#xff0c;如果将这三者混合使…

【数据结构】——排序的相关习题

目录 一、选择填空判断题题型一&#xff08;插入排序——直接插入排序&#xff09;题型二&#xff08;插入排序——折半插入排序&#xff09;题型三&#xff08;插入排序——希尔排序&#xff09;题型四&#xff08;交换排序——冒泡排序&#xff09;题型五&#xff08;交换排序…

Java注解以及自定义注解

Java注解以及自定义注解 要深入学习注解&#xff0c;我们就必须能定义自己的注解&#xff0c;并使用注解&#xff0c;在定义自己的注解之前&#xff0c;我们就必须要了解Java为 我们提供的元注解和相关定义注解的语法。 1、注解 1.1 注解的官方定义 注解是一种元数据形式。…

OLED透明屏导航:驾驶安全的未来趋势

在不断发展的科技领域中&#xff0c;OLED透明屏技术的出现为导航系统带来了革命性的变革。 今天&#xff0c;尼伽将深入探讨OLED透明屏导航的技术原理和应用前景&#xff0c;展示其在驾驶安全方面的优势&#xff0c;并引用最新的数据、报告和行业动态&#xff0c;以增加可信度…

无涯教程-JavaScript - CUMIPMT函数

描述 CUMIPMT函数返回start_period和end_period之间的贷款累计利息。 语法 CUMIPMT (rate, nper, pv, start_period, end_period, type)争论 Argument描述Required/OptionalRateThe interest rate.RequiredNperThe total number of payment periods.RequiredPvThe present …

产品经理学习笔记

产品文档之BRD、MRD和PRD - 知乎BRD、MRD和PRD一起被认为是从市场到产品需要形成的标准规范文档&#xff1a; 1、BRD&#xff08;Business Requirement Document&#xff09;&#xff0c;商业需求文档&#xff0c;是一份产品商业论证报告&#xff0c;基于商业目标或价值所描述的…

RocketMQMessageListener使用错误问题分析与排查

背景 RocketMQ与SpingBoot相结合可以大大降低我们开发的复杂度&#xff0c;但是最近在一个新项目中使用RocketMQMessageListener 监听消息&#xff0c;导致消费者启动失败&#xff0c;提示该消费组已经被创建了&#xff0c;请重新申请一个消费者组。 Caused by: org.apache.r…

java并发编程 ConcurrentLinkedQueue详解

文章目录 1 ConcurrentLinkedQueue是什么2 核心属性详解3 核心方法详解3.1 add(E e)3.2 offer(E e)3.3 poll()3.4 size()3.5 并发情况分析 4 总结 1 ConcurrentLinkedQueue是什么 ConcurrentLinkedQueue是一个无界的并发队列&#xff0c;和LinkedBlockingQueue相比&#xff0c…

【新版】系统架构设计师 - 软件架构设计<轻量级架构>

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 软件架构设计&#xff1c;轻量级架构&#xff1e;考点摘要轻量级架构表示层业务逻辑层持久层数据库 SSH与SSMHibernate与Mybatis 架构 - 软件架构设计&#xff1c;轻量级架构&#xff1e; 考点摘…

九)Stable Diffussion使用教程:ControlNet

在 ControlNet 出现之前&#xff0c;基于扩散模型的 AI 绘画是极难控制的&#xff0c;因为扩散的过程充满了随机性。 如果只是纯粹自娱自乐&#xff0c;这种随机性并不会带来多大困扰&#xff1b; 但在产业化上应用就难以普及了&#xff0c;因为随机性直接导致的就是缺乏稳定…

【C++漂流记】一文搞懂类与对象中的对象特征

在C中&#xff0c;类与对象是面向对象编程的基本概念。类是一种抽象的数据类型&#xff0c;用于描述对象的属性和行为。而对象则是类的实例&#xff0c;具体化了类的属性和行为。本文将介绍C中类与对象的对象特征&#xff0c;并重点讨论了对象的引用。 文章目录 一、构造函数和…

【云原生进阶之PaaS中间件】第二章Zookeeper-1-综述

1 Zookeeper基础 1.1 简介 ZooKeeper 是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;它包含一个简单的原语集&#xff0c;分布式应用程序可以基于它实现同步服务&#xff0c;配置维护和命名服务等。 Zookeeper是hadoop的一个子项目&#xff0c;其发…

职场工作与生活

序言&#xff1a; 和很多在CSDN的博主一样&#xff0c;大家在工作之后就很少或者是不再回到CSDN&#xff0c;确实自己也一年多没上了。因为可能当初大家在这就是为了记录和分享当初自己学习技术的东西。而大家走出象牙塔开始工作后&#xff0c;发生了很大的转变。在国内…

2核2G3M带宽服务器腾讯云和阿里云价格、性能对比

2核2G云服务器可以选择阿里云服务器或腾讯云服务器&#xff0c;腾讯云轻量2核2G3M带宽服务器95元一年&#xff0c;阿里云轻量2核2G3M带宽优惠价108元一年&#xff0c;不只是轻量应用服务器&#xff0c;阿里云还可以选择ECS云服务器u1&#xff0c;腾讯云也可以选择CVM标准型S5云…

堆相关例子-最大线段重合问题

问题描述 给定很多线段&#xff0c;每个线段都有两个数[start, end]&#xff0c; 表示线段开始位置和结束位置&#xff0c;左右都是闭区间 规定&#xff1a; 1&#xff09;线段的开始和结束位置一定都是整数值 2&#xff09;线段重合区域的长度必须>1 返回线段最多重合…

Alibaba(获得店铺的所有商品) API 接口

为了进行电商平台 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个alibaba应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载alibaba API的SDK并掌握基本的API基础知识和调用 4&#xff09;利…

appium+jenkins实例构建

自动化测试平台 Jenkins简介 是一个开源软件项目&#xff0c;是基于java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 前面我们已经开完测试脚本&#xff0c;也使用bat 批处…

Linux TCP和UDP协议

目录 TCP协议TCP协议的面向连接1.三次握手2.四次挥手 TCP协议的可靠性1.TCP状态转移——TIME_WAIT 状态TIME_WAIT 状态存在的意义&#xff1a;&#xff08;1&#xff09;可靠的终止TCP连接。&#xff08;2&#xff09;让迟来的TCP报文有足够的时间被识别并被丢弃。 2.应答确认、…

linux 堆探索

堆的虚拟地址是连续的&#xff0c;是brk来分配&#xff0c;brk是一个指针指向堆顶的指针&#xff0c;并且是可以复用的&#xff0c;但是只有在堆顶空闲128k时&#xff0c;才收缩&#xff0c;也就是说&#xff0c;为了减少page_fault&#xff0c;可重用&#xff0c;开销小的特点…

消息队列--入门篇

消息队列–入门篇 目录 消息队列--入门篇如何学习一门新技术&#xff1f;消息队列的组件大体介绍一下producerproducer groupnameSrvbrokerbroker clusterconsumer和consumer groupTopicTag 总结 如何学习一门新技术&#xff1f; 学习任何知识需要有一个整体的认识&#xff0c…