C++基础(三) —— 内存分配

news2024/11/15 11:41:49

文章目录

  • 概念
    • 01 物理地址内存的分配与释放
    • 02 虚拟用户进程空间内存的分配与释放
  • 03 allocator模板类
  • 04 new delete
  • 05 malloc free
  • 06 strcpy 与 memcpy 与 memset
    • strcpy
    • memcpy
    • memset


概念

01 物理地址内存的分配与释放

主要采用链表结构

使用了一个名叫page的结构体管理物理内存,结构体中包括了页的大小、页的状态以及指向相邻页的指针。

Linux内核使用这些指针来构建了一个逻辑链表,当需要分配内存的时候,会从链表中查找第一个空闲页并把它标记为已使用。

释放内存的时候,会把相应的页标记为空闲,并把它插入到链表对应的位置

02 虚拟用户进程空间内存的分配与释放

C++语言层次
智能指针 栈上的对象出作用域自动析构 自动管理内存的分配与释放
new delete

C语言层次
malloc free

系统调用
sbrk brk
管理进程的堆(heap)空间。

mmap

03 allocator模板类

#include <iostream>
#include <memory>

int main() {
    std::allocator<int> allocator;

    // 在堆上动态的分配大小为5*sizeof(int)的内存
    int* ptr = allocator.allocate(5);
    int* ptrnum = new int[5];
    int abc[5];  // abc也是指针

    // 构造对象
    for (int i = 0; i < 5; ++i) {
        allocator.construct(ptr + i, i);
        allocator.construct(ptrnum + i, i);
        allocator.construct(abc + i, i);
    }

    // 访问对象
    for (int i = 0; i < 5; ++i) {
        std::cout << ptr[i] << " ";
        std::cout << ptrnum[i] << " ";
        std::cout << abc[i] << " ";
    }
    std::cout << std::endl;

    // 销毁对象
    for (int i = 0; i < 5; ++i) {
        allocator.destroy(ptr + i);
        allocator.destroy(ptrnum + i);  
    }

    // 释放内存
    allocator.deallocate(ptr, 5);
    delete ptrnum;
    ptrnum = nullptr;

    return 0;
}

04 new delete

堆上分配内存

T* ptr = new T; // 分配单个对象的内存并构造对象
T* arr = new T[N]; // 分配对象数组的内存并构造对象
delete ptr; // 释放单个对象的内存并调用析构函数
delete[] arr; // 释放对象数组的内存并调用每个对象的析构函数

new 运算符在堆上分配的内存可以通过相应的 delete 运算符来释放,从而销毁对象并释放内存。

动态:
为了简化内存管理,C++11 引入了智能指针(如 std::shared_ptr 和 std::unique_ptr),它们提供了更安全和更方便的内存管理机制。智能指针可以自动管理动态分配的内存,避免显式使用 delete,从而减少了内存泄漏和资源管理的错误。

05 malloc free

堆上分配内存
void* malloc(size_t size);
malloc() 返回一个指向分配内存块的指针,该内存块大小为 size 字节。分配的内存块在堆上连续存储,可以手动管理其使用和释放。

void free(void* ptr);
free输入的是指向内存块的指针

问1:malloc(1) 会分配多大的虚拟内存
malloc() 在分配内存的时候,并不是老老实实按用户预期申请的字节数来分配内存空间大小,而是会预分配更大的空间作为内存池。
具体会预分配多大的空间,跟 malloc 使用的内存管理器有关系,我们就以 malloc 默认的内存管理器(Ptmalloc2)来分析。

#include <stdio.h>
#include <malloc.h>

int main() {
  printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
  
  //申请1字节的内存
  void *addr = malloc(1);
  printf("此1字节的内存起始地址:%x\n", addr);
  printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
 
  //将程序阻塞,当输入任意字符时才往下执行
  getchar();

  //释放内存
  free(addr);
  printf("释放了1字节的内存,但heap堆并不会释放\n");
  
  getchar();
  return 0;
}

程序输出:
此1字节的内存起始地址d73010

之后,使用cat /proc/…/maps查看内存分布情况。我在 maps 文件通过此 1 字节的内存起始地址过滤出了内存地址的范围。

