【C/C++】——小白初步了解——内存管理

目录

1. C/C++内存分布

代码区(Code Segment):

数据区(Data Segment):

堆区(Heap):

栈区(Stack):

常量区(Constant Segment):

2. C语言中动态内存管理方式

1.malloc(size_t size):

2.calloc(size_t nmemb, size_t size):

3.*realloc(void ptr, size_t size):

4.*free(void ptr):

3. C++中动态内存管理

1.new:

2.delete:

4. operator new与operator delete函数

5. new和delete的实现原理

6. 定位new表达式(placement-new)

7. 常见面试题

1.解释C++中new和malloc的区别

2.什么是内存泄漏?如何避免?

3.解释C++中的RAII(Resource Acquisition Is Initialization)

4.解释栈区和堆区的区别

5.如何实现自己的内存池?


1. C/C++内存分布

一个典型的C/C++程序在内存中的布局如下:

  1. 代码区(Code Segment):

    • 存储程序的机器指令,由编译器生成。
    • 该区域通常是只读的,以防止程序在运行时修改自身的指令。
    • 代码区在程序加载时被操作系统加载到内存中。
  2. 数据区(Data Segment):

    • 存储全局变量和静态变量,包括已初始化和未初始化的变量。
    • 数据区又分为两部分:
      • 已初始化数据区(Initialized Data Segment): 存储程序中已初始化的全局变量和静态变量。
      • 未初始化数据区(Uninitialized Data Segment or BSS): 存储未初始化的全局变量和静态变量,程序启动时这些变量会被初始化为0。
  3. 堆区(Heap):

    • 用于动态内存分配,大小不固定,可以在程序运行时动态地增长或缩小。
    • 由程序员手动管理内存的分配和释放。常用的函数有 malloc()free()
    • 堆区的内存分配效率较低,但灵活性高。
  4. 栈区(Stack):

    • 用于函数调用时的临时存储,包括函数的局部变量、参数和返回地址。
    • 栈区的内存由编译器自动分配和释放,具有后进先出的特点。
    • 栈区内存分配效率高,但大小有限,通常由操作系统决定。
  5. 常量区(Constant Segment):

    • 存储常量数据,如字符串字面量和常量变量。
    • 通常也是只读的,以保护常量数据不被修改。

2. C语言中动态内存管理方式

在C语言中,动态内存管理主要通过以下几个函数实现:

1.malloc(size_t size):

  • 功能:分配指定大小的字节,并返回一个指向这块内存的指针。

  • 特点:分配的内存未初始化,内容是随机的。

  • #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int *arr = (int *)malloc(10 * sizeof(int));
        if (arr == NULL) {
            printf("Memory allocation failed\n");
            return 1;
        }
        for (int i = 0; i < 10; i++) {
            arr[i] = i;
        }
        for (int i = 0; i < 10; i++) {
            printf("%d ", arr[i]);
        }
        free(arr);
        return 0;
    }
    

    2.calloc(size_t nmemb, size_t size):

  • 功能:分配nmemb个元素,每个元素size字节,并初始化所有分配的字节为0。

  • 特点:分配的内存被初始化为0,适合分配需要清零的数组。

  • #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int *arr = (int *)calloc(10, sizeof(int));
        if (arr == NULL) {
            printf("Memory allocation failed\n");
            return 1;
        }
        for (int i = 0; i < 10; i++) {
            printf("%d ", arr[i]);
        }
        free(arr);
        return 0;
    }
    

    3.*realloc(void ptr, size_t size):

  • 功能:调整之前分配的内存块的大小。

  • 特点:如果新大小大于原大小,新分配的内存区域中的内容是不确定的;如果新大小小于原大小,超出的内容将被丢弃。

  • #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int *arr = (int *)malloc(5 * sizeof(int));
        if (arr == NULL) {
            printf("Memory allocation failed\n");
            return 1;
        }
        for (int i = 0; i < 5; i++) {
            arr[i] = i;
        }
        arr = (int *)realloc(arr, 10 * sizeof(int));
        if (arr == NULL) {
            printf("Memory allocation failed\n");
            return 1;
        }
        for (int i = 5; i < 10; i++) {
            arr[i] = i;
        }
        for (int i = 0; i < 10; i++) {
            printf("%d ", arr[i]);
        }
        free(arr);
        return 0;
    }
    

    4.*free(void ptr):

  • 功能:释放之前分配的内存块,使其可以重新分配。

  • 特点:释放后,指针ptr不再指向有效的内存区域,应该将ptr置为NULL以防止野指针错误。

  • #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int *arr = (int *)malloc(10 * sizeof(int));
        if (arr == NULL) {
            printf("Memory allocation failed\n");
            return 1;
        }
        free(arr);
        arr = NULL;
        return 0;
    }
    

    3. C++中动态内存管理

    在C++中,动态内存管理不仅可以使用C语言的函数(如malloc、calloc等),还提供了更高级的 newdelete 运算符:

  • 1.new:

    • 功能:分配指定类型的内存,并调用该类型的构造函数。

    • 示例代码:

      #include <iostream>
      
      int main() {
          int *arr = new int[10];
          for (int i = 0; i < 10; i++) {
              arr[i] = i;
          }
          for (int i = 0; i < 10; i++) {
              std::cout << arr[i] << " ";
          }
          delete[] arr;
          return 0;
      }
      

      2.delete:

  • 功能:释放用new分配的内存,并调用该类型的析构函数。
