140 lines
21 KiB
HTML
140 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh" dir="ltr" class="client-nojs" xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh">
|
|
<head><meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>内存模型</title>
|
|
<link rel="stylesheet" href="ext.css" />
|
|
<link rel="stylesheet" href="site_modules.css" />
|
|
</head>
|
|
<body class="mediawiki ltr sitedir-ltr ns-0 ns-subject page-cpp_language_memory_model skin-cppreference2 action-view cpp-navbar">
|
|
<div id="cpp-content-base">
|
|
<div id="content"><a id="top"></a>
|
|
<h1 id="firstHeading" class="firstHeading">内存模型</h1>
|
|
<div id="bodyContent">
|
|
<div id="contentSub"><span class="subpages">< <a href="cpp.html">cpp</a>‎ | <a href="cpp-language.html">language</a></span></div>
|
|
<div id="mw-content-text" lang="zh" dir="ltr" class="mw-content-ltr" xml:lang="zh">
|
|
<p>为 C++ 抽象机的目的定义了计算机内存存储的语义。</p>
|
|
<p>可为 C++ 程序所用的内存是一或多个<i>字节</i>的连续序列。内存中的每个字节拥有唯一的<i>地址</i>。</p>
|
|
<h3><span class="mw-headline" id=".E5.AD.97.E8.8A.82">字节</span></h3>
|
|
<p><i>字节(byte)</i>是最小的可寻址内存单元。它被定义为相接的位序列,其大到足以保有</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx14">
|
|
<td>
|
|
<ul>
|
|
<li>任何 <code>UTF-8</code> 编码单元(256 个相异值)和</li>
|
|
</ul>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx14">(C++14 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-until-cxx23">
|
|
<td>
|
|
<ul>
|
|
<li><a href="cpp-language-charset.html#.E5.9F.BA.E7.A1.80.E6.89.A7.E8.A1.8C.E5.AD.97.E7.AC.A6.E9.9B.86">基础执行字符集</a>的任何成员。</li>
|
|
</ul>
|
|
</td>
|
|
<td><span class="t-mark-rev t-until-cxx23">(C++23 前)</span></td>
|
|
</tr>
|
|
</table>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx23">
|
|
<td>
|
|
<ul>
|
|
<li><a href="cpp-language-charset.html#.E5.9F.BA.E7.A1.80.E5.AD.97.E9.9D.A2.E9.87.8F.E5.AD.97.E7.AC.A6.E9.9B.86">基础字面量字符集</a>的任何成员的通常字面量编码。</li>
|
|
</ul>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx23">(C++23 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<p>与 C 相似,C++ 也支持 8 位或更大的字节。</p>
|
|
<p><span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw4">char</span></span></span>、<span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw4">unsigned</span> <span class="kw4">char</span></span></span> 和 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw4">signed</span> <span class="kw4">char</span></span></span> <a href="cpp-language-types.html">类型</a>把一个字节用于存储和<a href="cpp-language-object.html">值表示</a>。字节中的位数可作为 <span class="t-lc"><a href="cpp-types-climits.html">CHAR_BIT</a></span> 或 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-types-numeric_limits.html"><span class="kw344">std::<span class="me2">numeric_limits</span></span></a><span class="sy1"><</span><span class="kw4">unsigned</span> <span class="kw4">char</span><span class="sy1">></span><span class="sy4">::</span><span class="me2">digits</span></span></span> 访问。</p>
|
|
<h3><span class="mw-headline" id=".E5.86.85.E5.AD.98.E4.BD.8D.E7.BD.AE">内存位置</span></h3>
|
|
<p>一个<i>内存位置</i>是</p>
|
|
<ul>
|
|
<li>一个<a href="cpp-language-type.html">标量类型</a>(算术类型、指针类型、枚举类型或 std::nullptr_t)对象</li>
|
|
<li>或非零长<a href="cpp-language-bit_field.html">位域</a>的最大相接序列</li>
|
|
</ul>
|
|
<p>注意:语言的各种功能特性,例如<a href="cpp-language-reference.html">引用</a>和<a href="cpp-language-virtual.html">虚函数</a>,可能涉及到程序不可访问,但为实现所管理的额外内存位置。</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> S <span class="br0">{</span>
|
|
<span class="kw4">char</span> a<span class="sy4">;</span> <span class="co1">// 内存位置 #1</span>
|
|
<span class="kw4">int</span> b <span class="sy4">:</span> <span class="nu0">5</span><span class="sy4">;</span> <span class="co1">// 内存位置 #2</span>
|
|
<span class="kw4">int</span> c <span class="sy4">:</span> <span class="nu0">11</span>, <span class="co1">// 内存位置 #2 (延续)</span>
|
|
<span class="sy4">:</span> <span class="nu0">0</span>,
|
|
d <span class="sy4">:</span> <span class="nu0">8</span><span class="sy4">;</span> <span class="co1">// 内存位置 #3</span>
|
|
<span class="kw1">struct</span> <span class="br0">{</span>
|
|
<span class="kw4">int</span> ee <span class="sy4">:</span> <span class="nu0">8</span><span class="sy4">;</span> <span class="co1">// 内存位置 #4</span>
|
|
<span class="br0">}</span> e<span class="sy4">;</span>
|
|
<span class="br0">}</span> obj<span class="sy4">;</span> <span class="co1">// 对象 'obj' 由 4 个分离的内存位置组成</span></pre></div>
|
|
</div>
|
|
<h3><span class="mw-headline" id=".E7.BA.BF.E7.A8.8B.E4.B8.8E.E6.95.B0.E6.8D.AE.E7.AB.9E.E4.BA.89">线程与数据竞争</span></h3>
|
|
<p>执行线程是程序中的控制流,它始于 <span class="t-lc"><a href="cpp-thread-thread-thread.html">std::thread::thread</a></span>、<span class="t-lc"><a href="cpp-thread-async.html">std::async</a></span> 或以其他方式所进行的顶层函数调用。</p>
|
|
<p>任何线程都能潜在地访问程序中的任何对象(拥有自动或线程局部<a href="cpp-language-storage_duration.html">存储期</a>的对象仍然可以被另一线程通过指针或引用访问)。</p>
|
|
<p>不同的执行线程始终可以同时访问(读和写)不同的<i>内存位置</i>,不需要干涉或同步的任何要求。</p>
|
|
<p>当某个表达式的<a href="cpp-language-eval_order.html">求值</a>写入某个内存位置,而另一求值读或修改同一内存位置时,称这些表达式<i>冲突</i>。拥有两个冲突的求值的程序就有<i>数据竞争</i>,除非</p>
|
|
<ul>
|
|
<li>两个求值都在同一线程上,或者在同一<a href="cpp-utility-program-signal.html#.E4.BF.A1.E5.8F.B7.E5.A4.84.E7.90.86.E5.87.BD.E6.95.B0">信号处理函数</a>中执行,或</li>
|
|
<li>两个冲突的求值都是原子操作(见 <span class="t-lc"><a href="cpp-atomic-atomic.html">std::atomic</a></span> ),或</li>
|
|
<li>一个冲突的求值<i>发生早于(happens-before)</i>另一个(见 <span class="t-lc"><a href="cpp-atomic-memory_order.html">std::memory_order</a></span>)</li>
|
|
</ul>
|
|
<p>若出现数据竞争,则程序的行为未定义。</p>
|
|
<p>(特别是,<span class="t-lc"><a href="cpp-thread-mutex.html">std::mutex</a></span> 的释放<i>同步于</i>,从而<i>发生早于</i>另一线程对同一 mutex 的获取,这使得互斥锁可以用来防止数据竞争)</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw4">int</span> cnt <span class="sy1">=</span> <span class="nu0">0</span><span class="sy4">;</span>
|
|
<span class="kw4">auto</span> f <span class="sy1">=</span> <span class="br0">[</span><span class="sy3">&</span><span class="br0">]</span><span class="br0">{</span>cnt<span class="sy2">++</span><span class="sy4">;</span><span class="br0">}</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2146">std::<span class="me2">thread</span></span></a> t1<span class="br0">{</span>f<span class="br0">}</span>, t2<span class="br0">{</span>f<span class="br0">}</span>, t3<span class="br0">{</span>f<span class="br0">}</span><span class="sy4">;</span> <span class="co1">// 未定义行为</span></pre></div>
|
|
</div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><a href="cpp-atomic-atomic.html"><span class="kw2066">std::<span class="me2">atomic</span></span></a><span class="sy1"><</span><span class="kw4">int</span><span class="sy1">></span> cnt<span class="br0">{</span><span class="nu0">0</span><span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw4">auto</span> f <span class="sy1">=</span> <span class="br0">[</span><span class="sy3">&</span><span class="br0">]</span><span class="br0">{</span>cnt<span class="sy2">++</span><span class="sy4">;</span><span class="br0">}</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2146">std::<span class="me2">thread</span></span></a> t1<span class="br0">{</span>f<span class="br0">}</span>, t2<span class="br0">{</span>f<span class="br0">}</span>, t3<span class="br0">{</span>f<span class="br0">}</span><span class="sy4">;</span> <span class="co1">// OK</span></pre></div>
|
|
</div>
|
|
<h3><span class="mw-headline" id=".E5.86.85.E5.AD.98.E9.A1.BA.E5.BA.8F">内存顺序</span></h3>
|
|
<p>当线程从某个内存位置读取值时,它可能看到初值,同一线程所写入的值,或另一线程所写入的值。有关线程所作的写入操作对其他线程变为可见的顺序上的细节,见 <span class="t-lc"><a href="cpp-atomic-memory_order.html">std::memory_order</a></span>。</p>
|
|
<h3><span class="mw-headline" id=".E5.90.91.E5.89.8D.E8.BF.9B.E5.B1.95">向前进展</span></h3>
|
|
<h4><span class="mw-headline" id=".E5.85.8D.E5.A6.A8.E7.A2.8D">免妨碍</span></h4>
|
|
<p>当只有一个未在标准库函数中阻塞的线程执行某个免锁的<a href="cpp-atomic.html">原子函数</a>时,保证该执行将会完成(所有标准库免锁操作均为<a href="https://en.wikipedia.org/wiki/Non-blocking_algorithm#Obstruction-freedom" class="extiw" title="enwiki:Non-blocking algorithm">免妨碍</a>的)。</p>
|
|
<h4><span class="mw-headline" id=".E5.85.8D.E9.94.81">免锁</span></h4>
|
|
<p>当一或多个免锁原子函数同时运行时,保证其中至少一个将会完成(所有标准库免锁操作均为<a href="https://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom" class="extiw" title="enwiki:Non-blocking algorithm">免锁</a>的——确保其他线程不能不确定地活锁它们(例如以连续窃取缓存线的方式),是实现的工作)。</p>
|
|
<h4><span class="mw-headline" id=".E8.BF.9B.E5.B1.95.E4.BF.9D.E8.AF.81">进展保证</span></h4>
|
|
<p>合法的 C++ 程序中,每个线程最终要做下列之一:</p>
|
|
<ul>
|
|
<li>终止</li>
|
|
<li>调用 I/O 库函数</li>
|
|
<li>通过 <a href="cpp-language-cv.html">volatile</a> 泛左值进行访问</li>
|
|
<li>进行原子操作或同步操作</li>
|
|
</ul>
|
|
<p>没有线程能在不做任何这些可观察行为的情况下永远执行。</p>
|
|
<p>注意,这意味着包含无限递归或无限循环(无论是实现为 <a href="cpp-language-for.html">for 语句</a> 或是用 <a href="cpp-language-goto.html">goto</a> 循环还是其他方式)的程序具有<a href="cpp-language-ub.html">未定义行为</a>。这允许编译器移除所有无可观察行为的循环,而不必证明他们终将终止。</p>
|
|
<p>若线程执行了上述步骤之一(I/O、volatile、原子或同步操作),阻塞于标准库函数中,或调用由于某个未阻塞的并发线程而未能完成的原子免锁函数,则称它<i>取得进展(make progress)</i>。</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx17">
|
|
<td>
|
|
<h4><span class="mw-headline" id=".E5.B9.B6.E5.8F.91.E5.90.91.E5.89.8D.E8.BF.9B.E5.B1.95">并发向前进展</span></h4>
|
|
<p>若线程提供<i>并发向前进展保证(concurrent forward progress guarantee)</i>,则只要它尚未终止,就将在有限量的时间内<i>取得进展</i>(定义如上),无关乎其他线程(若存在)是否取得进展。</p>
|
|
<p>标准鼓励但不要求主线程和 <span class="t-lc"><a href="cpp-thread-thread.html">std::thread</a></span> 所启动的线程提供并发向前进展保证。</p>
|
|
<h4><span class="mw-headline" id=".E5.B9.B6.E8.A1.8C.E5.90.91.E5.89.8D.E8.BF.9B.E5.B1.95">并行向前进展</span></h4>
|
|
<p>若线程提供<i>并行向前进展保证(parallel forward progress guarantee)</i>,则若线程尚未执行任何执行步骤(I/O、volatile、原子或同步操作),就不要求实现保证该线程终将取得进展,但一旦此线程开始执行步骤,则它提供<i>并发向前进展</i>保证(此规则描述线程池中以任意顺序执行任务的线程)。</p>
|
|
<h4><span class="mw-headline" id=".E5.BC.B1.E5.B9.B6.E8.A1.8C.E5.90.91.E5.89.8D.E8.BF.9B.E5.B1.95">弱并行向前进展</span></h4>
|
|
<p>若线程提供<i>弱并行向前进展保证(weakly parallel forward progress guarantee)</i>,则不保证它终将取得进展,无关乎其他线程是否取得进展。</p>
|
|
<p><br />
|
|
此类线程仍然能通过以向前进展保证委托进行阻塞来保证取得进展:若线程 P 以此方式阻塞于线程集合 S 的完成,则 S 中至少有一个线程将提供等于或强于 P 的向前进展保证。一旦该线程完成,则类似地强化 S 中的另一线程。一旦该集合为空,则解除 P 的阻塞。</p>
|
|
<p>来自 C++ 标准库的<a href="cpp-algorithm.html">并行算法</a>,均以向前保证委托阻塞于某个标准库所管理的线程的未指明集合的完成上。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx17">(C++17 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<h3><span class="mw-headline" id=".E5.8F.82.E9.98.85">参阅</span></h3>
|
|
<table class="t-dsc-begin"></table>
|
|
</div>
|
|
<div class="visualClear"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|