【C++】C / C++内存管理(上)

简介: 【C++】C / C++内存管理(上)

👉C / C++内存分布👈


从C语言的角度来看内存,可以将内存简单地划分为栈区、堆区、静态区和常量区。


9eb750c2303544ec9ff7884d077d73cf.png

而从操作系统的角度来说,将内存划分为内核空间、栈区、内存映射段、堆区、数据段(静态区)和代码段(常量区)。


493957b4f66941c789b0689a50919e10.png


那为什么操作系统要把内存划分为一个个区域呢(分段)?其实这样子划分,是为了更好地管理数据,提高效率。因为不同的数据有着不同的性质。那有什么性质呢?我们一起来看看。


栈区:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束这些内容也会自动被销毁。其特点是效率高,但空间大小有限。

堆:由 malloc 系列函数或 new 操作符分配的内存。其生命周期由 free 或delete 决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错。

数据段(静态区):保存自动全局变量和 static 变量(包括 static 全局和局部变量)。静态区的内容在整个程序的生命周期内都存在,由编译器在编译的时候分配。

代码段(常量区):存储可执行代码(二进制指令代码)和只读常量,这个区域的数据是被硬件保护的,不可以修改里面的数据。


注:栈区是向下增长的,堆区是向上增长的。局部变量和堆区的数据不会提前创建,而是随着栈帧的创建而创建,只有全局数据和静态数据、常量会被提前创建。栈可以通过函数_alloca进行动态分配,不过注意,所分配空间不能通过free或delete进行释放。堆无法静态分配,只能动态分配。


int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
  static int staticVar = 1;
  int localVar = 1;
  int num1[10] = { 1, 2, 3, 4 };
  char char2[] = "abcd";
  const char* 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);
}
1. 选择题:
选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里?____
char2在哪里?____  *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____
2. 填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
3. sizeof 和 strlen 区别?


大家可以想一想上面的题目的答案是什么,我把参考答案写在下面,供大家参考。


C C C A A A A A D A B

40 5 4 4/8 4 4/8

sizeof 是操作符,计算类型、变量或者表达式的大小,单位是字节;而 strlen 是一个库函数,求'\0'之前字符出现的个数,也就是求字符串的长度。

提示:sizeof(数组名),计算的是整个数组的大小。

大家可以根据下图,再想想以上的问题为什么是这个答案。在这里,我只解释一下易错的题目。为什么 *char2 在栈区,而 *pChar3 在数据段呢?字符数组 char2 是在栈区开辟空间的,那么该数组存储的数据也在栈区上。pChar3 存的是存在代码段中“abcd”的首元素地址,那么 *pChar3 就是字符 a,其是存在代码段的。


749fccaab5d24e00b189acffcf807fe2.png


6f9fee62ed1d46dfb52b7bd7b16905d8.png

64 位操作系统虚拟进程地址空间有 2^24 TB,那我们的硬件能跟上吗?其实虚拟进程地址空间并不是硬件上真正的内存,操作系统会对虚拟进程空间进行分页。分页之后会有页表和物理内存,将虚拟内存和物理内存建立映射关系。


👉C语言中动态内存管理方式👈


void Test ()
{
  int* p1 = (int*) malloc(sizeof(int));
  free(p1);
  // 1.malloc/calloc/realloc的区别是什么?
  int* p2 = (int*)calloc(4, sizeof (int));
  int* p3 = (int*)realloc(p2, sizeof(int)*10);
  // 这里需要free(p2)吗?不需要
  free(p3 );
}


【面试题】

  1. malloc/calloc/realloc的区别?
  2. malloc的实现原理? glibc中malloc实现原理


👉C++内存管理方式👈


C语言内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。


new和delete操作内置类型


void Test()
{
  // 动态申请一个int类型的空间,不会初始化
  int* ptr4 = new int;
  // 释放空间
  delete ptr4;
  ptr4 = nullptr;
  // 动态申请一个int类型的空间, 并初始化为10
  int* ptr5 = new int(10);
  delete ptr5;
  ptr5 = nullptr;
  // 动态申请10个int的空间,不会初始化
  int* ptr6 = new int[10];
  delete[] ptr6;
  ptr6 = nullptr;
  // 动态申请10个int的空间,前四个初始化为1 2 3 4,后六个初始化为0
  int* ptr7 = new int[10]{ 1,2,3,4 };
  delete[] ptr7;
  ptr7 = nullptr;
}