#include <iostream>

int main() {
    int *arr = new int[10];
    delete[] arr;
    return 0;
}

在C++中,使用 newdelete 操作符进行内存管理比使用C语言中的函数更方便,因为它们不仅分配和释放内存,还自动调用构造函数和析构函数,确保对象在创建和销毁时执行必要的初始化和清理工作。

4. operator new与operator delete函数

C++中,operator newoperator delete 是为对象分配和释放内存的函数。它们类似于 mallocfree,但有一些重要区别:

  • operator new:

    • 功能:分配指定大小的内存,但不调用构造函数。

    • 通常在类的new运算符中隐式调用。

    • #include <iostream>
      
      void* operator new(size_t size) {
          std::cout << "Custom new for size " << size << std::endl;
          return malloc(size);
      }
      
      void operator delete(void* ptr) noexcept {
          std::cout << "Custom delete" << std::endl;
          free(ptr);
      }
      
      int main() {
          int *p = new int(10);
          delete p;
          return 0;
      }
      

    • operator delete:

      • 功能:释放用 operator new 分配的内存,但不调用析构函数。
      • 通常在类的delete运算符中隐式调用。
    • 可以重载这两个函数以定制内存分配行为。例如,在需要跟踪内存分配和释放的场景中,可以重载 operator newoperator delete 以记录每次内存操作的日志。

      5. new和delete的实现原理

      newdelete 的实现可以分为两个步骤:

    • new:

      • 调用 operator new 分配内存。
      • 在分配的内存上调用构造函数初始化对象。
    • delete:

      • 在内存上调用析构函数销毁对象。
      • 调用 operator delete 释放内存。
    • 示例代码展示了new和delete的工作机制:

      #include <iostream>
      
      class MyClass {
      public:
          MyClass() {
              std::cout << "Constructor called" << std::endl;
          }
          ~MyClass() {
              std::cout << "Destructor called" << std::endl;
          }
      };
      
      int main() {
          MyClass *obj = new MyClass();
          delete obj;
          return 0;
      }
      

在上面的代码中,当我们使用 new MyClass() 创建对象时,首先调用 operator new 分配内存,然后在分配的内存上调用 MyClass 的构造函数。当我们使用 delete obj 删除对象时,首先调用 MyClass 的析构函数,然后调用 operator delete 释放内存。

6. 定位new表达式(placement-new)

placement new 是C++中的一个高级特性,用于在已分配的内存上构造对象。它不会分配新的内存,只是调用对象的构造函数。

#include <iostream>

int main() {
    char buffer[sizeof(int)];
    int *p = new (buffer) int(5); // 在buffer中构造int
    std::cout << *p << std::endl;
    p->~int(); // 手动调用析构函数
    return 0;
}

在上面的代码中,我们在预先分配的内存 buffer 中使用 placement new 构造了一个 int 对象。这种技术通常用于自定义内存池或优化程序性能。

7. 常见面试题

1.解释C++中new和malloc的区别

  • new:
    • 分配内存并调用构造函数初始化对象。
    • 返回对象的指针。
    • 可以重载。
    • 用于分配类对象。
  • malloc:
    • 仅分配内存,不调用构造函数。
    • 返回 void* 类型的指针,需要类型转换。
    • 不能重载。
    • 用于分配任意类型的内存。

2.什么是内存泄漏?如何避免?

  • 内存泄漏: 是指程序在分配内存后,未能正确释放已分配的内存,导致内存无法被重新利用。
  • 避免方法:
    • 使用智能指针(如 std::unique_ptrstd::shared_ptr)来自动管理内存。
    • 确保每个 malloc 对应一个 free,每个 new 对应一个 delete
    • 使用工具如 Valgrind 进行内存泄漏检测。

