C++从入门到实战(十一)详细讲解C/C++语言中内存分布与C与C++内存管理对比

C++从入门到实战(十一)详细讲解C/C++语言中内存分布与C与C++内存管理对比

C++从入门到实战(十一)详细讲解C/C++语言中内存分布与C与C++内存管理对比


前言

  • 在之前的博客系列中,我们深入探讨了C++的第一个重要阶段——类和对象,以及与之相关的诸多核心内容,包括四大构造函数、类的种类、内部类、匿名类、友元等
  • 这些知识点如同坚实的基石,为后续的C++学习构建了稳固的基础,帮助我们逐步建立起对C++面向对象编程的深刻理解。
我的个人主页,欢迎来阅读我的其他文章
https://blog.ZEEKLOG.net/2402_83322742?spm=1011.2415.3001.5343
我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.ZEEKLOG.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482
  • 接下来,我们将开启新的篇章,深入剖析C/C++语言中的内存分布,并对比C与C++在内存管理方面的差异。

一、C/C++语言中内存分布

在这里插入图片描述

1.内核空间

  • 内核空间(Kernel Space):相当于 “管理层办公室”,存放着操作系统的核心代码(比如管理 CPU、内存、硬盘的程序)。只有 “管理层”(内核程序)能进去,普通程序不能随便访问,权限很高,负责处理最底层的硬件资源。

2.栈

  • 栈是一种后进先出(LIFO)的数据结构,用于存储局部变量、函数调用信息等。
  • 每当调用一个函数时,系统会在栈上为该函数的局部变量分配内存,函数执行完毕后,这些内存会被自动释放。
想象你去食堂打饭,餐盘是按顺序叠起来的:后放上去的餐盘先被拿走(后进先出)。栈就是这样一个 “自动清理的临时货架”,存放函数运行时的 临时数据(比如局部变量、函数参数、返回地址)。程序会自动分配和释放空间,不需要你手动管理。
// 函数,函数代码存储在代码段voidfunc(){// 局部变量,存储在栈上int local_variable =30; std::cout <<"Local variable: "<< local_variable << std::endl;}// 调用函数,函数调用信息和局部变量会在栈上操作intmain(){func();}

3.堆

堆是用于动态分配内存的区域,程序可以在运行时从堆中请求和释放内存

想象你去超市存包,储物柜可以按需申请:你觉得需要多大空间,就租多大的柜子,用完自己关门(释放),不关门的话柜子会一直被占着(内存泄漏)。堆就是这样一个 “动态分配的内存区域”,程序运行时可以主动申请(如 C 语言的 malloc、Python 的 list 扩容)和释放(free),空间大小不固定,灵活但需要自己管理
// 使用 malloc 分配内存int*ptr_malloc =(int*)malloc(5*sizeof(int));// 使用 calloc 分配内存int*ptr_calloc =(int*)calloc(5,sizeof(int));// 使用 realloc 调整内存大小 ptr_malloc =(int*)realloc(ptr_malloc,10*sizeof(int));// 释放内存free(ptr_malloc);free(ptr_calloc);
  • 上面的代码均在堆上执行

4.数据段

  • 数据段用于存储全局变量和静态变量
  • 全局变量在程序的整个生命周期内都存在,并且可以被程序的任何部分访问。
  • 数据段的代码程序运行时一直存在,直到程序结束才释放
// 全局变量,存储在数据段int global_variable =10;// 静态变量,存储在数据段staticint static_variable =20;voidfunc(){staticint static_age =18;// 静态局部变量,也存在数据段}

5.代码段

  • 代码段存放程序的可执行代码(二进制指令),比如我们写的if、for、函数逻辑等,运行时只能被执行,不能被修改(防止程序运行时代码被破坏)
在这里插入图片描述

二、例题带练巩固C/C++语言中内存分布的知识

  • 学习完上面的知识,我们用一道典型的企业面试常考例题来巩固一下我们刚刚学到的知识