[root@xiaolin ~]# cat /proc/3191/maps | grep d730
00d73000-00d94000 rw-p 00000000 00:00 0                                  [heap]

可以看到,堆空间的内存地址范围是 00d73000-00d94000,这个范围大小是 132KB,也就说明了 malloc(1) 实际上预分配 132K 字节的内存。
但是程序里打印的内存起始地址是 d73010,而 maps 文件显示堆内存空间的起始地址是 d73000,为什么会多出来 0x10 (16字节)呢?这个问题在问2中。

问2:free() 函数只传入一个内存地址,为什么能知道要释放多大的内存?
malloc 返回给用户态的内存起始地址比进程的堆空间起始地址多了 16 字节,这个多出来的 16 字节就是保存了该内存块的描述信息,比如有该内存块的大小。
在这里插入图片描述

这样当执行 free() 函数时,free 会对传入进来的内存地址向左偏移 16 字节,然后从这个 16 字节的分析出当前的内存块的大小,自然就知道要释放多大的内存了。

06 strcpy 与 memcpy 与 memset

内存数据拷贝函数
strcpy 和 memcpy 是 C 语言中的库函数,用于内存数据的拷贝操作。它们有不同的使用方式:
memset 是 C 语言中的库函数,用于将一块内存区域设置为指定的值

strcpy是提供了对字符串的复制,memcpy是内存的复制,对复制的内容没有限制,使用范围更广!!!
strcpy和memcpy主要有以下3方面的区别。
复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。

strcpy

char* strcpy(char* dest, const char* src);
strcpy 用于将一个以 null 结尾的字符串从源地址 src 复制到目标地址 dest,并返回目标地址的指针。
实例

char source[] = "Hello, World!";
char destination[20];
strcpy(destination, source);

memcpy

void* memcpy(void* dest, const void* src, size_t n);
memcpy 用于将源地址 src 的前 n 字节的数据复制到目标地址 dest,无返回值。
示例:

int source[] = {1, 2, 3, 4, 5};
int destination[5];
memcpy(destination, source, sizeof(source));

需要注意的是,使用这两个函数时,需要确保目标地址 dest 具有足够的空间来容纳要复制的数据。

一个数据报文的构成实例:

   char buffer[kBufferSize];
    memset(buffer, 0, sizeof(buffer));

    // Header
    buffer[0] = 0x5A;
    buffer[1] = 0xA5;

    // CMD_ID
    const char* CMD_ID = "00000000000000001";
    memcpy(buffer+2, CMD_ID, strlen(CMD_ID));

    // Frame_Type
    buffer[19] = 0xD1;

    // Packet_Type
    buffer[20] = 0x01;

    // Frame_No
    buffer[21] = 0x01;

    // Sub_Packet_Type
    buffer[22] = 0x00;
    buffer[23] = 0x00;

    // Time_Stamp
    srand(time(NULL));
    int time_stamp = rand() % 1000000;
    memcpy(buffer+24, &time_stamp, sizeof(int));

    // X
    float x = 123.456f;
    memcpy(buffer+28, &x, sizeof(float));

    // Y
    float y = 789.012f;
    memcpy(buffer+32, &y, sizeof(float));

    // Z
    float z = 345.678f;
    memcpy(buffer+36, &z, sizeof(float));

    // Version
    int version = 1;
    memcpy(buffer+40, &version, sizeof(int));

    // CRC16
    uint16_t crc = 0;
    for (int i = 0; i < 42; i++) {
        crc += (uint8_t)buffer[i];
    }
    memcpy(buffer+42, &crc, sizeof(uint16_t));

    // End
    buffer[44] = 0x96;

memset

memset 是 C 语言中的库函数,用于将一块内存区域设置为指定的值
void* memset(void* ptr, int value, size_t num);
memset 将指针 ptr 指向的内存区域的前 num 字节都设置为值 value。它返回指向 ptr 的指针。
它可以用来快速地将一块内存区域设置为特定的值,例如将数组全部设置为零或将某个标记数组全部设置为特定的标记值。

示例:

int array[5];
memset(array, 0, sizeof(array));  // 将数组全部设置为零


