前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++内存操作和管理(一)

C++内存操作和管理(一)

作者头像
程序员的园
发布2024-07-18 13:15:22
650
发布2024-07-18 13:15:22
举报
文章被收录于专栏:程序员的园——原创文章

如果知道我会死在哪里,那我将永远不去那个地方 -查理 芒格

前言

内存的操作和管理涉及东西较多且散,为便于查看,整理归纳成此文。可能有不全面之处,望大家批评指正。所有内容(见下图),我本想为了一次性更完,但是阅读体验不佳。遂将其拆分为两部分,此为其一。

内存布局

C++ 中的内存布局是由编译器和操作系统共同决定的。一般来说,C++ 中的内存布局可以分为以下几个部分:

  • 代码段(Code Segment):存储执行代码的二进制指令,通常是只读的。
  • 全局/静态变量区(Static Data Area):全局变量和静态变量被分配在这个区域。这些变量在程序的整个运行周期内都存在,它们的生命周期与程序的生命周期相同。
  • 常量区(Constant Data Area):常量字符串等常量数据被存储在这个区域。这些数据在程序运行期间不能被修改。
  • 堆(Heap):动态内存分配发生在堆上。使用 new 和 delete 或 malloc 和 free 进行动态内存分配和释放。
  • 栈(Stack):局部变量、函数参数和函数调用信息都存储在栈上。栈是一种后进先出(LIFO)的数据结构,通过栈指针(stack pointer)来管理。

栈内存使用

代码语言:javascript
复制

#include <iostream>
void stackMemoryExample() {
    int localVar = 10;  // 局部变量,存储在栈上
    std::cout << "Stack Memory Example: " << localVar << std::endl;
}  // localVar 在函数结束时被销毁

堆内存使用

代码语言:javascript
复制

#include <iostream>
void heapMemoryExample() {
    int* dynamicVar = new int(20);  // 动态分配内存,存储在堆上
    std::cout << "Heap Memory Example: " << *dynamicVar << std::endl;
    delete dynamicVar;  // 释放堆上的内存,防止内存泄漏
}

内存分类

C++程序的内存管理涉及物理内存和虚拟内存。而物理内存和虚拟内存的桥梁为存管理单元(MMU)。

  • 物理内存:物理内存是计算机实际硬件上的内存。它是计算机用于存储程序和数据的硬件组件,通常是RAM(随机访问存储器)。C++程序在运行时需要物理内存来存储变量、数据结构、函数调用栈和程序代码等。
  • 虚拟内存:虚拟内存是一种抽象概念,它扩展了计算机对内存的使用。每个运行的程序都拥有自己的虚拟内存空间,这是一个独立于物理硬件的地址空间。虚拟内存使得每个程序似乎都有足够的内存可用,即使实际物理内存有限。它允许程序使用比物理内存更大的地址空间,将不常用的数据存储在硬盘上,只在需要时将其加载到物理内存中。
  • 内存管理单元(MMU):MMU 是计算机系统中的硬件组件,负责将程序中的虚拟地址映射到物理地址。它实现了虚拟内存的概念。当程序访问虚拟内存中的地址时,MMU将这些地址转换为对应的物理地址,或者在需要时将数据从磁盘加载到物理内存。这种转换和管理是透明的,程序员无需关心。

动态内存分配

C++中可以使用new/delete及malloc/free来操作动态内存。

new和malloc的区别和联系

  • new 是C++的运算符,自定义类型可以实现operator new和operator delete的重载,而 malloc 是C的库函数。
  • new 返回的是分配类型的指针,而 malloc 返回的是 void*。在使用 new 时,编译器会执行类型检查,并确保分配的内存与所请求的类型相匹配。
  • new 不仅分配内存,还会调用对象的构造函数,同理delete也会调用对象的析构函数。而malloc只是分配一块内存,不会调用构造函数。
  • new 不需要传递类型的大小,因为编译器会根据类型进行计算。malloc 需要显式传递分配的内存大小。
  • new/delete、new[]/[]delete、malloc/free分别配套使用。

new分配内存

代码语言:javascript
复制

int* myInt = new int; // 分配一个整数的内存
//一些操作
delete myInt;

new分配数组

代码语言:javascript
复制

int* p = new int[5];
for(int i =0; i<5;i++)
{
    p[i]=i;
}

for(int i =0; i<5;i++)
{
    std::cout<<p[i]<<"\n";
}
delete []p;

malloc分配内存

代码语言:javascript
复制

int* p = (int*)malloc(5*sizeof(int));
for(int i =0; i<5;i++)
{
    p[i]=i;
}

for(int i =0; i<5;i++)
{
    std::cout<<p[i]<<"\n";
}
free(p);

malloc分配多级内存

代码语言:javascript
复制

int** p = (int**)malloc(5*sizeof(int*));
for(int i =0; i<5;i++)
{
    p[i]=(int*)malloc(2*sizeof(int));
    
    p[i][0]=i*i+0;
    p[i][1]=i*i+1;
}

for(int i =0; i<5;i++)
{
    std::cout<<p[i][0]<<"\t"<<p[i][1]<<"\n";
}

for(int i =0; i<5;i++)
{
    free(p[i]);
}
free(p);

操作动态内存可能存在的问题

内存泄漏

顾名思义,申请内存后没有释放,如new/delete、new[]/[]delete、malloc/free未能配套使用。通常意义上分为两种情况:(1)动态分配的内存没有释放;(2)虽然释放内存但是存在指向丢失导致的泄露

代码语言:javascript
复制
int main() 
{
int* p = new int;
*p = 10;
//no delete,memory leak
return 0;
}
代码语言:javascript
复制
int main() 
{
int* p = new int;//this pointer will memory leak
p = new int;
*p = 10;
free(p);
return 0;
}

解决方法:配套使用,或使用智能指针

野指针

指针定义后未初始化,致使指针指向的内存是无效值/随机值。

代码语言:javascript
复制
int* ptr = new int;
std::cout<<*ptr;//此时即为野指针

解决方法:定义即初始化,如果不确认指向,可指向nullptr

悬挂指针

一个曾经指向有效内存区域,但后来该内存区域被释放或重新分配,而指针仍保留原来的值,导致无法再通过该指针访问该内存区域

代码语言:javascript
复制
int* ptr = new int(10);  
delete ptr;
//此后如果再次使用ptr,ptr即为悬挂指针

内存释放后立刻将指针指向nullptr

double free

指针释放后再次释放会触发访问冲突崩溃,如下实例代码

代码语言:javascript
复制
void test_memory_double_free()
{
int* p = new int;
delete p;
p=nullptr;//if commit this line will trigger break point
delete p;
}

解决方法:指针释放后立刻将其指向nullptr

内存碎片和效率

内存碎片和效率主要存在于大量申请和释放内存的场景。频繁的内存申请和释放可能会导致内存的空存在大量碎片,致使分配大空间内存时内存不够;同时,内存的申请和释放是有CPU耗时的,过多的申请和释放会存在效率问题的。

解决方法:内存池,见后期文章

好习惯推荐:指针使用前检查其是否为nullptr,定义和释放后立刻将其指向nullptr

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员的园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档