66ea97c360ac4fcea89229a26b678908.png


注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],要匹配起来使用。对于内置类型,new 和 delete 相比于malloc 和 free,除了用法上的区别,没有其他区别。


new和delete操作自定义类型


b6988fc65e464279900d7dfa06737bf7.png


01ec265dd1db41ffb444118a151347d9.png


注:对于自定义类型,new/delete 和 malloc/free 最大区别是 new/delete除了开空间,还会调用自定义类型的构造函数和析构函数,而 malloc/free 不会调用自定义类型的构造函数和析构函数。


有了 new 和delete,那么之前我们写的链表就可以这样子玩了。


#include <iostream>
using namespace std;
struct ListNode
{
  ListNode* _next;
  int _val;
  ListNode(int val = 0)
    :_next(nullptr)
    ,_val(val)
  {}
};
int main()
{
  ListNode* n1 = new ListNode(1);
  ListNode* n2 = new ListNode(2);
  ListNode* n3 = new ListNode(3);
  ListNode* n4 = new ListNode(4);
  n1->_next = n2;
  n2->_next = n3;
  n3->_next = n4;
  ListNode* cur = n1;
  while (cur != nullptr)
  {
    cout << cur->_val << ' ';
    cur = cur->_next;
  }
  cout << endl;
  return 0;
}

3f11d0d9f3054d41b1c046336cda4fc1.png



相比于C语言构建链表,C++ 构建链表是不是相当的简单,相当的好玩?


new和delete 的常见错误


new 和 delete 最常见的错误就是不匹配使用。如果不匹配使用,可能会发生意想不到的错误,也有可能不会发生错误。

6cfb7df15c1a4ddbbd757742b08ce55e.png


6056d2a6256b42e38f637ac183913b74.png


不匹配使用会不会报错,这取决于 new 和 delete 的底层实现。而其底层实现取决于编译器,所以同一段代码在不同的编译器可能会报错,也可能不会报错。但是不报错并不意味着程序没有问题。


注:以下例子均在 VS2022 上运行。


下图的代码是正确的代码,调用了十次构造函数和析构函数。那如果将 delete[] 改成 delete,会发生什么呢?我们来看一下。


f2bd0af9c1d64377a37f98487c16d0fe.png



通过下图可以看到,程序直接崩溃挂掉了,并不是内存泄漏。因为内存泄漏不会一下子就会是程序挂掉。

39e2916a4e6d4237b0c39f0dc7b5a38a.png


申请对象数组,会调用构造函数10次,delete 由于没有使用 [],此时只会调用一次析构函数,但往往会引发程序崩溃。


我们将自定义类型 A 的析构函数屏蔽掉,再来看看程序会不会直接崩溃掉。


f98255ba17574aa482653926692701fd.png



好像程序没有报错哦。那为什么会这个样子呢?那我们就需要了解底层机制的实现了。如果写上 [],编译器认为你申请了多个空间,它会在前面多申请一个空间来存储你申请空间的个数,释放这些内存的时候往前找出申请空间的个数,然后再来释放申请的内存空间。


a734c7e824984eb783909a6d4e58e5e3.png

所以 deletep[ ] 省略 [ ] 时,就会报错,因为释放的位置不对。而如果屏蔽掉自己写的析构函数,那么编译器会自动生成析构函数。编译器自己生成析构函数的话,就不会在前面多开一个字节来存储申请空间的个数,也不会去调用析构函数,所以程序就没有报错。


所以,下面的代码也会有问题。



20a420f7501643849cf3d208a8b85813.png
























相关文章
|
2月前
|
编译器 C++ 开发者
【C++】深入解析C/C++内存管理:new与delete的使用及原理(三)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
21天前
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
41 3
|
28天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
66 4
|
2月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
137 21
|
2月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
44 0
【C++打怪之路Lv6】-- 内存管理
|
2月前
|
存储 C语言 C++
【C/C++内存管理】——我与C++的不解之缘(六)
【C/C++内存管理】——我与C++的不解之缘(六)
|
2月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
72 1
|
2月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
266 1
|
2月前
|
存储 安全 程序员
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
66 3