前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++奇迹之旅:C++内存管理的机制初篇

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

作者头像
学习起来吧
发布2024-05-06 08:42:31
1050
发布2024-05-06 08:42:31
举报
文章被收录于专栏:学习C/++学习C/++

📝C/C++内存分布

这是C/C++中程序内存区域划分图:

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

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

代码语言:javascript
复制
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 我们只能读取字符串的内容,而不能修改它。
  1. *pChar3 在栈中, pChar3 在代码段(常量区),指针变量 pChar3 存储在栈中,*pChar3 指向一个字符串常量,该字符串常量存储在代码段(常量区)中,代码段(常量区)用于存储程序中的常量数据,如字符串常量、枚举常量等。这些常量在程序执行期间不会被修改。
  2. ptr1 是局部指针变量,存储在栈上
  3. *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 返回指针本身的大小,而不是它所指向的对象的大小。

示例:

代码语言:javascript
复制
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 可以计算指针所指向的字符串的长度。 示例:
代码语言:javascript
复制
char str[] = "hello";
printf("Length of str: %zu\n", strlen(str)); // 输出: 5

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

  1. malloc: 语法void* malloc (size_t size); 功能:动态分配指定大小的内存块,并返回指向该内存块的指针, 分配的内存块内容是未初始化的。 使用方法
代码语言:javascript
复制
int* ptr = (int*)malloc(sizeof(int) * 4);
if (ptr == NULL) 
{
    // 内存分配失败,处理错误
    return;
}
// 使用分配的内存
// ...
free(ptr); // 释放内存
  1. calloc: 语法void* calloc (size_t num, size_t size); 功能:动态分配指定数量和大小的内存块,并返回指向该内存块的指针,分配的内存块内容会被初始化为0使用方法
代码语言:javascript
复制
// 分配 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)使用方法
代码语言:javascript
复制
// 先分配 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,则该函数不执行任何操作。 使用方法
代码语言:javascript
复制
int *ptr = (int *)malloc(4 * sizeof(int));
if (ptr == NULL) 
{
    // 内存分配失败,处理错误
    return;
}
// 使用分配的内存
// ...
free(ptr); // 释放内存
// 不能再访问已释放的内存

常见注意要点:

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

🌉C++内存管理方式

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

代码语言:javascript
复制
#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 函数的效果是相同的。
  • 例如:
代码语言:javascript
复制
 int* ptr = new int;  // 分配一个 int 类型的内存空间
 delete ptr;         // 释放 ptr 指向的内存空间

分配内存,但没有初始化

  • 动态申请一个int类型的空间并初始化为10
代码语言:javascript
复制
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
delete ptr2;

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

代码语言:javascript
复制
 int* arr = new int[10];  //  动态申请10个int类型的空间
 delete[] arr;           // 释放 arr 指向的数组内存空间

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

代码语言:javascript
复制
#include<iostream>
using namespace std;

int main()
{
	// 动态申请一个int类型的空间并初始化为10
	int* ptr3 = new int[10]{ 2,3,4,5,5 };
	delete[] ptr3;

	return 0;
}

这样一部分初始化想要的值,后面默认初始化为0

  • 使用 newdelete操作符时,编译器会自动调用构造函数和析构函数,但对于内置类型来说,这些函数是空操作。

注意:申请和释放单个元素的空间,使用newdelete操作符,申请和释放连续的空间,使用new[]delete[],注意:匹配起来使用。

在这里插入图片描述
在这里插入图片描述

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

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

代码语言:javascript
复制
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++的实现:

代码语言:javascript
复制
struct ListNode
{
	ListNode* _next;
	int _data;

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 📝C/C++内存分布
  • 🌠 C语言中动态内存管理方式
    • 🌉C++内存管理方式
    • 🌠new/delete操作内置类型
      • 🌉C与C++链表构建对比
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档