char str[10];
memset(str, 'A', sizeof(str));  // 将 str 数组的每个元素都设置为字符 'A'
for (int i = 0; i < sizeof(str); i++) printf("%c ", str[i]);

需要注意的是,memset 的参数 value 是一个整数,会被解释为无符号字符。因此,如果需要将内存区域设置为非零的特定值,需要确保该值在无符号字符的范围内。

在 C++ 中,也可以使用 std::fill 算法或使用初始化语法来实现相似的功能,以提供更安全和易用的方式来初始化和设置内存。

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

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

相关文章

基于nodejs实现text/event-stream简单应用案例,SSE

基于nodejs实现text/event-stream简单应用案例&#xff0c;SSE text/event-stream代码实现服务器端前端 效果 text/event-stream 是一种用于服务器向客户端推送事件的媒体类型&#xff08;Media Type&#xff09;。它是基于 HTTP 协议的一种流式传输技术&#xff0c;也被称为 …

揭秘新一代云数仓技术架构与最佳实践

从传统数仓到湖仓一体&#xff0c;历经三十多年发展&#xff0c;技术的浪潮快速迭代&#xff0c;以云原生数仓为中心的现代数据栈时代已然到来。 背后的核心的原因在于&#xff0c;企业正在加速走向数字化、智能化&#xff0c;对数据的应用也提出了全新要求&#xff0c;特别是对…

每日一练 | 华为认证真题练习Day55

1、RSTP协议配置BPDU中的Flag字段使用了哪些STP协议未使用的标志位&#xff1f;&#xff08;多选&#xff09; A. Agreement B. TCA C. TC D. Proposal 2、RSTP中Backup端口可以替换发生故障的根端口。 A. 对 B. 错 3、如下图所示的网络&#xff0c;在RouterA设备里面存在…

更适合中国打工人体质的报表工具,零代码自动生成老板满意模板!

“中国职场上大家公认最头疼的是什么&#xff1f;” “加班&#xff1f;裁员&#xff1f;薪资&#xff1f;” “一切的根源来源于哪&#xff1f;” “是因为做大大小小报表加班到深夜、是同事都在卷报表制作有人只能被动裁员&#xff0c;也是千篇一律的报表汇报决定了这职业…

FreeRTOS学习笔记(五)——应用开发(三)

文章目录 0x01 软件定时器应用场景定时器精度运作机制软件定时器控制模块函数接口xTimerCreate()prvInitialiseNewTimer()xTimerStart()xTimerGenericCommand()xTimerStartFromISR()xTimerStop()xTimerStopFromISR()xTimerDelete()软件定时器任务创建以及执行原理软件定时器实验…

如何优化档案库房管理?一招学会轻松提升效率

在现代企业运营中&#xff0c;档案库房扮演着重要的角色&#xff0c;承载着大量宝贵的纸质档案资料。这些档案包含着企业的历史、客户信息、法律文件等重要数据&#xff0c;对于企业的正常运转和决策制定至关重要。然而&#xff0c;传统的档案库房管理方式存在一系列的挑战和难…

深度刨析指针Advanced 1

作者主页&#xff1a;paper jie的博客_CSDN博客-C语言,算法详解领域博主 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《系统解析C语言》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白…

浅谈智能微电网供电系统的谐波治理

摘要&#xff1a;智能微电网供电系统的特性容易引发谐波&#xff0c;而谐波导致电力损耗加大&#xff0c;降低供电质量。本文从谐波的产 生原因和危害做出详细阐述&#xff0c;并结合智能微电网提出了治 理谐波的方法和措施。 关键词&#xff1a;智能微电网&#xff1b;谐波危害…

手术麻醉信息管理系统源码:全面监护,支持多设备采集

手术、麻醉是医院非常重要的一个组成部分&#xff0c;外科医生为病人进行手术的好与坏直接会危及到病人的生命&#xff0c;所以病人在手术麻醉过程中每一个环节都是非常重要的。随着现在高科技的发展&#xff0c;大量的医疗监视辅助仪器设备在手术过程中也得到广泛的应用&#…

Jenkins使用Docker(Podman)安装部署web应用