3.解释C++中的RAII(Resource Acquisition Is Initialization)

  • RAII: 是一种编程习惯,即资源的获取和释放通过对象的构造函数和析构函数来管理。
  • 示例:
    #include <iostream>
    #include <memory>
    
    class Resource {
    public:
        Resource() { std::cout << "Resource acquired" << std::endl; }
        ~Resource() { std::cout << "Resource released" << std::endl; }
    };
    
    void useResource() {
        std::unique_ptr<Resource> res(new Resource());
        // 使用资源
    }
    
    int main() {
        useResource();
        return 0;
    }
    

    4.解释栈区和堆区的区别

    • 栈区:
      • 用于存储函数调用的局部变量。
      • 内存由编译器自动分配和释放。
      • 具有后进先出的特点。
      • 内存分配效率高,但大小有限。
    • 堆区:
      • 用于动态内存分配。
      • 内存由程序员手动分配和释放。
      • 大小不固定,可以动态增长或缩小。
      • 内存分配效率较低,但灵活性高。

5.如何实现自己的内存池?

内存池是一种预分配大块内存以减少多次分配开销的方法。可以通过链表管理内存块,分配时从链表中取出一块内存,释放时将内存块重新挂回链表。

#include <iostream>
#include <vector>

class MemoryPool {
    std::vector<void*> pool;
public:
    MemoryPool(size_t size, size_t count) {
        for (size_t i = 0; i < count; ++i) {
            pool.push_back(malloc(size));
        }
    }

    void* allocate() {
        if (pool.empty()) return malloc(1); // 返回新分配内存
        void* ptr = pool.back();
        pool.pop_back();
        return ptr;
    }

    void deallocate(void* ptr) {
        pool.push_back(ptr);
    }

    ~MemoryPool() {
        for (auto ptr : pool) {
            free(ptr);
        }
    }
};

