C++03内存模型是什么?与并发有关。

36

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

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


你应该重新写问题,让它更清晰明了。 - 1800 INFORMATION
1
我同意 - 这个问题很重要,但措辞非常糟糕。我会尽力改进。 (Reputation Game Rant: 我觉得这个问题被标记为社区维基很讽刺 - 这意味着无论答案多么深入、技术性或好,都不会获得声望) - Suma
我认为社区维基系统很好。问题在于提问者是新手,由于社区维基的实际功能对新手来说不太清楚,许多人之所以将其设为维基,只是因为听起来很酷。当我刚开始在这里时,我也做了同样的事情 :) - Robert Gould
7个回答

33

C++内存模型规定了物理内存何时以及为什么会被读写。

在下一个C++标准之前,C++的内存模型与C相同。在C++0x标准中,预计将包括适用于多线程的正确的内存模型(请参见这里),并且它可能是下一版C标准C1X的一部分。当前标准非常简陋:

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

所以当前的状态是:只有在您有一个进程及其主线程并且不编写依赖于变量读/写的特定顺序的代码时,才指定C++内存操作。实质上,这意味着除了传统的hello world程序之外,您的程序可能会遇到困难。

当然,您可能会很快地添加"在我的机器上运行良好,您不可能是对的"。正确的句子应该是"使用特定的硬件组合、操作系统(线程库)和编译器,在我的机器上运行良好,并且彼此了解足以实现某些工作,但很可能会在某个时候出现问题"

好吧,这有点苛刻,但是看看Herb Sutter所承认的(只需阅读介绍部分),他谈论的是最广泛使用的C/C++工具链的所有2007年之前的版本...

C++标准委员会试图设计出一种能够解决这些问题的方案,同时比Java的内存模型更加灵活(从而性能更好)。

Hans Boehm在这里收集了一些与此问题相关的论文和来自C++委员会的资料。


1
我认为你在这里有点苛刻了。C++/C是大多数实时、关键任务、多线程嵌入式系统的首选语言。虽然C++标准没有MT支持,但没有一个C++编译器供应商会推出一款无法在MT环境中使用的工具。 - Roddy
你说得对,我重新阅读了自己的话,感觉有些严厉。不过如果你还没有看过这篇文章,我建议你去看一下: http://www.open-std.org/JTC1/sc22/wg21/docs/papers/2007/n2197.pdf 所有的 C/C++ 工具链都受到了这个问题的影响。 - bltxd
双重检查模式在 C 语言中是完全正常的。不幸的是,理论与实践不符,因为真实的处理器会重新排序。 - MSalters
3
抱歉,像“根据C语言标准,双重检查模式是可行的”这样的断言是荒谬的,正如文章中所述的原因。随着内存模型的发展,我认为默认情况下它也不应该被视为可行,但应该有一种方法可以使编译器产生所需的屏障(通过所谓的“低级原子操作”,如果我没记错的话)。 - Blaisorblade
1
没错:“C++0x”和C1x将是第一个关于使用线程时会发生什么的C++和C标准。如果没有这个,真的会发生很糟糕的事情:请参阅博客文章http://www.thegibson.org/blog/archives/23“内存排序和内存模型”(以及链接的LKML和GCC列表线程),了解当没有规则告诉编译器实现者他们可以做什么和不能做什么时会发生什么。所以,当然,现在许多事情似乎都能正常工作,但这主要是运气... - SamB
显示剩余2条评论

23

看了一些其他回答,似乎许多C++程序员甚至不知道你所问的"内存模型"是什么意思。

这个问题涉及内存模型的意义:关于写入/读取重排序(可能发生在编译器或运行时),有什么保证(如果有)?此问题对于多线程编程非常重要,因为没有这样的规则,编写正确的多线程程序是不可能的,而令人惊讶的事实是,在当前缺乏显式内存模型的情况下,许多多线程程序基本上都是通过“纯属运气”工作的-往往要感谢编译器假定函数调用间的指针别名。 -请参见Threads Cannot be Implemented as a Library

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

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


2

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

因此,C++内存模型等同于混合杂糅的模型,这意味着您始终需要尝试编写安全代码,不依赖于特定的内存模型。对于线程编程也是如此,因为编译器可以在关键部分之外进行任何优化,甚至乱序处理!


那是不正确的。除了有一个工作堆栈和一个堆之外,没有任何“假定”的内存模型。该模型不是混乱的混合体,而只是硬件上存在的任何内容。即,哈佛或冯·诺依曼,没有堆,堆。它是基于硬件的,这表示它具有一定的顺序。 - Paul Nathan
1
@Vlion:你弄错了。内存模型是描述何时以及为什么物理内存被读取/写入的说明。硬件在这里基本上无关紧要,除非所指定的内容可以在当前硬件上实现! - bltxd
感谢blue.tuxedo在那里帮我澄清;对于那些不熟悉这个术语的人,我的回答应该更具体。 - Robert Gould

2

1

-1

简短回答:没有

长篇回答:C++没有托管内存,您必须自己分配和释放内存。智能指针类可以使这个过程更轻松。如果您忘记释放已分配的内存,那就是内存泄漏和错误。如果您尝试在释放内存后使用内存,或者尝试多次释放内存,那也是非常严重的错误。

至于底层细节,C++没有规定 - 这取决于硬件。内存通过指针访问,其中包含某种内存地址。内存地址可以是物理地址或虚拟地址。只有在操作系统内核上工作或读取在实模式下运行的旧DOS代码时,您才会看到物理地址。有关更多详细信息,请阅读虚拟内存,有很多好的资源可供参考。

x86架构还允许使用段描述符来寻址内存。这是一个完全不同的问题,自Win16以来就没有真正被使用过了,如果您幸运的话,您永远不需要处理它。


-8
简而言之,C++内存模型包括...
  • 一个向下增长的堆栈--也就是说,当你推入一个堆栈帧时,堆栈指针的值比它之前的值小

  • 一个向上增长的堆,也就是新分配的内存的结束地址比之前的内存大。你可以使用malloc()或new在堆中分配内存。如果堆中没有足够的内存可用,则malloc(或new)调用系统函数brk() sbrk()来增加堆的大小。如果调用brk()或sbrk()失败,则malloc或new会因为内存不足而失败。

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

  • 一个内存分配器malloc,它按8位字节分配内存。New也分配内存,但它分配的内存量基于被newed对象的大小。

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

一个程序可能有其他特殊用途的文本部分。

您可以使用Linux系统上的objdump查看程序在静态(加载之前)时的组织方式。

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

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


栈增长的方向取决于硬件架构,而不是任何C++内存模型。 - Ferruccio
你是正确的。可能我没有写清楚,但那就是我说的话。 - mxg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接