https://blog.csdn.net/onePageKownAll/article/details/128182290 https://blog.csdn.net/weixin_45647685/article/details/127825728 https://zhuanlan.zhihu.com/p/562495608 最终效果&#xff1a;在jenkins对某个项目进行构建&#xff0c;jenkins先通过git拉取最项目的…

MySQL基本知识复习补充

MySQL基本知识复习补充 SQL分类 DDL&#xff1a;数据定义语言。create、alter、drop、rename、truncate(清空表) DML&#xff1a;数据操作语言。insert、delete、update、select DCL&#xff1a;数据控制语言。commit、rollback、savepoint、grant、revoke 因为查询语句使…

最后机会!桥接 LAND 可以获得返还奖励!

经过 1 年的服务&#xff0c;The Sandbox 向我们的社区成员分发了超过 40 万 SAND&#xff0c;LAND 桥接返还奖励计划即将结束。 该计划是为了减轻土地持有者从以太坊桥接到 Polygon 的成本。每块土地的桥接都可获得 10 SAND 的奖励。 最后机会&#xff01;再次呼吁各位桥接 LA…

从小白到大神之路之学习运维第36天---第三阶段---mysql数据库之企业级mysql部署方案

第三阶段基础 时 间&#xff1a;2023年6月8日 参加人&#xff1a;全班人员 内 容&#xff1a; 企业级mysql部署方案 目录 企业级MySQL部署方案 企业级mysql部署主要步骤 Linux系统初始化设置&#xff08;做公司服务器&#xff09; 企业级MySQL高可用集群部署方案 企业…

Keysight是德MSOS604A高清晰度示波器1 GH

Keysight是德MSOS604A S系列示波器配备 6 GHz 存储器、15 英寸 XGA 电容触摸屏和 10 位模数转换器。主要特性与技术指标 1 GHz带宽和平坦的频率响应确保高信号保真度 20 GSa/s 最大采样率 10 位模数转换器&#xff08;ADC&#xff09;保证高垂直分辨率 低噪声前端&#xff…

【React】setState原理,SCU,不可变对象,Ref,受控组件,高阶组件

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 setState原理setState异步更新 SCU不可变对象RefRef获取DOMRef获取组件 受控组件高阶组件(HOC)作…

告别 Spread 运算符:使用默认 Composer

在 JavaScript 中处理对象时&#xff0c;通常需要为空的strings// objects、或属性设置默认值。在处理嵌套对象时&#xff0c;这会变得更加复杂并且需要复杂的编程逻辑。然而&#xff0c;有了“ default-composer ”库&#xff0c;这项任务变得简单易行。arraysnullundefined …

零瑕疵全核心,这份RocketMQ笔记仅用330页直接封神

RocketMQ天生为金融互联网领域而生&#xff0c;追求高可靠、高可用、高并发、低延迟 RocketMQ在阿里集团也被广泛应用在订单&#xff0c;交易&#xff0c;充值&#xff0c;流计算&#xff0c;消息推送&#xff0c;日志流式处理&#xff0c;binglog分发等场景 其主要功能有&am…

怎样快速选择正确的可视化图表?

数据可视化的图表类型十分丰富&#xff0c;好的图表可以有效、清晰地呈现数据的信息。对于用户而言&#xff0c;选择正确的图表是十分关键的&#xff0c;不仅可以达到“一图胜千言”的效果&#xff0c;而且会直接影响分析的结果。 用户选择正确的数据可视化图表前&#xff0c;…

银河麒麟服务器ZYJ操作系统,文件储存inode节点占用根目录/空间满了解决办法

【问题描述】 今天发现业务系统运转异常&#xff0c;df -h 检查服务器发现磁盘根目录空间未满&#xff0c;df -i 检查发现根目录文件满了。inode节点中&#xff0c;记录了文件的类型、大小、权限、所有者、文件连接的数目、创建时间与更新时间等重要的信息&#xff0c;还有一个…

蓝奥声核心技术分享——无线同步数据传输技术

1、技术背景 无线同步数据传输技术指基于对目标场景状态变化的协同感知而获得触发响应并进行智能决策&#xff0c;属于蓝奥声核心技术--边缘协同感知(EICS&#xff09;技术的关键支撑性技术之一。该项技术主要涉及网络服务节点与目标对象设备之间的无线通信方式及服务机制与流…