36

C++03中并发的内存模型是什么?

(而且,C++11 是否更改了内存模型以更好地支持并发?)

4

7 回答 7

33

C++ 内存模型是针对 C++ 代码读取/写入物理内存的时间和原因的规范。

在下一个 C++ 标准之前,C++ 内存模型与 C 相同。在 C++0x 标准中,预计将包含一个适合多线程的内存模型(参见此处),并且可能会成为下一个修订版的一部分C标准,C1X。当前的一个是基本的:

  • 它仅指定当前程序可观察到的内存操作的行为。
  • 当多个进程访问同一内存时(没有共享内存或进程的概念),它没有说明并发内存访问。
  • 当多个线程访问同一内存时(没有线程的概念),它没有说明并发内存访问。
  • 它无法指定内存访问的顺序(编译器优化包括代码运动和最近的处理器重新排序访问,两者都可能破坏诸如双重检查初始化之类的模式)。

因此,当前状态是:仅当您有 1 个进程、其主线程且不编写取决于变量读/写的特定顺序的代码时才指定 C++ 内存操作,仅此而已。从本质上讲,这意味着除了传统的 hello world 程序之外,您还搞砸了。

当然,您会被提示添加“它今天在我的机器上运行,您不可能是对的”。正确的句子是“它今天可以在我的机器上运行,它具有硬件、操作系统(线程库)和编译器的这种特定组合,这些硬件、操作系统(线程库)和编译器彼此了解足够多,可以实现一些有点工作但可能会在某个时候中断的东西”

好吧好吧,这有点苛刻,但地狱,甚至 Herb Sutter 也承认(只需阅读介绍),他正在谈论最普遍的 C/C++ 工具链之一的所有 2007 年之前的版本......

C++ 标准委员会试图提出一些可以解决所有这些问题的方法,同时仍然比 Java 的内存模型限制更少(因此性能更好)。

Hans Boehm 在这里收集了一些关于这个问题的论文,包括学术论文和 C++ 委员会的论文。

于 2008-10-21T07:20:28.120 回答
23

看到其他一些答案,似乎许多 C++ 程序员甚至都不知道您所询问的“内存模型”是什么意思。

从某种意义上说,问题是关于内存模型的:关于写入/读取重新排序(可能发生在编译器端或运行时端)有什么保证(如果有的话)?这个问题对于多线程编程非常重要,因为如果没有这样的规则,编写正确的多线程程序是不可能的,而且有些令人惊讶的事实是,由于当前缺乏显式内存模型,许多多线程程序或多或少地“靠运气”工作——通常要归功于编译器在函数调用之间假设指针别名。- 见线程不能作为库实现

在当前的 C++ 中,没有标准的内存模型。一些编译器为 volatile 变量定义内存模型,但这是非标准的。C++0x 为此目的定义了新的“原子”原语。检查最近状态的详尽起点可以在C++ 的线程和内存模型中找到

重要的链接还有并发内存模型原子类型C++ 数据依赖排序:原子和内存模型标准提案。

于 2008-10-21T07:27:03.800 回答
2

不幸的是,在 C++ 中没有像 Java 那样的“标准内存模型”。实际的实现由编译器、运行时库和处理器决定。

因此,C++ 内存模型 == 模型的混沌混搭,这意味着您总是必须尝试编写不依赖于特定内存模型的安全代码,这也适用于线程编程,因为编译器可以做到它想要在关键部分之外进行的任何优化,甚至是乱序处理!

于 2008-10-21T04:54:04.080 回答
2

在 C++ 标准委员会网站上查看论文怎么样:

?

于 2008-10-21T07:23:33.043 回答
1

如果您想更深入地了解共享内存一致性模型,我建议您参考以下教程。

http://rsim.cs.uiuc.edu/~sadve/Publications/computer96.pdf

于 2010-05-04T01:40:27.080 回答
-1

简短的回答:没有

长答案:C++ 没有托管内存,您必须自己分配和释放它。智能指针类可以减轻这种负担。如果您忘记释放分配的内存,那就是内存泄漏和错误。如果您在释放内存后尝试使用内存,或者您尝试多次释放内存,这些也是令人讨厌的错误。

至于低级细节,C++ 没有具体说明——这取决于硬件。内存是通过指针访问的,其中包含某种内存地址。内存地址可以是物理地址或虚拟地址。如果您正在使用操作系统内核,或者您正在阅读以实模式运行的旧 DOS 代码,您只会看到物理地址。有关更多详细信息,请阅读虚拟内存,那里有很多好的资源。

x86 架构还允许使用段描述符来寻址内存。这是一大堆蠕虫,自 Win16 时代以来就没有真正使用过,如果幸运的话,你永远不必处理它。

于 2008-10-21T05:21:51.387 回答
-8

简而言之,C++ 内存模型包括...

  • 向下增长的堆栈——也就是说,当您压入堆栈帧时,堆栈指针的值小于原来的值

  • 向上增长的堆,即新分配的内存的结束地址比之前的内存大。您使用 malloc() 或 new 在堆中分配内存。如果堆中没有足够的可用内存,则 malloc(或 new)调用系统函数 brk() sbrk() 以增加堆的大小。如果对 brk() 或 sbrk() 的调用失败,则 malloc 或 new 失败并出现内存不足异常。

您永远不需要关心堆栈或堆是向下还是向上增长,并且在某些系统中,它们可能以相反的方式运行。只需考虑堆栈和堆从地址空间的末端向内增长。

  • 内存分配器 malloc,它以 8 位字节分配内存。New 也分配内存,但它分配的内存量取决于正在更新的对象的大小。

  • 包含可执行代码的文本空间。文本位于堆下方。您不能在执行期间更改文本空间

程序可能在文本下方有其他特殊用途的部分。

您可以在 linux 系统上使用 objdump 查看程序是如何静态组织的(在加载之前)。

我注意到虽然您在问题中没有提到它,但“并发”是您分配给这个问题的关键字之一。线程系统在堆上为每个线程分配额外的线程空间,然后管理堆栈指针以在线程之间切换。

还有更多细节,其中许多是特定于特定硬件、操作系统或线程系统的,但这是基本思想。

于 2008-10-21T05:38:23.520 回答