我只是想知道内存是如何工作的,语言标准(例如C ++的ISO / ANSI标准)可以保证任何数据结构(甚至数组)都是连续的。
我甚至不知道如何使用连续内存编写数据结构,但是请您举一个设计师如何做到这一点的简短示例?
例如,假设来自C ++的std :: vector在运行时分配了所有内存,它怎么知道当前分配的内存前面的内存槽没有被使用(因此,向量可以自由使用) ?向量只是看起来很遥远,并希望用户不会尝试push_back这么多的对象,它不能再将它存储在一个连续的内存块中?或者操作系统是否随意在存储器中移动以防止这成为一个问题(不知道这将如何工作)?
答案 0 :(得分:3)
标准可能要求兼容的实现以某种方式运行 - 它们只能“保证”兼容的实现以这种方式运行,因为根据定义,违反标准要求的实施不合规。
std::vector
的典型实现如何符合连续内存要求:它分配一定量的空间(“容量”),如果当前大小赶上容量,它会重新分配空间(相当于进行全新的分配,通常是当前容量的固定倍数,将当前内容复制到新空间,并释放先前获得的空间)。
答案 1 :(得分:3)
您的问题似乎是关于如何从初学者的角度理解内存分配的概念。让我试着以非常简单的方式解释发生了什么。作为一个例子,我们可以想到一个C ++程序,它为std::vector
添加了很多元素。
当程序启动时,C ++运行时将调用操作系统来分配一些内存。这段内存称为堆,它在C ++程序需要动态内存时使用。最初堆大部分未使用,但对new
和malloc
的调用将在堆上划出内存块。在内部,堆使用一些簿记信息来跟踪堆的已用和免费区域。
std::vector
内部的行为究竟取决于实现,但通常它会为堆上的向量元素分配缓冲区。这个缓冲区大到足以容纳向量中的所有元素,但大多数时候它在末尾有一些自由空间。这是一个存储5个元素的缓冲区,并且有足够的空间容纳8个元素。缓冲区位于堆上的地址1000处。
1000: X X X X X _ _ _
std::vector
跟踪向量(5)中的元素数量以及缓冲区的大小(8)和位置(1000)。
这是调用push_back
以向向量添加新元素后的缓冲区:
1000: X X X X X X _ _
可以再做两次,直到缓冲区中使用了所有空格。
1000: X X X X X X X X
但是如果再次调用push_back
会发生什么?向量必须增加缓冲区的大小。缓冲区在堆上分配,如果缓冲区之后的区域未使用,实际上可以简单地增加缓冲区。但是,大多数时候内存已经分配给其他一些对象。这是堆跟踪的东西。为了能够增长缓冲区的向量,它必须分配一个增加大小的全新缓冲区。许多实现只会使缓冲区的大小加倍。这是新的缓冲区,现在存储9个元素,并有16个元素的空间。新缓冲区在堆上的地址2000处分配:
2000: X X X X X X X X X _ _ _ _ _ _ _
旧缓冲区的内容被复制到新缓冲区,如果缓冲区很大,这个操作可能会很昂贵。
如果您想知道在程序运行时堆也可能会增长,就像在堆上分配的单个块可能会增长一样。这将增加程序的内存消耗。随着越来越多的元素被添加到向量中,堆将不得不增长,直到操作系统拒绝增加堆的大小。当发生这种情况时,程序将因内存不足而失败。
总结一下:
std::vector
将预分配缓冲区以允许向量增长,但如果向量增长超出缓冲区的大小,它将分配一个新缓冲区并将向量的全部内容复制到此新缓冲区。 答案 2 :(得分:1)
要记住的一件事是,所有现代操作系统都有一些名为Virtual Memory的东西。这允许任何程序能够假定它可以完全访问该体系结构的最大可能内存量,而不管其他进程,甚至是计算机中的物理RAM量。因此,在编译时,编译器可以自动在堆栈上分配足够的空间(如果在编译时需要大小),或者它可以编写程序集以在堆上动态分配内存,就像整个内存空间可用一样。这是一个抽象层次,使事情变得非常容易。
操作系统所做的是在各种运行程序需要时将页面交换进出物理内存。这样,没有程序需要担心其他程序,但你仍然可以同时运行程序。
我当然大大简化了实际情况,但我认为至少可以回答你关于编译器如何判断哪些内存是免费的问题;因为它可以假设所有内存都是空的。