我们来看下面的一段代码和相关问题
int globalVar =1;staticint staticGlobalVar =1;voidTest(){staticint staticVar =1;int localVar =1;int num1[10]={1,2,3,4};char char2[]="abcd";constchar* pChar3 ="abcd";int* ptr1 =(int*)malloc(sizeof(int)*4);int* ptr2 =(int*)calloc(4,sizeof(int));int* ptr3 =(int*)realloc(ptr2,sizeof(int)*4);free(ptr1);free(ptr3);}
  • 题目
在这里插入图片描述

题目讲解

int globalVar =1;staticint staticGlobalVar =1;voidTest(){staticint staticVar =1;int localVar =1;int num1[10]={1,2,3,4};char char2[]="abcd";constchar* pChar3 ="abcd";int* ptr1 =(int*)malloc(sizeof(int)*4);int* ptr2 =(int*)calloc(4,sizeof(int));int* ptr3 =(int*)realloc(ptr2,sizeof(int)*4);free(ptr1);free(ptr3);}
  • 结合我们刚刚讲的知识和例子和知识
  • globalVar:全局变量,存放在 数据段(静态区)数据段用于存放全局变量和静态变量。
  • staticGlobalVar:静态全局变量,也在 数据段(静态区)。静态全局变量本质还是全局变量,只是作用域受限。
  • staticVar:静态局部变量,存于 数据段(静态区)。静态局部变量生命周期长,存储在数据段
  • localVar:局部变量,存于 栈 中。栈用于存放局部变量,由系统自动分配释放。
  • num1:局部数组,也存于 栈 中,数组作为局部变量,空间在栈中分配。
  • char2:字符数组(局部),存于 栈。数组本身是局部变量,元素空间在栈中。
  • *char2:char2 是栈中的数组,*char2 访问栈中数组元素,存于 栈。
  • pChar3:指针变量(局部),存于 栈。指针本身作为局部变量,空间在栈。
  • *pChar3:pChar3 指向字符串常量 “abcd”,字符串常量存于 代码段(常量区)。代码段存放常量
  • ptr1:指针变量(局部),存于 栈。指针本身是局部变量,在栈中。
  • *ptr1:ptr1 指向 malloc 分配的内存,malloc 分配的内存来自 堆。堆用于动态内存分配。

题目答案

结合我们刚刚的知识。想必大家已经搞定了刚刚的例题,下面我们将例题的答案放出来

在这里插入图片描述

三、C语言动态内存分配(知识回顾)

  • 在正式开始C++的new与delete之前,我们需要回顾并补充一些知识C语言的知识,以便我们后续的理解

3.1 为什么需要动态内存分配

在许多场景下,我们在编写代码时可能并不清楚程序运行时到底需要多少内存。

  • 例如,你要存储用户输入的一串字符,可用户输入的字符数量并不固定。
  • 这种情况下,动态内存分配就能派上用场,它可以让程序在运行时根据实际需求来分配内存。

C语言提供了几个用于动态内存分配的函数,这些函数都在 <stdlib.h> 头文件中。

3.2 malloc 函数

malloc 函数用于分配指定大小的内存块,其原型如下:

void*malloc(size_t size);

size 表示要分配的内存字节数,若分配成功,函数会返回一个指向该内存块起始位置的指针;若分配失败,则返回 NULL

下面是一个简单的示例:

#include<stdio.h>#include<stdlib.h>intmain(){// 分配一个能存储 5 个整数的内存块int*ptr =(int*)malloc(5*sizeof(int));if(ptr ==NULL){printf("内存分配失败\n");return1;}// 给分配的内存块赋值for(int i =0; i <5; i++){ ptr[i]= i;}// 打印内存块中的值for(int i =0; i <5; i++){printf("%d ", ptr[i]);}printf("\n");// 释放分配的内存free(ptr);return0;}
在这里插入图片描述

