C++奇迹之旅:C++内存管理的机制初篇

ops/2024/9/25 10:57:29/

请添加图片描述

文章目录

  • 📝C/C++内存分布
  • 🌠 C语言中动态内存管理方式
    • 🌉C++内存管理方式
  • 🌠new/delete操作内置类型
    • 🌉C与C++链表构建对比
  • 🚩总结


📝C/C++内存分布

这是C/C++中程序内存区域划分图:
在这里插入图片描述
数据段:也叫静态数据段或初始化数据段,用于存储程序中的全局变量和静态变量,这些变量在程序启动时就已经分配好内存空间并初始化。
代码段:也叫文本段或指令段,用于存储程序的可执行指令代码。
这部分内存区域通常是只读的,程序在运行时不能修改代码段中的内容。

我们先来看下面的一段代码和相关问题

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在哪里?C staticGlobalVar在哪里?C
    staticVar在哪里?C localVar在哪里?A
    num1 在哪里?A

char2在哪里?A *char2在哪里?_A
pChar3在哪里?A *pChar3在哪里?D
ptr1在哪里?A *ptr1在哪里?B

  1. 全局变量 globalVarstaticGlobalVar 都存储在数据段(静态区)中。全局变量globalVar 的生命周期贯穿整个程序的执行过程,直到程序结束,静态全局变量 staticGlobalVar 的作用域仅限于当前源文件,其生命周期也贯穿整个程序的执行过程。

  2. staticVar 是静态局部变量,也存储在数据段(静态区)中。

  3. localVar 是普通的局部变量,存储在栈中,栈是一种后进先出(LIFO)的数据结构,用于存储函数调用时的局部变量和返回地址等信息,当函数调用结束时,栈中分配给该函数的内存空间会被自动释放。

  4. 局部数组 num1 存储在栈中,数组在内存中是连续分布的,因此 num1 占用了一块连续的栈空间。

  5. *char2char2 在栈中,
    *char2char2[] 是一个局部字符数组,存储在栈上。当你使用字符串字面量初始化它时,编译器会在栈上分配足够的内存空间,并将字符串字面量的内容(包括结尾的 \0)复制到这块内存中,所以 *char2 指向的是存储在栈上的可修改的字符数组。
    *pChar3const char* pChar3 = "abcd"; 中的字符串字面量 "abcd" 存储在只读的数据段(常量区)中。而pChar3 本身是一个指针变量,存储在栈上,它指向常量区中的字符串。由于字符串字面量是只读的,所以通过 *pChar3 我们只能读取字符串的内容,而不能修改它。
    在这里插入图片描述

  6. *pChar3 在栈中, pChar3 在代码段(常量区),指针变量 pChar3 存储在栈中,*pChar3 指向一个字符串常量,该字符串常量存储在代码段(常量区)中,代码段(常量区)用于存储程序中的常量数据,如字符串常量、枚举常量等。这些常量在程序执行期间不会被修改。

  7. ptr1 是局部指针变量,存储在栈上

  8. *ptr1 指向的内容,就是malloc分配的内存,该内存在堆上

总结:

  1. 栈(Stack): 用于存储函数调用时的上下文信息,如返回地址、函数参数和局部变量,遵循先进后出(LIFO)的原则,大小有限,如果使用不当可能导致栈溢出
  2. 堆(Heap): 用于动态分配内存,存储动态分配的对象和数据结构,开发者需要手动管理堆上的内存,分配和释放,大小一般比栈要大得多,但访问速度相对较慢
  3. 数据段(Data Segment): 分为初始化的数据段(.data)和未初始化的数据段(.bss)用于存储全局变量和静态变量,这些变量的生命周期贯穿整个程序执行期
  4. 代码段(Code Segment): 存储可执行的机器指令,通常是只读的,以保护程序代码不被意外修改,存着可执行的代码/只读常量。

填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;

  1. sizeof(num1) = 40;
    num1 是一个包含 10 个 int 类型元素的数组,每个 int 类型占 4 个字节,所以数组大小为 10 * 4 = 40 字节。
  2. sizeof(char2) = 5; strlen(char2) = 4;
    • char2 是一个包含 5 个字符(包括结尾的 '\0')的字符数组,所以 sizeof(char2) 为 5 字节。
    • strlen(char2) 返回字符串的长度,不包括结尾的 '\0',所以为 4。
  3. sizeof(pChar3) = 8; strlen(pChar3) = 4;
    • pChar3 是一个指向字符串常量 "abcd" 的指针,在 32 位系统上,指针大小为 4 字节。在 64 位系统上,指针大小为 8 字节。
    • strlen(pChar3) 返回字符串的长度,不包括结尾的 '\0',所以为 4。
  4. sizeof(ptr1) = 8;
    • ptr1 是一个指向动态分配的 int 类型数组的指针,在 32 位系统上,指针大小为 4 字节。在 64 位系统上,指针大小为 8 字节。

