尝试动态内存分配 c++

Attempt at dynamic memory allocation c++

本文关键字:分配 c++ 内存 动态      更新时间:2023-10-16

我试图通过使用指针来分配 1 KiB 的内存

GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
std::cout << pmc.WorkingSetSize << " Current physical memory used by the process" << std::endl;
int a = pmc.WorkingSetSize;
char *test= new char[1024];
GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
int b = pmc.WorkingSetSize;
std::cout << "Actual allocated  " << (b - a) / 1024 << std::endl;

问题是每次我运行此代码时,它似乎都会分配 100 KiB 到 400 KiB 之间的任何位置,我使用了 char,因为它的大小为 1 字节

该实现可以自由分配比您背后要求的更多内容,以便能够更快地满足未来(预期)的分配(而无需涉及操作系统)。这通常是一个很好的优化,无需担心 - 特别是因为操作系统在您实际写入它们之前甚至可能不支持实际物理页面的allcation,因此通常根本不花费任何费用。

无论如何,从C++语言的角度来看;只要你得到了你请求的内存,实现是否实际分配了更多,通常不是你能知道的,也不是你应该关心的。

new背后的代码知道操作系统。它通常知道哪些分配大小是有效的。事实证明,大多数程序都会对较小的尺寸进行更多的分配。因此,new背后的代码通常会要求更大的内存块,并根据需要细分每个块。

看起来new在您的情况下要求大约 100kB,分发 1KB 并具有 99kB,因此在不打扰操作系统的情况下满足未来的请求。

::operator new正在分配内存(如文档所示),由您的C++标准库提供(这是一些用英语编写的规范,也是C++标准的一部分,例如在 n3337 中起草)。在您的系统和计算机上,它的实现是使用操作系统服务(原则上,您可能有一些C++标准库实现,它不使用任何操作系统并在一些裸硬件上运行,但我不知道)。你应该期望new&delete需要一些内部开销并消耗资源(但你不应该真正关心并希望它保持合理)。

阅读操作系统:三个简单的部分(可免费下载的操作系统教科书)。

该术语并非完全协商一致,因为Microsoft有自己的术语。但是,该书中的概念和解释适用于大多数当前的操作系统,包括Microsoft Windows。您还可以找到各种讲座和幻灯片,以略有不同的术语解释类似的概念。但我发现那本教科书写得很好,我建议阅读它。一旦你理解了这本书的大部分内容,你就可以自己回答你的问题,并发现术语的差异。


您的C++标准库实现可能正在从您的操作系统请求堆内存(也许使用某些特定于操作系统的系统调用 - 在 Linux 上,包括 mmap(2),我不知道 Windows 上有哪些系统调用可用;该信息可能不是公开的)。当然,进程的虚拟地址空间会增长多个页面。

虚拟地址空间被维基百科定义为

">操作系统提供给进程的虚拟地址范围集"

并且(如果使用该定义)它确实会发生变化(可能使用VirtualAlloc - 可能由newmalloc调用 - 以及LoadLibrary和其他函数)。顺便说一句,在x86-64上,页面大小(由硬件决定,例如MMU)通常为4KB,因此您的虚拟地址空间不可能仅增长1024字节。

(请参阅下面PS中的注释;对于Iinspectable和Microsoft,"虚拟地址空间"具有不同的定义,因此含义不同;我在维基百科定义中使用它,然后它可能会随着时间的推移而变化)

两者之间没有保证的关系(由new分配的内存和操作系统提供的虚拟地址空间)。在许多系统中,new调用malloc(来自 C 标准库),它从操作系统请求内存,但在可能的情况下更喜欢重用以前free-d 内存区域。某些C++标准库实现可能直接调用操作系统服务进行内存管理。详细信息特定于实现。

请求堆内存,并将其从/释放回操作系统内核,通常是一项昂贵的操作。因此,您的实现通常倾向于请求比所需更多的内存,并保留以前delete-d(或free-d)内存区域以供以后重用。

