41

我最近一直在研究内存分配,我对基础知识有点困惑。我一直无法围绕简单的东西。分配内存是什么意思?怎么了?我将不胜感激任何这些问题的答案:

  1. 分配的“内存”在哪里?
  2. 这个“记忆”是什么?数组中的空间?或者是其他东西?
  3. 当这个“内存”被分配时会发生什么?
  4. 当内存被释放时会发生什么?
  5. 如果有人能回答 malloc 在这些 C++ 行中的作用,那也会对我很有帮助:

    char* x; 
    x = (char*) malloc (8);
    

谢谢你。

4

4 回答 4

69

记忆模型

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),则释放函数应释放指针引用的存储,使引用释放存储的任何部分的所有指针无效.

newdelete

通常,您不需要直接使用分配和释放函数,因为它们只会给您未初始化的内存。相反,在 C++ 中,您应该使用newdelete动态分配对象。新表达式通过使用上述分配函数之一获得所请求类型的存储,然后以某种方式初始化该对象。例如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)。

C标准库:mallocfree

此外,<cstdlib>头文件还引入了stdlib.hC 标准库的内容,其中包括mallocfree函数。它们也被 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,或者如果空间已通过调用freeor被释放realloc,则行为未定义。

然而,在 C++中使用mallocand从来没有一个好的借口。free如前所述,C++ 有自己的替代方案。


问题的答案

所以直接回答你的问题:

  1. 分配的“内存”在哪里?

    C++ 标准不在乎。它只是说程序有一些由字节组成的内存。可以分配此内存。

  2. 这个“记忆”是什么?数组中的空间?或者是其他东西?

    就标准而言,内存只是一个字节序列。这是故意非常通用的,因为该标准仅尝试对典型的计算机系统进行建模。在大多数情况下,您可以将其视为计算机 RAM 的模型。

  3. 当这个“内存”被分配时会发生什么?

    分配内存使某些存储区域可供程序使用。对象在分配的内存中初始化。您只需要知道您可以分配内存。实际分配给进程的物理内存往往是由操作系统完成的。

  4. 当内存被释放时会发生什么?

    重新分配一些以前分配的内存会导致该内存对程序不可用。它成为解除分配的存储。

  5. 如果有人能回答 malloc 在这些 C++ 行中的作用,那也会对我很有帮助:

    char* x; 
    x = (char*) malloc (8);
    

    在这里,malloc只是简单地分配了 8 个字节的内存。它返回的指针被强制转换为 achar*并存储在x.

于 2013-03-24T22:03:24.057 回答
14

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 通常只为此调用一个系统函数来处理所有其余部分,并且就像这里的其他所有内容一样,它在操作系统之间、系统到系统等方面有很大不同。

于 2013-03-24T22:07:15.030 回答
9

1. 分配的“内存”在哪里?

从语言的角度来看,这没有具体说明,主要是因为细节通常并不重要。此外,该C++标准倾向于在未指定硬件细节方面犯错,以尽量减少不必要的限制(编译器可以运行的平台和可能的优化)。

sftrabbit 的回答很好地概述了这一切(这就是你真正需要的),但如果有帮助,我可以举几个例子。

示例 1:

在一台足够老的单用户计算机(或足够小的嵌入式计算机)上,大部分物理 RAM 可能对您的程序直接可用。在这种情况下,调用mallocnew本质上是内部簿记,允许运行时库跟踪该 RAM 的哪些块当前正在使用。您可以手动执行此操作,但很快就会变得乏味。

示例 2:

在现代多任务操作系统上,物理 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 值,您唯一可以说的有用的是:

  • 访问这 8 个字节中的每一个都是合法(且安全)的x[0]..x[7]
  • 访问或实际上是非法的(未定义的x[-1]行为x[8] x[i]除非0 <= i <= 7
  • 比较任何一个都是合法的x, x+1, ..., x+8(尽管你不能取消引用最后一个)
  • 如果您的平台/硬件/任何东西对您可以在内存中存储数据的位置有任何限制,那么x满足它们
于 2013-03-24T22:31:36.163 回答
4

分配内存意味着向操作系统请求内存。这意味着程序本身仅在需要时才在 RAM 中请求“空间”。例如,如果你想使用一个数组,但在程序运行之前你不知道它的大小,你可以做两件事: - 声明和 array[x],x 由你指定,任意长。例如 100。但是如果您的程序只需要一个包含 20 个元素的数组呢?你白白浪费内存。- 然后你的程序可以在知道 x 的正确大小时 malloc 一个 x 元素的数组。内存中的程序分为 4 个段: - 堆栈(调用函数所需) - 代码(二进制可执行代码) - 数据(全局变量/数据) - 堆,在此段中您可以找到分配的内存。当您决定不再需要分配的内存时,

如果要分配 10 个整数的数组,请执行以下操作:

int *array = (int *)malloc(sizeof(int) * 10)

然后你用 free(array) 把它还给操作系统

于 2013-03-24T21:59:59.103 回答