C++学习笔记----6、内存管理(三)---- 底层内存操作

2024-09-07 02:52

本文主要是介绍C++学习笔记----6、内存管理(三)---- 底层内存操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        C++相对于C的一个非常大的优势就是你不必太担心内存。如果你的代用到了对象,只需要确信每个类可以好好管理自己的内存。通过构造与析构函数,编译器通过告诉你什么时候去做来帮助你管理内存。在类内隐藏了对内存的管理在使用上带来了很大的不同,就像标准库类展示的那样。然而,对于一些应用或者遗留代码,你可能也会碰到需要在底层处理内存。不管是遗留代码、效率、排错或者是好奇,懂得一些对于原始字节的操作技巧还是很有帮助的。

1、指针的算术运算

        C++编译器使用指针的声明类型来允许你执行指针的算术运算。如果你声明了一个整数指针,然后对指针进行加1操作,指针就会在内存中以整型的大小向前移动,而不是以一个单独字节的长度进行移动。对于数组来说这种类型的操作非常有用,因为它们在内存中包含了顺序的同类型的数据。例如,假设你声明了一个在栈上的整型数组:

int* myArray { new int[8] };

你应该对于下面的将索引值为2的元素进行赋值的语法很熟悉了吧:

myArray[2] = 33;

        对于指针算术运算,你可以同样地使用下面的语法,它包含了一个myArray数组的“2个整数之前”的内存的指针,然后通过引用取值来对其进行赋值:

*(myArray + 2) = 33;

        作为访问单个元素的另一种语法,指针算术运算并不那么吸引人。它真正的能力在于事实上像myArray+2这样的表达式仍然是一个指向Int的指针,可以代表一个小一点的整型数组。

        让我们看一个宽字符串的例子。宽字符串会在以后讨论,在我们要表达的观点上其细节并不重要。现在,只要知道宽字符串支持Unicode字符就可以了,例如,日文字符串。wchar_t类型是一个能够放置这样的Unicode字符的字符类型,一般来说要比char大;也就是说,其长度不仅是一个字节。告诉编译器一个字符串常量是一个宽字符串常量,以L为前缀。例如,假设你有下面的宽字符串:

const wchar_t* myString { L"Hello, World" };

        假定你还要有一个以一个宽字符串为输入参数的函数,返回一个包含了输入参数的大写版本的新字符串:

wchar_t* toCaps(const wchar_t* text);

        你可以通过将myString传递给这个函数来将其转化为大写。然而,如果你只是要将myString的部分进行大写转化的话,你可以使用指针算术运算来指向字符串的后面的一部分。下面的代码调用了对World部分的宽字符串的toCaps()函数,简单地在指针上进行了加7的操作,即使wchar_t通常是大于1个字节的:

toCaps(myString + 7);

        另一个指针算术运算的用处就是减法。将一个指针从另一个同类型的指针相减会给出两个指针之间指向类型的元素数量,而不是它们之间的绝对字节数。

2、客户化内存管理

        你碰到的99%的情形(有人可能会说100%),在c++中内置的内存分配工具就足够了。在这背后,new与delete干了所有以适合大小的chunk进行内存分发的所有工作,维护了可用内存区域的列表,在删除时释放内存chunk到列表中。

        当资源限制特别严格时,或者在特别特殊的情况下,例如管理共享内存,实现客户化内存管理可能就是一个可行的选项。别担心----并不像听起来那么吓人。本质上讲,自己管理内存意味着类分配一大串内存,然后根据需要进行小块的内存发放。

        这个方法怎么会好一些呢?管理自己的内存可以有效地减少开销。当你使用new来分配内存时,程序也需要留出一小块空间来记录分配了多少内存。用这种方式,当你调用delete时,合适大小的内存可以被释放。对于大部分对象来说,开销要比内存分配小的多得多,不产生本质性的影响。然而,对于小的对象或者拥有大量对象的程序来说,这个开销可能会有影响。

        当你自己管理内存时,你会预先知道每个对象的大小,也就能够避免每个对象的额外开销。对于大量的小对象来说会有非常大的不同。执行客户化的内存管理需要对new与delete操作符进行重载,这个主题我们以后再讨论。

