我最近一直在研究内存分配,我对基础知识有点困惑。我一直无法围绕简单的东西。分配内存是什么意思?怎么了?我将不胜感激任何这些问题的答案:
- 分配的“内存”在哪里?
- 这个“记忆”是什么?数组中的空间?或者是其他东西?
- 当这个“内存”被分配时会发生什么?
- 当内存被释放时会发生什么?
如果有人能回答 malloc 在这些 C++ 行中的作用,那也会对我很有帮助:
char* x; x = (char*) malloc (8);
谢谢你。
我最近一直在研究内存分配,我对基础知识有点困惑。我一直无法围绕简单的东西。分配内存是什么意思?怎么了?我将不胜感激任何这些问题的答案:
如果有人能回答 malloc 在这些 C++ 行中的作用,那也会对我很有帮助:
char* x;
x = (char*) malloc (8);
谢谢你。
C++ 标准有一个内存模型。它尝试以通用方式对计算机系统中的内存进行建模。该标准定义字节是内存模型中的存储单元,并且内存由字节组成(第 1.7 节):
C++ 内存模型中的基本存储单元是字节。[...] C++ 程序可用的内存由一个或多个连续字节序列组成。
该标准始终提供对象模型。这指定了一个对象是一个存储区域(因此它由字节组成并驻留在内存中)(§1.8):
C++ 程序中的构造创建、销毁、引用、访问和操作对象。对象是一个存储区域。
所以我们开始了。内存是存储对象的地方。要将对象存储在内存中,必须分配所需的存储区域。
该标准提供了两个隐式声明的全局范围分配函数:
void* operator new(std::size_t);
void* operator new[](std::size_t);
这些如何实施不是标准的关注点。重要的是它们应该返回一个指向某个存储区域的指针,其字节数对应于传递的参数(第 3.7.4.1 节):
分配函数尝试分配请求的存储量。如果成功,它将返回存储块的开始地址,其字节长度应至少与请求的大小一样大。从分配函数返回时分配的存储内容没有限制。
它还定义了两个相应的释放函数:
void operator delete(void*);
void operator delete[](void*);
定义为释放先前分配的存储(§3.7.4.2):
如果给标准库中的释放函数的参数是一个不是空指针值的指针(4.10),则释放函数应释放指针引用的存储,使引用释放存储的任何部分的所有指针无效.
new
和delete
通常,您不需要直接使用分配和释放函数,因为它们只会给您未初始化的内存。相反,在 C++ 中,您应该使用new
并delete
动态分配对象。新表达式通过使用上述分配函数之一获得所请求类型的存储,然后以某种方式初始化该对象。例如new int()
,将为一个int
对象分配空间,然后将其初始化为 0。参见 §5.3.4:
new-expression 通过调用分配函数 (3.7.4.1) 来获取对象的存储空间。
[...]
创建 T 类型对象的新表达式初始化该对象 [ ... ]
在相反的方向,delete
将调用对象的析构函数(如果有),然后释放存储空间(§5.3.5):
如果delete-expression的操作数的值不是空指针值,则delete-expression将为要删除的对象或数组的元素调用析构函数(如果有)。
[...]
如果delete-expression的操作数的值不是空指针值,则delete-expression将调用释放函数 (3.7.4.2)。
但是,这些并不是分配或取消分配存储的唯一方式。该语言的许多结构都隐含地需要分配存储空间。例如,给出一个对象定义,如int a;
,也需要存储(§7):
定义导致保留适当的存储量并完成任何适当的初始化(8.5)。
malloc
和free
此外,<cstdlib>
头文件还引入了stdlib.h
C 标准库的内容,其中包括malloc
和free
函数。它们也被 C 标准定义为分配和释放内存,很像 C++ 标准定义的分配和释放函数。以下是malloc
(C99 §7.20.3.3) 的定义:
void *malloc(size_t size);
说明
该malloc
函数为大小由 指定size
且值不确定的对象分配空间。
返回
该malloc
函数返回一个空指针或一个指向已分配空间的指针。
free
以及(C99 §7.20.3.2)的定义:
void free(void *ptr);
说明
该free
函数使 所指向的空间ptr
被释放,即可供进一步分配。如果ptr
是空指针,则不执行任何操作。calloc
否则,如果参数与先前由、malloc
或函数返回的指针不匹配realloc
,或者如果空间已通过调用free
or被释放realloc
,则行为未定义。
然而,在 C++中使用malloc
and从来没有一个好的借口。free
如前所述,C++ 有自己的替代方案。
所以直接回答你的问题:
分配的“内存”在哪里?
C++ 标准不在乎。它只是说程序有一些由字节组成的内存。可以分配此内存。
这个“记忆”是什么?数组中的空间?或者是其他东西?
就标准而言,内存只是一个字节序列。这是故意非常通用的,因为该标准仅尝试对典型的计算机系统进行建模。在大多数情况下,您可以将其视为计算机 RAM 的模型。
当这个“内存”被分配时会发生什么?
分配内存使某些存储区域可供程序使用。对象在分配的内存中初始化。您只需要知道您可以分配内存。实际分配给进程的物理内存往往是由操作系统完成的。
当内存被释放时会发生什么?
重新分配一些以前分配的内存会导致该内存对程序不可用。它成为解除分配的存储。
如果有人能回答 malloc 在这些 C++ 行中的作用,那也会对我很有帮助:
char* x;
x = (char*) malloc (8);
在这里,malloc
只是简单地分配了 8 个字节的内存。它返回的指针被强制转换为 achar*
并存储在x
.
1)正在分配的“内存”在哪里?
根据您的操作系统、编程环境(gcc、Visual C++、Borland C++ 和其他任何东西)、计算机、可用内存等,这是完全不同的。一般来说,内存是从所谓的堆分配的,内存区域只是在等待周围供您使用。它通常会使用您可用的 RAM。但总有例外。在大多数情况下,只要它给我们记忆,它来自哪里并不是一个大问题。有一些特殊类型的内存,例如虚拟内存,它们可能在任何给定时间实际上都在 RAM 中,也可能不在 RAM 中,如果您的实际内存用完,它们可能会转移到您的硬盘驱动器(或类似的存储设备)中。完整的解释会很长!
2)这个“记忆”是什么?数组中的空间?或者是其他东西?
内存通常是计算机中的 RAM。如果将内存视为一个巨大的“数组”会有所帮助,那么它肯定会像一个数组一样运行,然后将其视为大量字节(8 位值,很像unsigned char
值)。它从内存底部的索引 0 开始。不过,就像以前一样,这里有很多例外,内存的某些部分可能会映射到硬件,甚至可能根本不存在!
3)当这个“内存”被分配时会发生什么?
在任何给定时间,都应该(我们真的希望!)其中一些可供软件分配。它的分配方式高度依赖于系统。通常,分配一个内存区域,分配器将其标记为已使用,然后给您一个指针以供您使用,该指针告诉程序该内存位于系统的所有内存中的哪个位置。在您的示例中,程序将找到一个连续的 8 字节 (char) 块,并在将其标记为“正在使用”之后返回一个指向该块所在位置的指针。
4)当内存被释放时究竟会发生什么?
系统将该内存标记为可再次使用。这非常复杂,因为这通常会导致内存出现漏洞。分配 8 个字节,然后再分配 8 个字节,然后释放前 8 个字节,你就有了一个漏洞。有关于处理释放、内存分配等的整本书。所以希望简短的回答就足够了!
5) 如果有人能回答 malloc 在这些 C++ 行中的作用,它也会对我很有帮助:
真的很粗略,假设它在一个函数中(顺便说一句,永远不要这样做,因为它不会释放你的内存并导致内存泄漏):
void mysample() {
char *x; // 1
x = (char *) malloc(8); // 2
}
1) 这是一个保留在本地堆栈空间中的指针。它还没有被初始化,所以它指向其中的任何内存。
2) 它使用参数 8 调用 malloc。转换只是让 C/C++ 知道您打算将其设为 (char *),因为它返回 (void *) 意味着它没有应用类型。然后将结果指针存储在您的 x 变量中。
在非常粗糙的 x86 32 位汇编中,这看起来有点像
PROC mysample:
; char *x;
x = DWord Ptr [ebp - 4]
enter 4, 0 ; Enter and preserve 4 bytes for use with
; x = (char *) malloc(8);
push 8 ; We're using 8 for Malloc
call malloc ; Call malloc to do it's thing
sub esp, 4 ; Correct the stack
mov x, eax ; Store the return value, which is in EAX, into x
leave
ret
实际分配在第 3 点中进行了模糊描述。Malloc 通常只为此调用一个系统函数来处理所有其余部分,并且就像这里的其他所有内容一样,它在操作系统之间、系统到系统等方面有很大不同。
1. 分配的“内存”在哪里?
从语言的角度来看,这没有具体说明,主要是因为细节通常并不重要。此外,该C++
标准倾向于在未指定硬件细节方面犯错,以尽量减少不必要的限制(编译器可以运行的平台和可能的优化)。
sftrabbit 的回答很好地概述了这一切(这就是你真正需要的),但如果有帮助,我可以举几个例子。
在一台足够老的单用户计算机(或足够小的嵌入式计算机)上,大部分物理 RAM 可能对您的程序直接可用。在这种情况下,调用malloc
或new
本质上是内部簿记,允许运行时库跟踪该 RAM 的哪些块当前正在使用。您可以手动执行此操作,但很快就会变得乏味。
在现代多任务操作系统上,物理 RAM 与许多进程和其他任务(包括内核线程)共享。它还用于后台的磁盘缓存和 I/O 缓冲,并由虚拟内存子系统增强,该子系统可以在不使用数据时将数据交换到磁盘(或其他一些存储设备)。
在这种情况下,调用new
可能首先检查您的进程内部是否已经有足够的可用空间,如果没有,则向操作系统请求更多空间。返回的内存可能是物理的,也可能是虚拟的(在这种情况下,在实际访问之前可能不会分配物理 RAM 来存储它)。你甚至无法区分,至少在不使用特定于平台的 API 的情况下是这样,因为内存硬件和内核会合力将其隐藏起来。
2. 这个“记忆”是什么?数组中的空间?或者是其他东西?
在示例 1 中,它类似于数组中的空间:返回的地址标识物理 RAM 的可寻址块。即使在这里,RAM 地址也不一定是平坦的或连续的——一些地址可能是为 ROM 或 I/O 端口保留的。
在示例 2 中,它是对更虚拟的事物的索引:您的进程的地址空间。这是一种抽象,用于向您的进程隐藏底层虚拟内存详细信息。当你访问这个地址时,内存硬件可能会直接访问一些真实的 RAM,或者它可能需要请求虚拟内存子系统提供一些。
3. 当这个“内存”被分配时会发生什么?
通常,会返回一个指针,您可以使用它来存储所需的字节数。在这两种情况下,malloc
或者new
操作员都会做一些内务处理来跟踪进程地址空间的哪些部分被使用,哪些部分是免费的。
4. 当内存被释放时会发生什么?
一般来说,free
或者delete
会做一些内务处理,以便他们知道内存可以重新分配。
如果有人能回答 malloc 在这些 C++ 行中的作用,那也会对我很有帮助:
char* x;
x = (char*) malloc (8);
它返回一个指针,要么是NULL
(如果它找不到你想要的 8 个字节),要么是一些非 NULL 值。
关于这个非 NULL 值,您唯一可以说的有用的是:
x[0]..x[7]
,x[-1]
行为x[8]
), x[i]
除非0 <= i <= 7
x, x+1, ..., x+8
(尽管你不能取消引用最后一个)x
满足它们分配内存意味着向操作系统请求内存。这意味着程序本身仅在需要时才在 RAM 中请求“空间”。例如,如果你想使用一个数组,但在程序运行之前你不知道它的大小,你可以做两件事: - 声明和 array[x],x 由你指定,任意长。例如 100。但是如果您的程序只需要一个包含 20 个元素的数组呢?你白白浪费内存。- 然后你的程序可以在知道 x 的正确大小时 malloc 一个 x 元素的数组。内存中的程序分为 4 个段: - 堆栈(调用函数所需) - 代码(二进制可执行代码) - 数据(全局变量/数据) - 堆,在此段中您可以找到分配的内存。当您决定不再需要分配的内存时,
如果要分配 10 个整数的数组,请执行以下操作:
int *array = (int *)malloc(sizeof(int) * 10)
然后你用 free(array) 把它还给操作系统