3.3 calloc 函数

calloc 函数和 malloc 类似,不过它会把分配的内存初始化为 0。其原型如下:

void*calloc(size_t num,size_t size);

num 代表要分配的元素数量,size 表示每个元素的大小。

以下是使用 calloc 的示例:

#include<stdio.h>#include<stdlib.h>intmain(){// 分配一个能存储 5 个整数的内存块,并初始化为 0int*ptr =(int*)calloc(5,sizeof(int));if(ptr ==NULL){printf("内存分配失败\n");return1;}// 打印内存块中的值for(int i =0; i <5; i++){printf("%d ", ptr[i]);}printf("\n");// 释放分配的内存free(ptr);return0;}
在这里插入图片描述

3.4 realloc 函数

realloc 函数用于调整已分配内存块的大小。其原型如下:

void*realloc(void* ptr,size_t size);

ptr 是指向已分配内存块的指针,size 是新的内存块大小。若分配成功,函数会返回一个指向新内存块起始位置的指针;若失败,则返回 NULL

下面是使用 realloc 的示例:

#include<stdio.h>#include<stdlib.h>intmain(){// 分配一个能存储 5 个整数的内存块int*ptr =(int*)malloc(5*sizeof(int));if(ptr ==NULL){printf("内存分配失败\n");return1;}// 给分配的内存块赋值for(int i =0; i <5; i++){ ptr[i]= i;}// 调整内存块大小,使其能存储 10 个整数 ptr =(int*)realloc(ptr,10*sizeof(int));if(ptr ==NULL){printf("内存重新分配失败\n");return1;}// 给新分配的内存块赋值for(int i =5; i <10; i++){ ptr[i]= i;}// 打印内存块中的值for(int i =0; i <10; i++){printf("%d ", ptr[i]);}printf("\n");// 释放分配的内存free(ptr);return0;}
在这里插入图片描述

3.5 free 函数

free 函数用于释放之前动态分配的内存。其原型如下:

voidfree(void* ptr);

ptr 是指向要释放的内存块的指针。一旦内存被释放,就不能再使用该指针访问这块内存了。

3.6 C语言动态内存分配的缺点

  • 在我们刚刚的代码中,我们发现
  • 在 C 语言中,使用malloc、calloc和realloc函数进行动态内存分配时,返回的是void*类型的指针。
  • 这就要求程序员手动将其转换为所需的指针类型,若转换出错,就可能在运行时引发难以调试的错误
  • 而且,C 语言主要是面向过程的语言,动态分配的内存只是简单的字节块,无法自动调用对象的构造函数和析构函数。
  • 在处理复杂的数据类型(如类对象)时,就需要手动管理对象的初始化和清理工作,容易出错
  • C 语言没有内置的异常处理机制,在内存分配失败时,通常只能通过返回NULL指针来表示错误,程序员需要手动检查返回值并进行相应处理,代码会变得繁琐

因此C++ 引入了new和delete运算符来进行动态内存分配和释放,它们能很好地解决 C 语言动态内存分配的部分问题

四、C++动态内存分配

1. new 和 delete

C++ 引入了new和delete运算符来进行动态内存分配和释放,它们能很好地解决 C 语言动态内存分配的部分问题

  • new 运算符:用于动态分配内存,同时会自动调用对象的构造函数。
  • delete 运算符:用于释放动态分配的内存,同时会自动调用对象的析构函数
#include<iostream>classMyClass{public:MyClass(){ std::cout <<"构造函数被调用"<< std::endl;}~MyClass(){ std::cout <<"析构函数被调用"<< std::endl;}};intmain(){// 使用new分配内存并创建对象 MyClass *obj =newMyClass();// 使用对象// ...// 使用delete释放内存delete obj;return0;}
在这里插入图片描述

2. new[] 和 delete[] 运算符

  • 若要动态分配数组,可使用new[]和delete[]运算符