sizeof 和 strlen 区别?
sizeofstrlen 是两个不同的操作符/函数,sizeof 是一个编译时操作,返回变量或数据类型的大小;而 strlen 是一个运行时函数,返回字符串的长度。

  1. sizeof: sizeof 是一个操作符,用于获取变量或数据类型的大小(以字节为单位),它在编译时就确定了返回值,不需要在运行时计算,对于数组,sizeof 返回整个数组的大小,而不是单个元素的大小,对于指针,sizeof 返回指针本身的大小,而不是它所指向的对象的大小。

示例:

char str[] = "hello";
printf("Size of str: %zu\n", sizeof(str)); // 输出: 6 (包括'\0')
printf("Size of char: %zu\n", sizeof(char)); // 输出: 1
  1. strlen:strlen 是一个函数,用于计算字符串的长度(不包括结尾的 '\0' 字符),它在运行时计算字符串的长度,需要遍历整个字符串,对于数组,strlen 只能用于字符数组(字符串),不能用于其他类型的数组,对于指针,strlen 可以计算指针所指向的字符串的长度。
    示例:
char str[] = "hello";
printf("Length of str: %zu\n", strlen(str)); // 输出: 5

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

  1. malloc:
    语法void* malloc (size_t size);
    功能:动态分配指定大小的内存块,并返回指向该内存块的指针, 分配的内存块内容是未初始化的。
    使用方法
int* ptr = (int*)malloc(sizeof(int) * 4);
if (ptr == NULL) 
{// 内存分配失败,处理错误return;
}
// 使用分配的内存
// ...
free(ptr); // 释放内存
  1. calloc:
    语法void* calloc (size_t num, size_t size);
    功能:动态分配指定数量和大小的内存块,并返回指向该内存块的指针,分配的内存块内容会被初始化为0
    使用方法
// 分配 4 个 int 型元素的内存,并初始化为 0
int *ptr = (int *)calloc(4, sizeof(int));
if (ptr == NULL) {// 内存分配失败,处理错误return;
}
// 使用分配的内存,所有元素都被初始化为 0
// ...
free(ptr); // 释放内存
  1. realloc:
    语法void* realloc (void* ptr, size_t size);
    功能:调整已分配内存块的大小,并返回指向新内存块的指针。
    • 如果新大小小于原大小,则保留原有数据;如果新大小大于原大小,则原有数据会被保留,新增部分为未初始化。
    • 如果ptrNULL,则等同于malloc(size)
      使用方法
// 先分配 4 个 int 型元素的内存
int *ptr = (int *)malloc(4 * sizeof(int));
if (ptr == NULL) 
{// 内存分配失败,处理错误return;
}
// 使用分配的内存
// ...
// 重新分配为 8 个 int 型元素的内存
int *new_ptr = (int *)realloc(ptr, 8 * sizeof(int));
if (new_ptr == NULL) 
{// 内存重新分配失败,处理错误free(ptr); // 释放原有内存return;
}
ptr = new_ptr; // 更新指针
// 使用新分配的内存
// ...
free(ptr); // 释放内存
  1. free:
    语法void free (void* ptr);
    功能:释放动态分配的内存块,将其返回给操作系统。注意:必须确保释放的内存块是之前使用malloc/calloc/realloc动态分配的
    • 如果ptrNULL,则该函数不执行任何操作。
      使用方法
int *ptr = (int *)malloc(4 * sizeof(int));
if (ptr == NULL) 
{// 内存分配失败,处理错误return;
}
// 使用分配的内存
// ...
free(ptr); // 释放内存
// 不能再访问已释放的内存

常见注意要点:

  1. 动态分配的内存必须在使用完毕后及时释放,否则会导致内存泄漏。
  2. 不能访问已经释放的内存块,否则会出现未定义行为。
  3. 如果分配失败,这些函数会返回NULL指针,需要进行错误处理。

🌉C++内存管理方式

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

#include<stdlib.h>
int main()
{int* ptr = (int*)malloc(4 * sizeof(int));free(ptr);int* ptr2 = (int*)calloc(4, sizeof(int));//判断是否成功开辟空间,每个还需要检查int* ptr3 = (int*)realloc(ptr, 8 * sizeof(int));free(ptr3);return 0;
}

🌠new/delete操作内置类型

在 C++ 中,newdelete 操作符用于动态内存分配和释放。当使用这些操作符时,需要注意以下几点:

内置类型:

  • 对于内置类型(如 intdoublechar 等),使用 newdelete 操作符与使用 mallocfree 函数的效果是相同的。
  • 例如:
 int* ptr = new int;  // 分配一个 int 类型的内存空间delete ptr;         // 释放 ptr 指向的内存空间

分配内存,但没有初始化
在这里插入图片描述

  • 动态申请一个int类型的空间并初始化为10
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
delete ptr2;

在这里插入图片描述

动态申请10个int类型的空间,并释放

 int* arr = new int[10];  //  动态申请10个int类型的空间delete[] arr;           // 释放 arr 指向的数组内存空间

当然,我们也可以开辟空间的时候,又进行初始化

#include<iostream>
using namespace std;int main()
{// 动态申请一个int类型的空间并初始化为10int* ptr3 = new int[10]{ 2,3,4,5,5 };delete[] ptr3;return 0;
}

这样一部分初始化想要的值,后面默认初始化为0
在这里插入图片描述

  • 使用 newdelete操作符时,编译器会自动调用构造函数和析构函数,但对于内置类型来说,这些函数是空操作。
    在这里插入图片描述
    注意:申请和释放单个元素的空间,使用newdelete操作符,申请和释放连续的空间,使用new[]delete[],注意:匹配起来使用。
    在这里插入图片描述

🌉C与C++链表构建对比

C语言构造链表节点的方式:

struct ListNode
{ListNode* _next;int _data;
};struct ListNode* LTCreateNode(int x)
{struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));if (newnode == NULL) {perror("malloc fail!");exit(1);}newnode->_data = x;newnode->_next = NULL;return newnode;
}

这是C++的实现:

struct ListNode
{ListNode* _next;int _data;ListNode(int data):_next(nullptr), _data(data){}
};

在这里插入图片描述
前面我们知道new不仅会开空间,还会调用构造函数,析构函数的目的是初始化,delete会调用析构函数,因此即使是自定义类型,也可以使用new开空间并初始化。
因此,只要我们写好构造函数,new的使用是真香啊


🚩总结

请添加图片描述


http://www.ppmy.cn/ops/29495.html

相关文章

基于php+mysql+html图书管理系统(含实训报告)

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、Php、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

速盾:cdn真的抗打吗?

CDN&#xff08;Content Delivery Network&#xff09;是一种分布式网络架构&#xff0c;旨在提供高速、可靠的内容传输服务。它通过将内容存储在位于全球各地的服务器上&#xff0c;使用户可以从最近的服务器获取内容&#xff0c;从而提高访问速度和性能。然而&#xff0c;尽管…

微信小程序demo-----制作文章专栏

前言&#xff1a;不管我们要做什么种类的小程序都涉及到宣传或者扩展其他业务&#xff0c;我们就可以制作一个文章专栏的页面&#xff0c;实现点击一个专栏跳转到相应的页面&#xff0c;页面可以有科普类的知识或者其他&#xff0c;然后页面下方可以自由发挥&#xff0c;添加联…

WPF之自定义绘图

1&#xff0c;创建自定义控件类 class CustomDrawnElement:FrameworkElement{public static readonly DependencyProperty BackgroundColorProperty;static CustomDrawnElement(){FrameworkPropertyMetadata meta new FrameworkPropertyMetadata(Colors.SkyBlue);meta.Affects…

C语言--贪吃蛇小游戏

目录 一、Win32API介绍 1.1Win32API 1.2控制台程序 1.3控制台屏幕上的坐标COORD 1.4GetStdHandle 1.5GetConsoleCursorInfo 1.6 CONSOLE_CURSOR_INFO 1.7 SetConsoleCursorInfo 1.8SetConsoleCursorPosition 1.9GetAsyncKeyState 二、贪吃蛇游戏设计与分析 2.1地图 …

小程序地理位置接口权限直接抄作业

小程序地理位置接口有什么功能&#xff1f; 随着小程序生态的发展&#xff0c;越来越多的小程序开发者会通过官方提供的自带接口来给用户提供便捷的服务。但是当涉及到地理位置接口时&#xff0c;却经常遇到申请驳回的问题&#xff0c;反复修改也无法通过&#xff0c;给的理由也…

JS 笔记9 认识JavaScript

相关内容&#xff1a;JS对象、属性、常用事件处理过程、运算符、if...else、for、…… <script type"text/javascript"></script> type属性用来指定MIME(Multipurpose Internet Mail Extension)类型&#xff0c;主要是告诉浏览器目前使用的是哪一种Scri…

k8s部署maven项目

failed to verify certificate: x509: certificate signed by unknown authority 今天在执行kubectl get nodes的时候报的证书验证问题&#xff0c;看了一圈首次搭建k8s的都是高频出现的问题。 couldn’t get current server API group list: Get “https://kubernetes.docker…