【C++八股题整理】内存布局、堆和栈、内存泄露、函数调用栈

news2024/12/23 15:58:53

C++八股题整理

  • 内存布局
    • C++中的内存分配情况
    • 堆和栈的内存有什么区别?
    • 堆内存分配慢如何优化?内存池
    • 内存溢出和内存泄漏是什么?如何避免?
    • 内存碎片是什么?怎么解决?
    • 为什么栈的访问效率比堆高?
    • 函数调用时栈的变化?
    • 函数的参数列表为什么从右往左入栈?

内存布局

C++中的内存分配情况

在这里插入图片描述

区域存储内容分配方式生命周期
(Stack)局部变量、局部常量、函数的参数和返回地址自动分配和释放,由编译器管理。连续的内存块,分配释放速度快函数调用时入栈,返回时释放
(Heap)动态分配的对象和数组newdelete,或mallocfree动态分配和释放。不连续的内存块,易出现碎片,速度慢程序员手动分配和释放
全局/静态存储区全局变量、静态变量程序启动时分配,程序结束时释放与程序的整个生命周期一致
常量区字符串字面量、const修饰的全局和静态变量、虚函数表程序启动时分配与程序的整个生命周期一致
代码区程序的二进制代码由操作系统在程序加载时分配与程序的整个生命周期一致
#include <iostream>

int globalVar = 10;  // 全局变量,存储在全局/静态存储区
static int staticGlobalVar = 20;  // 静态全局变量,存储在全局/静态存储区

class MyClass {
public:
    int memberVar;  // 成员变量,存储在对象实例所分配的内存中(栈或堆)
    static int staticMemberVar;  // 静态成员变量,存储在全局/静态存储区

    // 构造函数,存储在代码区
    MyClass(int val) : memberVar(val) {}

    // 成员函数,存储在代码区
    void show() {
        std::cout << "Member Var: " << memberVar << std::endl;
    }

};

const int constGlobalVar = 40;  // 全局常量,存储在常量区

int main() {
    int localVar = 50;  // 局部变量,存储在栈中
    const int constLocalVar = 60;  // 局部常量,存储在栈中(通常编译器优化后会放入寄存器)

    // 静态创建对象
    MyClass obj(localVar);  // 对象本身和成员变量存储在栈中

    // 动态创建对象
    MyClass* heapObj = new MyClass(localVar);  // 指针heapObj在栈上,对象本身和成员变量存储在堆中

    // 释放动态分配的对象
    delete heapObj;  // 释放堆中的内存

    return 0;
}

堆和栈的内存有什么区别?

特性栈内存堆内存
管理方式自动管理,系统分配和释放手动管理,程序员分配和释放
内存布局连续的内存块,不会产生内存碎片非连续的内存块,会产生内存碎片
分配地址由高向低由低向高
分配速度慢,因为要寻找合适大小的内存块
空间大小较小,通常几MB到几十MB较大,通常可达GB级别
生命周期随函数调用开始和结束程序员控制,直到显式释放
存储内容局部变量、函数调用信息动态分配的对象和数据结构

堆内存分配慢如何优化?内存池

内存池通过一次性预先分配一大块内存,并将其划分为多个固定大小的小块,当需要分配内存时,从这些小块中快速分配,而不是每次调用 malloc;释放内存时,直接将小块返回内存池,而不调用 free。这样大幅减少了频繁的堆内存分配和释放操作的系统开销,降低了内存碎片的风险,显著提升了在高频率、小内存块分配场景下的性能。

#include <iostream>

class MemoryAllocator {
private:
    int poolsize;            // 内存池的总大小
    int blocksize;           // 每个块的大小
    int numblocks;           // 内存池中块的数量
    char* pool;              // 指向内存池的指针
    bool* used;              // 用于标记每个块是否被使用的布尔数组

public:
    // 构造函数:初始化内存池
    MemoryAllocator(int ps, int bs) : poolsize(ps), blocksize(bs), numblocks(ps / bs) {
        pool = new char[poolsize];          // 分配内存池
        used = new bool[numblocks];         // 分配标记数组
        for (int i = 0; i < numblocks; i++) {
            used[i] = false;                // 初始化标记数组,所有块都是未使用的
        }
    }