3、垃圾回收

        在支持垃圾回收的环境,程序员如果有的话,也很少去显式地对对象进行释放内存的操作。事实上,不再被引用的对象会被运行时库在适当时进行自动清理。

        垃圾回收并不像C#与Java一样被集成至c++语言中。在现代c++中,你会用智能指针来管理内存,而在遗留代码中,你会看到在对象层次用new与delete进行内存管理。像shared_ptr(我们会在后面进行讨论)这样的智能指针提供了与内存垃圾回收非常类似的功能;也就是说,对于特定的资源,当最后的shared_ptr实例被破坏时,在这个时点上资源也被破坏了。在c++中实现垃圾回收是可能的,但有没有那么容易,但是,在释放内存的任务中自己进行释放可能会带来新的令人头痛的问题。

        垃圾收集的一个方法被叫做标记且清除。用这个方法,垃圾回收器不断地检查程序中的每个单独的指针,标注所引用的内存依然被使用的事实。在每个循环的结束,任何不再被标注的内存就完成了使命,会被释放掉。在c++中实现这样的算法并不麻烦,但是,如果没有弄对,会比使用delete更容易出错!

        尝试以安全且容易的技术进行垃圾回收已经在C++中进行,但是即使是一个完美的垃圾回收在c++中的实现的出现,也不可能做到对所有应用都适用。其中垃圾回收的负面总结如下:

  • 在垃圾收集器还在运行时,程序不响应了。
  • 对于垃圾收集器,你却有不确定的析构函数。因为在垃圾回收之前,对象不会被破坏,当对象离开其作用范围时,析构函数不会立刻执行。这就意味着被析构函数完成的清理资源(比如关闭文件,释放锁资源等等)会在未来的某个不确定的时间执行。

        写一个垃圾回收装置非常难。不可避免地会出错,还容易出错,更可能的是会变慢,如果你真的想在应用中使用垃圾回收,推荐你调研可重用的既存的专业的垃圾回收库。

4、对象池

        垃圾收集就像为野营购买盘子,把用过的盘子丢在露营地,在某个时间点有人把它们捡起来扔掉。当然了,内存管理也有一个更生态化的方法。

        对象池就是相应的循环池。你买了一些盘子,在用过一个以后,洗好之后留作后用。对象池对于你需要使用同类型的对象多次的情况的理想选择,生成每一个对象都会有开销产生。

        对于使用对象池进行性能提升的细节我们以后再讨论。

这篇关于C++学习笔记----6、内存管理(三)---- 底层内存操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1143850

相关文章

Golang基于内存的键值存储缓存库go-cache

《Golang基于内存的键值存储缓存库go-cache》go-cache是一个内存中的key:valuestore/cache库,适用于单机应用程序,本文主要介绍了Golang基于内存的键值存储缓存库... 目录文档安装方法示例1示例2使用注意点优点缺点go-cache 和 Redis 缓存对比1)功能特性

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

C++实现回文串判断的两种高效方法

《C++实现回文串判断的两种高效方法》文章介绍了两种判断回文串的方法:解法一通过创建新字符串来处理,解法二在原字符串上直接筛选判断,两种方法都使用了双指针法,文中通过代码示例讲解的非常详细,需要的朋友... 目录一、问题描述示例二、解法一:将字母数字连接到新的 string思路代码实现代码解释复杂度分析三、

mac安装nvm(node.js)多版本管理实践步骤

《mac安装nvm(node.js)多版本管理实践步骤》:本文主要介绍mac安装nvm(node.js)多版本管理的相关资料,NVM是一个用于管理多个Node.js版本的命令行工具,它允许开发者在... 目录NVM功能简介MAC安装实践一、下载nvm二、安装nvm三、安装node.js总结NVM功能简介N

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Python调用Orator ORM进行数据库操作

《Python调用OratorORM进行数据库操作》OratorORM是一个功能丰富且灵活的PythonORM库,旨在简化数据库操作,它支持多种数据库并提供了简洁且直观的API,下面我们就... 目录Orator ORM 主要特点安装使用示例总结Orator ORM 是一个功能丰富且灵活的 python O

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::