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

相关文章

在C#中分离饼图的某个区域的操作指南

《在C#中分离饼图的某个区域的操作指南》在处理Excel饼图时,我们可能需要将饼图的各个部分分离出来,以使它们更加醒目,Spire.XLS提供了Series.DataFormat.Percent属性,... 目录引言如何设置饼图各分片之间分离宽度的代码示例:从整个饼图中分离单个分片的代码示例:引言在处理

MySQL底层文件的查看和修改方法

《MySQL底层文件的查看和修改方法》MySQL底层文件分为文本类(可安全查看/修改)和二进制类(禁止手动操作),以下按「查看方法、修改方法、风险管控三部分详细说明,所有操作均以Linux环境为例,需... 目录引言一、mysql 底层文件的查看方法1. 先定位核心文件路径(基础前提)2. 文本类文件(可直

Python列表的创建与删除的操作指南

《Python列表的创建与删除的操作指南》列表(list)是Python中最常用、最灵活的内置数据结构之一,它支持动态扩容、混合类型、嵌套结构,几乎无处不在,但你真的会创建和删除列表吗,本文给大家介绍... 目录一、前言二、列表的创建方式1. 字面量语法(最常用)2. 使用list()构造器3. 列表推导式

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

C++构造函数中explicit详解

《C++构造函数中explicit详解》explicit关键字用于修饰单参数构造函数或可以看作单参数的构造函数,阻止编译器进行隐式类型转换或拷贝初始化,本文就来介绍explicit的使用,感兴趣的可以... 目录1. 什么是explicit2. 隐式转换的问题3.explicit的使用示例基本用法多参数构造

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

C++打印 vector的几种方法小结

《C++打印vector的几种方法小结》本文介绍了C++中遍历vector的几种方法,包括使用迭代器、auto关键字、typedef、计数器以及C++11引入的范围基础循环,具有一定的参考价值,感兴... 目录1. 使用迭代器2. 使用 auto (C++11) / typedef / type alias

C++ scoped_ptr 和 unique_ptr对比分析

《C++scoped_ptr和unique_ptr对比分析》本文介绍了C++中的`scoped_ptr`和`unique_ptr`,详细比较了它们的特性、使用场景以及现代C++推荐的使用`uni... 目录1. scoped_ptr基本特性主要特点2. unique_ptr基本用法3. 主要区别对比4. u