顺便说一句,如果你使用Linux,所有C++标准库,C++编译器,C标准库和内核都是自由软件,你可以研究它们的源代码以获得所有血腥的细节。

请注意,虚拟地址空间确实发生了变化(通过 Linux 上的 mmap(2) 和相关系统调用,具有VirtualAlloc和其他函数(如 LoadLibrary),可能还有其他几个 Windows 上的系统函数)。顺便说一句,在 Linux 上,您可以使用 proc(5)(带/proc/self/maps)以编程方式查询该虚拟地址空间。我想Windows上也有某种方法可以查询它。当某些"操作系统提供给进程的虚拟地址范围集"发生变化时,例如,当区域发生变化时(例如在Windows上成功VirtualAlloc或在Linux上成功mmap之后),VAS正在发生变化。

Iinspectable在他的评论中声称虚拟地址空间永远不会改变,但他以不同且不寻常的方式定义了虚拟地址空间。我坚持维基百科的定义,这个定义更常见,甚至在 1970 年代在 IBM 大型机上使用过。

它似乎分配了 100 KiB 到 400 KiB 之间的任何位置

您可能需要研究C++标准库实现的源代码(可能很难获得或成本高昂)来解释这一点。我的猜测是,这种可变性可能与 ASLR 有关(但这只是一个盲目的猜测,我可能是错的)。

如果您担心实际的内存消耗,则可以重新定义自己的::operator new实现(以及相关的,特别是delete)。以下一个可能符合标准的文字:

void* ::operator new  ( std::size_t count ) { throw std::bad_alloc; }

但实际上是无用的。另请参阅我提议的malloc实现。随意改进!

(两者都是笑话;只是为了表明在实践中你期望的不仅仅是标准的文字)


附言。似乎Microsoft文档(以及下面Iinspectable的评论)使用"虚拟地址空间",其定义与维基百科不同。我遵循维基百科的定义,这也是操作系统:三个简单部分(见其第13章)中使用的定义。当然,虚拟地址空间的可能跨度由ISA和处理器定义(IIRC 48位在Ryzen上,参见x86-64维基页面关于规范表单地址的图)。有些人称"分段"这些[单独的]"操作系统提供给进程的虚拟地址范围",但该术语并不通用,分段内存可能意味着不同的东西。其他人谈论虚拟内存区域是为了同样的事情(进程的虚拟地址空间中有效地址的单个连续间隔)。AMD 文档在 §2.1.5.1 AMD 锐龙中提到了虚拟内存空间 (256TB),用于实现虚拟地址空间(由操作系统提供)。其他一些文档将虚拟地址范围称为虚拟地址中有效位的数量(它受硬件的限制,例如 MMU)。关键是操作系统正在管理每个进程的虚拟地址空间,并且可以分配"区域"或"段" - 即其中的连续地址范围。请记住,进程是由操作系统内核(它们不存在于硬件中;进程是操作系统提供的抽象)及其调度程序管理的工件。虚拟地址空间也是操作系统管理的工件(某个进程的一部分或属性)。在 Linux 上, mmap(2) 被定义为 "在虚拟地址空间中创建一个新的映射" 所以改变VAS。

PPS 工作集和驻留集大小以及抖动和虚拟内存是相关的概念(但虚拟地址空间不同)。虚拟地址空间是每个进程的基本属性,但不断变化的属性(每个进程都有自己的属性)。operator newmalloc的实现可能会更改进程的 VAS(但不要每次都更改)(例如,通过在 Windows 上调用VirtualAlloc,或在 Linux 上调用mmapsbrk)。如果你坚持Microsoft的定义,虚拟地址空间意味着不同的东西,它永远不可变(但这不是很有趣,也不是过程的功能,而是硬件的功能,AMD称之为"虚拟内存空间";另见这个)。


铌。我不了解Windows,也从未使用过它(并希望在我的职业生涯中永远不要在它上编码)。但我从1974年开始编程,从1987年开始使用类Unix系统。今天我是一个Linux用户,我对GCC编译器的内部有一些了解。