    // 析构函数:释放分配的内存
    ~MemoryAllocator() {
        delete[] pool;   // 释放内存池
        delete[] used;   // 释放标记数组
    }

    // 分配内存块
    void* Alloc() {
        for (int i = 0; i < numblocks; i++) {
            if (!used[i]) {                  // 查找第一个未使用的块
                used[i] = true;             // 标记为已使用
                return &pool[blocksize * i]; // 返回块的指针
            }
        }
        return nullptr;  // 如果没有可用块,返回nullptr
    }

    // 释放内存块
    void free(void* ptr) {
        // 检查指针是否在内存池的有效范围内
        if (ptr >= pool && ptr < pool + poolsize) {
            // 计算块的索引
            int index = (static_cast<char*>(ptr) - pool) / blocksize;
            if (used[index]) used[index] = false; // 标记为未使用
        }
    }
};

int main() {
    MemoryAllocator pool(100, 4);  // 创建一个内存池,总大小100字节,每块4字节

    // 分配两个内存块
    void* ptr1 = pool.Alloc();
    std::cout << "Allocated at " << ptr1 << std::endl;

    void* ptr2 = pool.Alloc();
    std::cout << "Allocated at " << ptr2 << std::endl;

    // 释放第二个内存块
    pool.free(ptr2);
    std::cout << "Freed at " << ptr2 << std::endl;

    return 0;
}

内存溢出和内存泄漏是什么?如何避免?

  • 内存泄漏:程序在堆上动态分配内存后,未能正确释放不再需要的内存,导致这部分内存无法被重用,从而使得系统中的可用内存逐渐减少。内存泄漏最终会导致内存溢出。
  • 内存溢出(out of memory,OOM):程序试图分配的内存超过了系统或应用程序所能提供的最大可用内存,导致程序运行失败或异常。内存溢出可能发生在堆或栈上。
    • 堆溢出:通常是因为内存泄漏
      int main() {
          while (true) { int* ptr = new int[1000000]; } // 不断分配内存,最终会导致堆溢出
          return 0;
      }
      
    • 栈溢出:通常是因为过深的递归调用
      void recursiveFunction() {
          int largeArray[10000]; // 大量消耗栈空间
          recursiveFunction();   // 无限递归调用,最终会导致栈溢出
      }
      int main() {
          recursiveFunction();
          return 0;
      }
      

可以使用智能指针来自动管理内存,避免手动管理的复杂性。

内存碎片是什么?怎么解决?

  • 内存碎片:在动态内存管理中,由于频繁的分配和释放内存,导致内存中出现许多大小不一的、无法被利用的空闲块的现象
    • 内部碎片:当分配的内存块比实际需要的内存要大时,未使用的部分称为内部碎片。内部碎片主要出现在固定大小的内存分配策略中
    • 外部碎片:内存中存在足够多的空闲空间总量,但由于它们不连续,无法为大块的内存请求提供服务
  • 解决方法
    • 内存紧缩:通过移动分散的内存块来将合并外部碎片,形成连续的内存块。紧缩通常伴随暂停程序运行,因此在实时系统中不太适用
    • 优化分配策略:最佳适应、最先适应、最后适应、首次适应等
    • 内存池:因为块的大小固定且地址连续,因此能避免外部碎片

为什么栈的访问效率比堆高?

  • 缓存局部性:栈内存分配是线性增长的,符合CPU缓存的局部性原则,访问效率高;而堆内存分配则可能在内存中是离散的,导致缓存命中率低,访问速度相对较慢
  • 线程安全性:每个线程都有自己独立的栈,所以栈上的操作通常是线程安全的,不需要同步机制;堆是全局共享的资源,多个线程访问时可能需要同步机制(如锁),这会进一步降低堆内存分配和访问的效率

函数调用时栈的变化?

参考资料 参考资料
比如main函数调用add(int a , int b)函数:

  1. 从右往左压入add的参数值
  2. 保存call指令的下一条指针的地址
  3. 压入EBP(此时EBP指向main的栈底),EBP=ESP(让EBP指向add的栈底),保存main的栈
  4. 开辟add函数的栈空间
  5. add函数中的指令
  6. add函数保存计算值
  7. ESP=EBP,add函数开始退栈
  8. EBP出栈,就是还原main函数的栈底指针
  9. 清除add函数栈空间,继续执行main