int main() {
    MemoryPool pool(256, 10);
    void* p1 = pool.allocate();
    pool.deallocate(p1);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GE的六西格玛是怎么成功的?

六西格玛作为一种先进的质量管理方法&#xff0c;旨在通过消除缺陷、提高流程效率来降低成本、增强客户满意度。GE作为最早采用六西格玛的公司之一&#xff0c;其成功的原因首先离不开高层领导对精益六西格玛理念的坚定支持和推动。公司高层不仅亲自参与培训和项目实践&#xf…

Android电量优化,让你的手机续航更持久

节能减排&#xff0c;从我做起。一款Android应用如果非常耗电&#xff0c;是一定会被主人嫌弃的。自从Android手机的主人用了你开发的app&#xff0c;一天下来&#xff0c;也没干啥事&#xff0c;电就没了。那么他就会想尽办法找出耗电量杀手&#xff0c;当他找出后&#xff0c…

河南劳务资质申请到拿证,时间规划全攻略

河南劳务资质申请到拿证&#xff0c;时间规划全攻略如下&#xff1a; 一、前期准备阶段 材料准备 准备企业营业执照、企业章程、人员身份证明、职称或技能证书、社保证明等相关材料。特别注意准备《建筑业企业资质申请表》、建筑业企业资质申报材料承诺书、标准要求的主要设备…

藏汉翻译通app:藏文OCR文字识别提取是怎么提高你学习藏语的效率的?今天手把手教会你使用!

工作上的沟通、出门旅游的沟通、学习过程中的沟通&#xff0c;都离不开语言。而语言也是连接不同文化的桥梁。当你还是一个萌新&#xff0c;刚开始接触并使用藏语时&#xff0c;一个趁手的翻译工具&#xff0c;将帮助你快速掌握藏语这门语言。 藏汉翻译通小程序&#xff0c;不仅…

使用DLL还是通讯协议进行LabVIEW设备集成

在使用LabVIEW进行设备集成时&#xff0c;可以选择通过设备提供的DLL或直接使用通讯协议。选择方法取决于开发效率、性能、灵活性和维护成本等因素。本文将从这几个方面详细分析两种方法的优劣&#xff0c;帮助做出最佳决策。 分析角度 1. 开发效率&#xff1a; DLL&a…

EAP设备自动化系统基本功能与概念

EAP&#xff08;Equipment Automation Programming&#xff09;实现了对生产线上机台的实时监控&#xff0c;是工厂自动化不可缺少的控制系统。EAP系统与FAB中的机台紧密相关&#xff0c;系统的设计与开发必须与生产线的机台实际生产流程相一致&#xff0c;才能达到自动化控制机…

SG5032CAN晶体振荡器适用于单片机应用

单片机晶振的作用是为系统提供基本的时钟信号。通常一个系统共用一个晶振&#xff0c;便于各部分保持同步。有些通讯系统的基频和射频使用不同的晶振&#xff0c;而通过电子调整频率的方法保持同步。晶振是为单片机提供一个基本震荡源&#xff0c;也就相当于人体的心跳&#xf…

ThingsKit:智能物联网平台的创新者

在数字化浪潮的推动下&#xff0c;物联网&#xff08;IoT&#xff09;正在迅速改变我们的生活和工作方式。ThingsKit&#xff0c;一个领先的物联网平台&#xff0c;致力于通过其创新的技术和服务&#xff0c;为用户提供一个全面、灵活且易于使用的解决方案。 核心特点 设备连接…

【30天精通Prometheus:一站式监控实战指南】第19天:haproxy_exporter从入门到实战:安装、配置详解与生产环境搭建指南,超详细

亲爱的读者们&#x1f44b;   欢迎加入【30天精通Prometheus】专栏&#xff01;&#x1f4da; 在这里&#xff0c;我们将探索Prometheus的强大功能&#xff0c;并将其应用于实际监控中。这个专栏都将为你提供宝贵的实战经验。&#x1f680;   Prometheus是云原生和DevOps的…

如何充分利用代理IP扩大网络接触面

目录 前言 第一部分&#xff1a;什么是代理IP&#xff1f; 第二部分&#xff1a;如何获取代理IP&#xff1f; 1. IP质量 2. 匿名性 3. 限制 第三部分&#xff1a;如何使用代理IP&#xff1f; 第四部分&#xff1a;如何充分利用代理IP&#xff1f; 总结&#xff1a; 前…

SqlServer: 如何产生没有重复的报名编号

背景&#xff1a; 某一期的报名过程中&#xff0c;希望报名能做到报名从1开始&#xff0c;从小到大依次来。但实际生产环境中&#xff0c;用户集中大并发式报名&#xff0c;报名编号非常容易重复。 下面我们用简单的SQL来模拟和重现这个过程&#xff1a; USE tempdb GO DROP…

城规跨考地信:你需要知道的几件事

24考研结束&#xff0c;25地信考研的小伙伴也开始准备。 在这期间发现一个现象&#xff0c;城规跨考GIS的讨论度非常高。 对这一点&#xff0c;我并不感到意外&#xff0c;因为随着地产行业的节节败退&#xff0c;很多单位不需要那么多规划人和建筑人&#xff0c;乃至土木人。…

【前端Vue3】——JQuery知识点总结(超详细)

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门知识专栏&#xff1a;&#x1f387;【MySQL&#…

ceph对象储存的使用

radosgw-admin user create --uid“user1” --display-name“user1” #创建用户 sudo apt install s3cmd cephadminceph-mgr01:~/ceph-cluster/s3$ s3cmd --configure Enter new values or accept defaults in brackets with Enter. Refer to user manual for detailed desc…

GPT-4o:人工智能新纪元的突破与展望

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

常见排序算法之插入排序

目录 一、直接插入排序 1.1 什么是插入排序 1.2 代码思路 1.3 C语言源码 二、希尔排序 2.0 插入排序的弊端 2.1 什么是希尔排序&#xff1f; 2.2 排序思路 2.3 C语言源码 一、直接插入排序 1.1 什么是插入排序 插入排序是一种简单直观的排序算法&#xff0c;它通过构…

Django视图层探索:GET/POST请求处理、参数传递与响应方式详解

系列文章目录 Django入门全攻略&#xff1a;从零搭建你的第一个Web项目Django ORM入门指南&#xff1a;从概念到实践&#xff0c;掌握模型创建、迁移与视图操作Django ORM实战&#xff1a;模型字段与元选项配置&#xff0c;以及链式过滤与QF查询详解Django ORM深度游&#xff…

LeetCode刷题之HOT100之字母异位词分组

中午没有回宿舍午休&#xff0c;每次回去都没睡着&#xff0c;我想就没有必要回去了。京东买的书今天还没到&#xff0c;提前把题做了好专心做开发任务。 1、题目描述 2、逻辑分析 我看了几分钟&#xff0c;有点理解不了这道题需求是什么。看看题解。看了才理解题目的意思。字…

Java编程常见问题汇总四

系列文章目录 文章目录 系列文章目录前言一、忽略所有异常二、重复包装RuntimeException三、不正确的传播异常四、用日志记录异常五、异常处理不彻底 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。…

设计循环队列---力扣622

1、题目 1.1基础设置与讲解 循环队列&#xff0c;即固定长度的队列&#xff0c;可以想象成一个环形队列 就类似于这种队列&#xff0c;队尾指针后会有一个空位&#xff0c;用于控制判断队列为空还是为满&#xff1b; typedef int MyDataType;typedef struct {MyDataType fron…