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

08 Python 数据分析:学生画像匹配与相似度计算

Python 数据分析:学生画像匹配与相似度计算 适合人群:Python 初学者 / 数据分析入门 / 推荐系统基础学习者 / 教学案例分享 在数据分析和机器学习中,我们经常会遇到这样的问题: * 如何判断两个学生的学习习惯是否相似? * 如何衡量两个商品是不是“同类竞品”? * 为什么推荐系统能给你推送“你可能喜欢”的内容? * 两段文本内容相似,应该怎么用数据来表示? 这些问题,归根到底,都指向一个核心概念: 相似性度量 本文将通过“学生画像匹配”和“课程评价文本分析”两个小案例,带你理解下面几个非常常用的概念: * 欧氏距离(Euclidean Distance) * 曼哈顿距离(Manhattan Distance) * 余弦相似度(Cosine Similarity) 并结合 Python 完成简单实战。 一、案例引入:谁和你最像? 假设我们想根据学生的学习数据,寻找“和你最相似的同学”。 比如现在有三位学生的成绩数据: 学生数学英语A8085B8288C6070 问题来了:

By Ne0inhk
java面试这一篇就够了(干货)

java面试这一篇就够了(干货)

前言 一、基础篇 1.1.Java语言有哪些特点 1、简单易学、有丰富的类库 2、面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高) 3、与平台无关性(JVM是Java跨平台使用的根本) 4、可靠安全 5、支持多线程 1.2.面向对象和面向过程的区别 面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发,以函数为单位,一步一步完成,后期出现问题 可能会牵一发而动全身. 面向对象:以对象为最小单位是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要低。 1.3.

By Ne0inhk
一次搭好、终身不乱Windows Python 环境治理(EPGF)系列总览 / 阅读路线图 [目录]

一次搭好、终身不乱Windows Python 环境治理(EPGF)系列总览 / 阅读路线图 [目录]

【EPGF 白皮书】路径治理驱动的多版本 Python 架构—— Windows 环境治理与 AI 教学开发体系 一次搭好、终身不乱 Windows Python 环境治理(EPGF)系列总览 / 阅读路线图 [目录] EPGF(Engineering Python Governance Framework) 一套专为 Windows 设计的 Python 环境治理与教学落地体系 —— 用工程化方法,终结“环境地狱” 一、这不是一套“工具教程”,而是一套环境治理体系 如果你点进这个系列,是因为你曾遇到过下面任意一种情况: * Python 装了,但不知道现在用的是哪个 * 项目能跑,但换电脑 / 换同学就全崩 * 虚拟环境创建了,依赖却“跑丢了” * 工具越装越多,C 盘越来越乱 * 教学中

By Ne0inhk
2024第十六届蓝桥杯模拟赛(第二期)-Python

2024第十六届蓝桥杯模拟赛(第二期)-Python

提示:前五题是填空,不需要提交代码。 知识点: 1、质因数、质数、约数。 2、动态规划(dp)。 3、思维。    一、【问题描述】 如果一个数 p 是个质数,同时又是整数 a 的约数,则 p 称为 a 的一个质因数。 请问, 2024 的最大的质因数是多少?【答案提交】 这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。 1、思路:质数的判定https://www.acwing.com/file_system/file/content/whole/index/content/4443622/ 2、

By Ne0inhk