这一过程中开辟出的空间称为栈帧,它包括以下内容:

  1. 被调函数的参数:通常从右向左顺序入栈(根据调用约定),即最后一个参数最先入栈。
  2. 被调函数的返回地址:用于在被调函数执行完毕后,返回到调用函数的下一条指令。
  3. 调用者函数的栈帧指针(ebp):保存调用者的栈底地址,以便在被调函数执行完毕后恢复调用者的栈帧。
  4. 被调函数的局部变量:在栈上为被调函数的局部变量分配的空间。

函数的参数列表为什么从右往左入栈?

主要是为了支持函数的变长参数 示例
但这只是一种约定,有些语言和平台并不这样

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

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

相关文章

奉加微PHY6233进入DTM模式;TX单音信号;

TX单音信号 参考文档"PH62XX射频测试仪器操作说明文档.pdf"进行DTM配置和操作,这里的目的是为了测试频偏: 这里先把原厂给的DTM的ihex固件下载到芯片里面去: 设置好参数后点击start按钮即可打出单音信号: 这时候频谱的信号如下: 接下来调成其他参数可以看到如下频…

一文彻底搞懂Spring, Spring MVC, Spring Boot 和 Spring Cloud 区别

1.定义说明 Spring, Spring MVC, Spring Boot 和 Spring Cloud 是Spring家族中的四个不同的项目&#xff0c;它们有各自的功能&#xff0c;并且可以在Spring应用程序中一起使用。 1&#xff09;Spring Spring是一个开源容器框架&#xff0c;它集成各类型的工具&#xff0c;通…

Android ROM和Linux内核源码在线阅读网站

1&#xff0c; Android在线代码阅读 http://www.aospxref.com/ 支持最新android源码 http://androidxref.com/ 支持到Android9 2&#xff0c; Linux内核在线阅读网站 https://lxr.missinglinkelectronics.com/ 支持在线阅读linux内核&#xff0c;uboot&#xff0c;qemu &am…

JavaScript方法链

前言 在JavaScript中&#xff0c;方法链&#xff08;Chaining Methods&#xff09;是一种编程技术&#xff0c;允许你在一个表达式中连续调用多个方法。这通常通过让每个方法返回对象自身&#xff08;this&#xff09;来实现&#xff0c;从而可以在同一行上依次调用多个方法。…

文件加密软件怎么选呢?五款人气超高的文件加密软件推荐给你

文件加密软件怎么选呢&#xff1f;以下是五款人气超高的文件加密软件&#xff0c;包括安企神在内&#xff0c;它们各自具有独特的功能和优势&#xff1a; 安企神 功能特点&#xff1a;安企神是一款功能强大的企业级文件加密软件&#xff0c;提供全方位的数据安全保护。它采用先…

TPAMI 2024|如何在动态世界中捕捉每一个细节?智能视觉识别的突破,开放长尾识别技术详解!

题目&#xff1a;Open Long-Tailed Recognition in a Dynamic World 动态世界中的开放长尾识别 作者&#xff1a;Ziwei Liu; Zhongqi Miao; Xiaohang Zhan; Jiayun Wang; Boqing Gong; Stella X. Yu 源码链接&#xff1a; https://liuziwei7.github.io/projects/LongTail.ht…

Java项目: 基于SpringBoot+mysql网上订餐系统分前后台(含源码+数据库+开题报告+PPT+毕业论文)

一、项目简介 本项目是一套基于SpringBootmysql网上订餐系统分前后台 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单…

Ollydbg提示:xxxxxx可能不是一个 32 位 PE 文件,无论如何都尝试载入吗?

原标题&#xff1a;OD提示C:\Users\XuanRan\Desktop\xxxx.exe’可能不是一个个 32 位 PE 文件,无论如何都尝试载入吗? 它的意思就是告诉你&#xff0c;OD现在只能用于32位软件。 如果要调试64位程序&#xff0c;去使用x64dbg x64dbg下载链接&#xff1a; https://github.com…

代码随想录算法训练营第32天|509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

