217 lines
26 KiB
HTML
217 lines
26 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_ub 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>若违反某些规则,则令整个程序失去意义。</p>
|
|
<h3><span class="mw-headline" id=".E8.A7.A3.E9.87.8A">解释</span></h3>
|
|
<p>C++ 标准为不被归入下列分类之一的每个程序都精确定义了其<a href="cpp-language-as_if.html">可观察行为</a>:</p>
|
|
<ul>
|
|
<li><i>非良构(ill-formed)</i>——程序拥有语法错误或可诊断的语义错误。遵从标准的 C++ 编译器必须为此给出诊断,即使它定义了为这种代码赋予了含义的语言扩展(例如用非常量长度数组)也应如此。标准文本用 <i>shall(应当)</i>、 <i>shall not(不应当)</i>和 <i>ill-formed(非良构)</i>给出了这些要求。</li>
|
|
<li><i>非良构而不要求诊断(ill-formed, no diagnostic required)</i>——程序拥有通常情况下可能无法诊断的语义错误(例如 <a href="cpp-language-definition.html">ODR</a> 的违规或者其他只能在链接时检测的错误)。若执行这种程序则行为未定义。</li>
|
|
<li><i>由实现定义的行为(implementation-defined behavior)</i>——程序的行为随实现而变动,遵从标准的实现必须为每个这样的行为的效果提供文档。例如 <span class="t-lc"><a href="cpp-types-size_t.html">std::size_t</a></span> 的类型或每个字节的位数,或者 <span class="t-lc"><a href="cpp-memory-new-bad_alloc.html">std::bad_alloc::what</a></span> 的文本。由实现定义的行为的一个子集是<i>本地环境特定行为(locale-specific behavior)</i>,它取决于实现所提供的<a href="cpp-locale.html">本地环境</a>。</li>
|
|
<li><i>未指明的行为( unspecified behavior )</i>——程序的行为随实现而变动,且遵从标准的实现不需要为每个行为的效果提供文档。例如<a href="cpp-language-eval_order.html">求值顺序</a>,等同的<a href="cpp-language-string_literal.html">字符串字面量</a>是否为不同的对象,数组分配的开销,等等。每个未指明行为均产生合法结果集合中的一个结果。</li>
|
|
<li><i>未定义行为(undefined behavior,UB)</i>——对程序的行为无任何限制。未定义行为的例子是数组边界外的内存访问,有符号整数溢出,空指针的解引用,在一个表达式中对同一标量<a href="cpp-language-eval_order.html">多于一次</a>的<span class="t-rev-inl t-until-cxx11"><span>中间无序列点</span> <span><span class="t-mark-rev t-until-cxx11">(C++11 前)</span></span></span><span class="t-rev-inl t-since-cxx11"><span>无序</span> <span><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></span></span>的修改,通过<a href="cpp-language-reinterpret_cast.html#.E7.B1.BB.E5.9E.8B.E5.88.AB.E5.90.8D.E5.8C.96">不同类型的指针</a>访问对象,等等。编译器不需要诊断未定义行为(尽管许多简单情形确实会得到诊断),而且所编译的程序不需要做任何有意义的事。</li>
|
|
</ul>
|
|
<h3><span class="mw-headline" id="UB_.E4.B8.8E.E4.BC.98.E5.8C.96">UB 与优化</span></h3>
|
|
<p>因为正确的 C++ 程序不含未定义行为,所以在启用优化选项时编译确实含有 UB 的程序时,编译器可能产生意料之外的结果:</p>
|
|
<p>例如,</p>
|
|
<h4><span class="mw-headline" id=".E6.9C.89.E7.AC.A6.E5.8F.B7.E6.BA.A2.E5.87.BA">有符号溢出</span></h4>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw4">int</span> foo<span class="br0">(</span><span class="kw4">int</span> x<span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">return</span> x<span class="sy2">+</span><span class="nu0">1</span> <span class="sy1">></span> x<span class="sy4">;</span> <span class="co1">// 要么为 true,要么因为有符号溢出导致 UB</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能编译为(<a rel="nofollow" class="external text" href="https://godbolt.org/z/y4vIi1">演示</a>)</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">foo<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="sy4">:</span>
|
|
movl $<span class="nu19">1</span>, <span class="sy2">%</span>eax
|
|
ret</pre></div>
|
|
</div>
|
|
<p><br /></p>
|
|
<h4><span class="mw-headline" id=".E8.BE.B9.E7.95.8C.E5.A4.96.E8.AE.BF.E9.97.AE">边界外访问</span></h4>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw4">int</span> table<span class="br0">[</span><span class="nu0">4</span><span class="br0">]</span> <span class="sy1">=</span> <span class="br0">{</span><span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw4">bool</span> exists_in_table<span class="br0">(</span><span class="kw4">int</span> v<span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<span class="co1">// 可能在头 4 次迭代中返回 true,或者因为边界外访问导致 UB</span>
|
|
<span class="kw1">for</span> <span class="br0">(</span><span class="kw4">int</span> i <span class="sy1">=</span> <span class="nu0">0</span><span class="sy4">;</span> i <span class="sy1"><=</span> <span class="nu0">4</span><span class="sy4">;</span> i<span class="sy2">++</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>table<span class="br0">[</span>i<span class="br0">]</span> <span class="sy1">==</span> v<span class="br0">)</span> <span class="kw1">return</span> <span class="kw2">true</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="kw1">return</span> <span class="kw2">false</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能编译为(<a rel="nofollow" class="external text" href="https://godbolt.org/z/e2bC84">演示</a>)</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">exists_in_table<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="sy4">:</span>
|
|
movl $<span class="nu19">1</span>, <span class="sy2">%</span>eax
|
|
ret</pre></div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E6.9C.AA.E5.88.9D.E5.A7.8B.E5.8C.96.E7.9A.84.E6.A0.87.E9.87.8F">未初始化的标量</span></h4>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><a href="cpp-types-size_t.html"><span class="kw100">std::<span class="me2">size_t</span></span></a> f<span class="br0">(</span><span class="kw4">int</span> x<span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-types-size_t.html"><span class="kw100">std::<span class="me2">size_t</span></span></a> a<span class="sy4">;</span>
|
|
<span class="kw1">if</span><span class="br0">(</span>x<span class="br0">)</span> <span class="co1">// x 为零时会有 UB</span>
|
|
a <span class="sy1">=</span> <span class="nu0">42</span><span class="sy4">;</span>
|
|
<span class="kw1">return</span> a<span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能编译为(<a rel="nofollow" class="external text" href="https://godbolt.org/z/QYZvsl">演示</a>)</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">f<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="sy4">:</span>
|
|
mov eax, <span class="nu0">42</span>
|
|
ret</pre></div>
|
|
</div>
|
|
<div class="t-example">
|
|
<p>以下给出的输出曾在旧版本 gcc 上观察到</p>
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw4">bool</span> p<span class="sy4">;</span> <span class="co1">// 未初始化的局部变量</span>
|
|
<span class="kw1">if</span><span class="br0">(</span>p<span class="br0">)</span> <span class="co1">// UB :访问未初始化的标量</span>
|
|
<a href="cpp-io-c-puts.html"><span class="kw1830">std::<span class="me2">puts</span></span></a><span class="br0">(</span><span class="st0">"p is true"</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">if</span><span class="br0">(</span><span class="sy3">!</span>p<span class="br0">)</span> <span class="co1">// UB :访问未初始化的标量</span>
|
|
<a href="cpp-io-c-puts.html"><span class="kw1830">std::<span class="me2">puts</span></span></a><span class="br0">(</span><span class="st0">"p is false"</span><span class="br0">)</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
<p>可能的输出:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="text source-text">
|
|
<pre class="de1">p is true
|
|
p is false</pre></div>
|
|
</div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E9.9D.9E.E6.B3.95.E6.A0.87.E9.87.8F">非法标量</span></h4>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw4">int</span> f<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw4">bool</span> b <span class="sy1">=</span> <span class="kw2">true</span><span class="sy4">;</span>
|
|
<span class="kw4">unsigned</span> <span class="kw4">char</span><span class="sy2">*</span> p <span class="sy1">=</span> <span class="kw1">reinterpret_cast</span><span class="sy1"><</span><span class="kw4">unsigned</span> <span class="kw4">char</span><span class="sy2">*</span><span class="sy1">></span><span class="br0">(</span><span class="sy3">&</span>b<span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="sy2">*</span>p <span class="sy1">=</span> <span class="nu0">10</span><span class="sy4">;</span>
|
|
<span class="co1">// 从 b 读取现在是 UB</span>
|
|
<span class="kw1">return</span> b <span class="sy1">==</span> <span class="nu0">0</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能编译为(<a rel="nofollow" class="external text" href="https://gcc.godbolt.org/z/6itUO1">演示</a>)</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">f<span class="br0">(</span><span class="br0">)</span><span class="sy4">:</span>
|
|
movl $<span class="nu19">11</span>, <span class="sy2">%</span>eax
|
|
ret</pre></div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E7.A9.BA.E6.8C.87.E9.92.88.E8.A7.A3.E5.BC.95.E7.94.A8">空指针解引用</span></h4>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw4">int</span> foo<span class="br0">(</span><span class="kw4">int</span><span class="sy2">*</span> p<span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw4">int</span> x <span class="sy1">=</span> <span class="sy2">*</span>p<span class="sy4">;</span>
|
|
<span class="kw1">if</span><span class="br0">(</span><span class="sy3">!</span>p<span class="br0">)</span> <span class="kw1">return</span> x<span class="sy4">;</span> <span class="co1">// 要么如上产生 UB,要么不可能采用此分支</span>
|
|
<span class="kw1">else</span> <span class="kw1">return</span> <span class="nu0">0</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="kw4">int</span> bar<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw4">int</span><span class="sy2">*</span> p <span class="sy1">=</span> nullptr<span class="sy4">;</span>
|
|
<span class="kw1">return</span> <span class="sy2">*</span>p<span class="sy4">;</span> <span class="co1">// 无条件 UB</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能编译为( <a rel="nofollow" class="external text" href="https://godbolt.org/z/kzr8VJ">foo 用 gcc 、 bar 用 clang</a> )</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">foo<span class="br0">(</span><span class="kw4">int</span><span class="sy2">*</span><span class="br0">)</span><span class="sy4">:</span>
|
|
xorl <span class="sy2">%</span>eax, <span class="sy2">%</span>eax
|
|
ret
|
|
bar<span class="br0">(</span><span class="br0">)</span><span class="sy4">:</span>
|
|
retq</pre></div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E8.AE.BF.E9.97.AE.E5.B7.B2.E4.BC.A0.E9.80.92.E7.BB.99_realloc_.E7.9A.84.E6.8C.87.E9.92.88">访问已传递给 realloc 的指针</span></h4>
|
|
<div class="t-example">
|
|
<p>选择 clang 以观察所示输出</p>
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <iostream></span>
|
|
<span class="co2">#include <cstdlib></span>
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw4">int</span> <span class="sy2">*</span>p <span class="sy1">=</span> <span class="br0">(</span><span class="kw4">int</span><span class="sy2">*</span><span class="br0">)</span><a href="cpp-memory-c-malloc.html"><span class="kw759">std::<span class="me2">malloc</span></span></a><span class="br0">(</span>sizeof<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw4">int</span> <span class="sy2">*</span>q <span class="sy1">=</span> <span class="br0">(</span><span class="kw4">int</span><span class="sy2">*</span><span class="br0">)</span><a href="cpp-memory-c-realloc.html"><span class="kw762">std::<span class="me2">realloc</span></span></a><span class="br0">(</span>p, sizeof<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="sy2">*</span>p <span class="sy1">=</span> <span class="nu0">1</span><span class="sy4">;</span> <span class="co1">// UB :访问已传递给 realloc 的指针</span>
|
|
<span class="sy2">*</span>q <span class="sy1">=</span> <span class="nu0">2</span><span class="sy4">;</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>p <span class="sy1">==</span> q<span class="br0">)</span> <span class="co1">// UB :访问已传递给 realloc 的指针</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="sy2">*</span>p <span class="sy1"><<</span> <span class="sy2">*</span>q <span class="sy1"><<</span> <span class="st0">'<span class="es1">\n</span>'</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能的输出:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="text source-text">
|
|
<pre class="de1">12</pre></div>
|
|
</div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E6.97.A0.E5.89.AF.E4.BD.9C.E7.94.A8.E7.9A.84.E6.97.A0.E9.99.90.E5.BE.AA.E7.8E.AF">无副作用的无限循环</span></h4>
|
|
<div class="t-example">
|
|
<p>选择 clang 以观察所示输出</p>
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <iostream></span>
|
|
|
|
<span class="kw4">int</span> fermat<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw4">const</span> <span class="kw4">int</span> MAX <span class="sy1">=</span> <span class="nu0">1000</span><span class="sy4">;</span>
|
|
<span class="kw4">int</span> a<span class="sy1">=</span><span class="nu0">1</span>,b<span class="sy1">=</span><span class="nu0">1</span>,c<span class="sy1">=</span><span class="nu0">1</span><span class="sy4">;</span>
|
|
<span class="co1">// 无副作用的无限循环是 UB</span>
|
|
<span class="kw1">while</span> <span class="br0">(</span><span class="nu0">1</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span><span class="br0">(</span><span class="br0">(</span>a<span class="sy2">*</span>a<span class="sy2">*</span>a<span class="br0">)</span> <span class="sy1">==</span> <span class="br0">(</span><span class="br0">(</span>b<span class="sy2">*</span>b<span class="sy2">*</span>b<span class="br0">)</span><span class="sy2">+</span><span class="br0">(</span>c<span class="sy2">*</span>c<span class="sy2">*</span>c<span class="br0">)</span><span class="br0">)</span><span class="br0">)</span><span class="br0">)</span> <span class="kw1">return</span> <span class="nu0">1</span><span class="sy4">;</span>
|
|
a<span class="sy2">++</span><span class="sy4">;</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>a<span class="sy1">></span>MAX<span class="br0">)</span> <span class="br0">{</span> a<span class="sy1">=</span><span class="nu0">1</span><span class="sy4">;</span> b<span class="sy2">++</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>b<span class="sy1">></span>MAX<span class="br0">)</span> <span class="br0">{</span> b<span class="sy1">=</span><span class="nu0">1</span><span class="sy4">;</span> c<span class="sy2">++</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>c<span class="sy1">></span>MAX<span class="br0">)</span> <span class="br0">{</span> c<span class="sy1">=</span><span class="nu0">1</span><span class="sy4">;</span><span class="br0">}</span>
|
|
<span class="br0">}</span>
|
|
<span class="kw1">return</span> <span class="nu0">0</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>fermat<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"Fermat's Last Theorem has been disproved.<span class="es1">\n</span>"</span><span class="sy4">;</span>
|
|
<span class="kw1">else</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"Fermat's Last Theorem has not been disproved.<span class="es1">\n</span>"</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能的输出:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="text source-text">
|
|
<pre class="de1">Fermat's Last Theorem has been disproved.</pre></div>
|
|
</div>
|
|
</div>
|
|
<h3><span class="mw-headline" id=".E5.A4.96.E9.83.A8.E9.93.BE.E6.8E.A5">外部链接</span></h3>
|
|
<ul>
|
|
<li><a rel="nofollow" class="external text" href="http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html">每个 C 程序员都应该知道的关于未定义行为的事 #1/3</a></li>
|
|
<li><a rel="nofollow" class="external text" href="http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html">每个 C 程序员都应该知道的关于未定义行为的事 #2/3</a></li>
|
|
<li><a rel="nofollow" class="external text" href="http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html">每个 C 程序员都应该知道的关于未定义行为的事 #3/3</a></li>
|
|
<li><a rel="nofollow" class="external text" href="http://blogs.msdn.com/b/oldnewthing/archive/2014/06/27/10537746.aspx">未定义行为能导致时间旅行(在所有事项中,时间旅行可是最惊人的)</a></li>
|
|
<li><a rel="nofollow" class="external text" href="http://www.cs.utah.edu/~regehr/papers/overflow12.pdf">了解 C/C++ 中的整数溢出</a></li>
|
|
<li><a rel="nofollow" class="external text" href="http://kukuruku.co/hub/cpp/undefined-behavior-and-fermats-last-theorem">未定义行为与费马大定理</a></li>
|
|
<li><a rel="nofollow" class="external text" href="http://lwn.net/Articles/342330/">空指针的趣事,第一部分</a> ( Linux 2.6.30 中空指针解引用所致的未定义行为所引发的局部滥用)</li>
|
|
</ul>
|
|
<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>
|