#include<iostream>classMyClass{public:MyClass(){ std::cout <<"构造函数被调用"<< std::endl;}~MyClass(){ std::cout <<"析构函数被调用"<< std::endl;}voidprintMessage(){ std::cout <<"这是 MyClass 类对象的消息。"<< std::endl;}};intmain(){// 使用new[]分配数组内存 MyClass* arr =new MyClass[3];// 使用数组,遍历数组并调用成员函数for(int i =0; i <3;++i){ arr[i].printMessage();}// 使用delete[]释放数组内存delete[] arr;return0;}eturn 0;}
在这里插入图片描述

五、C与C++内存管理对比

5.1 C语言内存管理

C语言主要通过标准库函数来进行内存管理,核心函数有 malloccallocreallocfree。下面是这些函数的详细介绍:

  • malloc:用来分配指定大小的内存块,返回的是 void* 类型指针,需要手动进行类型转换。分配的内存内容是未初始化的。
  • calloc:功能和 malloc 类似,不过它会把分配的内存初始化为 0。
  • realloc:用于调整已经分配的内存块大小,可以扩大或缩小。
  • free:释放之前动态分配的内存,释放后该内存可被系统重新使用。

5.2 C++内存管理

C++ 除了可以使用 C 语言的内存管理函数,还引入了 newdelete 运算符来进行动态内存管理。

  • new:用于动态分配内存,会自动调用对象的构造函数。对于单个对象使用 new,对于数组使用 new[]
  • delete:用于释放 new 分配的内存,会自动调用对象的析构函数。对应 new 使用 delete,对应 new[] 使用 delete[]

5.3 C 与 C++ 内存管理对比表格

对比项C 语言C++
内存分配函数/运算符malloccallocreallocnewnew[]
内存释放函数/运算符freedeletedelete[]
类型安全性返回 void* 指针,需要手动类型转换,类型安全性低直接返回正确类型的指针,无需手动转换,类型安全性高
对象构造和析构不支持自动调用构造和析构函数,需要手动管理自动调用构造和析构函数,简化对象生命周期管理
异常处理内存分配失败返回 NULL,需手动检查内存分配失败抛出 std::bad_alloc 异常,可使用 try-catch 处理
代码风格面向过程,使用函数进行内存管理面向对象,使用运算符进行内存管理,与类和对象结合紧密
  • C 语言的内存管理方式更偏向于底层和过程化,需要程序员手动处理很多细节,容易出错。
  • 而 C++ 的内存管理方式在类型安全性、对象生命周期管理和异常处理方面有很大改进,更适合开发大型、复杂的面向对象程序。
  • 不过,在一些性能敏感或者需要与 C 代码兼容的场景中,C 语言的内存管理方式仍然很有用。

以上就是这篇博客的全部内容,下一篇我们将继续探索C++中new和delete中更多精彩内容。

我的个人主页,欢迎来阅读我的其他文章
https://blog.ZEEKLOG.net/2402_83322742?spm=1011.2415.3001.5343
我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.ZEEKLOG.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482
非常感谢您的阅读,喜欢的话记得三连哦
在这里插入图片描述

Read more

BeyondCompare安装(永久免费使用+全网最详细版)

BeyondCompare安装(永久免费使用+全网最详细版)

一.下载: * 阿里云盘(不限速) https://www.alipan.com/s/WaG1z54BQ2U 官网下载(速度较慢): https://www.scootersoftware.com/download.php 二.安装(无脑下一步即可) 三.永久免费使用: 1. 在搜索栏中输入 regedit ,打开注册表 2. 删除项目:计算机 \HKEY_CURRENT_USER\Software\ScooterSoftware\Beyond Compare 4\CacheId 修改注册表 四.每周自动删掉CacheId: 1.创建删除CacheId脚本,命名为freshBeyondcompare4.bat(注意:这里不要放在有中文路径的文件夹下) ```python # 内容如下:

By Ne0inhk
从安装到代码提交:Git 远程协作中 90% 的问题都能在这里找到答案

从安装到代码提交:Git 远程协作中 90% 的问题都能在这里找到答案

工欲善其事,必先利其器。 目录 * 安装 Git 的步骤: * 本地Git与远程仓库连接及操作全指南 * 一、本地仓库初始化与远程仓库连接 * 1. 初始化本地Git仓库 * 2. 关联远程仓库 * 1. 查看当前分支状态 * 2. 新建本地分支 * 方法1:基于当前分支创建新分支 * 方法2:创建并直接切换到新分支(推荐) * 方法3:基于远程分支创建本地分支 * 3. 切换到已有的本地分支 * 二、分支管理与远程分支同步 * 1. 查看远程分支 * 2. 拉取远程分支到本地 * 三、代码提交与推送到远程仓库 * 1. 常规提交流程 * 2. 简化推送命令 * 四、远程仓库信息查看与更新 * 1. 查看远程仓库详细信息 * 2. 同步远程仓库最新数据 * 五、常见问题解决与优化配置 * 1. 网络与连接问题修复 * 2. 推送大文件或提升传输稳定性

By Ne0inhk

【GitHub项目推荐--OpenAkita:自我进化的开源AI助手框架】⭐⭐⭐

简介 OpenAkita 是一个开源的自我进化AI助手框架,由OpenAkita团队开发并维护。该项目以其独特的“永不放弃”的设计理念而闻名——正如其名所寓意的秋田犬一样,忠诚、可靠且持续学习。与其他AI助手不同,OpenAkita在用户关闭聊天后不会忘记一切,而是能够自主学习新技能、修复自身错误,并记住用户的所有信息。框架支持3分钟快速设置,仅需一个API密钥即可启动,提供8种预设人格、6种即时通讯平台集成,甚至具备发送表情包的能力,为AI助手注入了独特的“灵魂”。 核心价值: * 自我进化:AI助手在用户睡眠时自主学习、记忆巩固和错误修复 * 人格化体验:8种预设人格(女友、管家、Jarvis等)提供沉浸式交互 * 极简部署:桌面应用程序实现3分钟从安装到对话的完整流程 * 开放生态:基于Agent Skills和MCP开放标准,支持一键技能安装 技术定位:OpenAkita填补了传统静态AI助手与动态学习系统之间的空白。它不仅仅是一个对话工具,更是一个能够随时间推移而不断进化的智能伙伴。通过将记忆管理、自我检查和技能生成等能力内置到框架核心,它为开发者提供了一个构

By Ne0inhk
第20届缩微光电开源—循迹部分

第20届缩微光电开源—循迹部分

在刚刚过去的20届智能车竞赛中,本人负责软件的所有工作,在这个过程中学习到了无数宝贵的知识和经验,折线镜头组不知道以后还会不会出现,但是还是开源出来,希望能帮助到有需要的人。 在这届缩微光电的比赛中,我见到的车大多数都是用的镜头,自己也是镜头循迹的。循迹主要是靠青山佬开源的元素行元素列方法,具体的算法详见下面这个链接,这个算法真是非常顶级,青山佬讲的也是非常好,在这里我只把我用元素行元素列处理元素的方法开源出来供大家参考。 【20届智能车竞赛|走进缩微组别-青山和你一起开启智能车之旅!】 https://www.bilibili.com/video/BV1RkoQYsEQu/?share_source=copy_web&vd_source=b3d6e49592556ffe946be56f617991c3 1、基本循迹 图像处理的本质其实就是处理一个二维的图像数组,以常见的120*188图像为例,定义一个image【120】【188】数组。数组的每个值就是一个0—255的灰度值,我们对这一帧图像提取边线信息进行处理,就可以进行循迹了。我用的是大津法二值化循迹,在ZEEKLOG有

By Ne0inhk