目录 509. 斐波那契数1、题目描述2、思路3、code4、复杂度分析 70. 爬楼梯1、题目描述2、思路3、code 746. 使用最小花费爬楼梯1、题目描述2、思路3、code4、复杂度分析 509. 斐波那契数 题目链接&#xff1a;link 1、题目描述 斐波那契数 &#xff08;通常用 F(n) 表示&…

503错误

503 Service Temporarily Unavailable 我在学习ES-IK分词器时restart es后发现刷新网页报了503错误 后面发现是由于浏览器缓存或存储的Cookies引起的&#xff0c;需要清除缓存和Cookies 然后在游览器设置中找到 ​​ 然后刷新发现可以了

从初阶到顶尖:腾讯产品经理五级能力模型深度解读

产品经理在互联网企业中扮演着至关重要的角色&#xff0c;他们负责从需求分析到产品落地的整个生命周期。在腾讯的产品经理能力模型中&#xff0c;能力被分为五个等级&#xff08;Level 1 到 Level 5&#xff09;&#xff0c;每个等级代表了产品经理在通用能力、专业知识、专业…

【Python报错已解决】“ModuleNotFoundError: No module named ‘mne‘”

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言&#xff1a;一、问题描述1.1 报错示例&#xff1a;当我们尝试导入MNE-Python库时&#xff0c;可能会看到以下错误…

基础动销方案:开启稳健起步之路

在当今竞争激烈的商业世界中&#xff0c;如何让产品或服务实现有效动销&#xff0c;是企业生存与发展的关键所在。而基础动销方案&#xff0c;正是企业迈出稳健第一步的重要策略。 基础动销到底有多重要呢&#xff1f;它能在产品或服务推出初期&#xff0c;通过一系列营销手段达…

2025毕业季:如何用Java SpringBoot构建医疗就诊平台?掌握最新技术,开启医疗信息化大门

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

Windows 下载安装RabbitMQ

环境描述 windows10 Erlang 26.2.x 版本 RabbitMQ 3.13.7 因为RabbitMQ是Erlang语言开发的&#xff0c;所以必须安装 Erlang RabbitMQ官网链接: https://www.rabbitmq.com/docs/which-erlang 1.下载并安装Erlang 26.2.5 1.1下载Erlang 26.2.5 https://erlang.org/dow…

2.【R语言】RStudio的下载和安装

2.1 RStudio的介绍 RStudio 是一种集成开发环境 (Integrated Development Environment, IDE)&#xff0c;主要用于 R 语言的开发和数据分析。它为 R 语言的使用者提供了一系列便捷的工具和功能&#xff0c;使得编写、调试和执行 R 代码变得更加高效和直观。以下是对 RStudio 主…

面向电商与营销的AI一键试衣:打造个性化购物体验

随着人工智能技术的迅猛发展,虚拟试衣功能正在逐渐改变消费者的在线购物体验。本文将介绍一项名为“Kolors”的项目,它利用先进的AI技术,为电商平台和营销活动提供了一键试衣功能。通过精确调整和优化服装在生成图像中的外观,Kolors使得虚拟试衣体验更加自然和逼真。 一、项…

nginx 部署前端vue项目

文章目录 一、什么是nginx&#xff1f;二、nginx 部署前端vue项目步骤2.1 安装nginx2.1.1 windows环境安装2.1.2 linux环境安装 2.2 打包vue项目 2.3 配置nginx 一、什么是nginx&#xff1f; Nginx是一款轻量级的HTTP服务器&#xff0c;采用事件驱动的异步非阻塞处理方式框架&…

JDK7前时间相关类(Data,SimpleDataFormat,Calender)

Data时间类 世界标准时间&#xff1a;格林尼治时间&#xff08;GMT&#xff09; 目前世界标准时间&#xff08;UTC&#xff09;已经替换为&#xff1a;原子钟 中国标准时间&#xff1a;世界标准时间8小时 总结&#xff1a; 1.如何创建日期对象&#xff1f; Data data new…

FreeRTOS线程数据传递---消息队列

简介 队列操作 创建队列 队列写入 队列写入 队列读取 队列相关函数 1.创建队列 2.向队列写入 3.从队列读取 队列的其他写入API函数 简介 在实际的项目开发中&#xff0c;经常会遇到在任务于任务之间或任务于中断之间需要进行“沟通交 流”&#xff0c;这…