<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>计算机系统 on 苏三有春的博客</title><link>https://www.lyrical-wander.cn/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/</link><description>Recent content in 计算机系统 on 苏三有春的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>Lyrical Wander</copyright><lastBuildDate>Tue, 05 Aug 2025 21:14:00 +0800</lastBuildDate><atom:link href="https://www.lyrical-wander.cn/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/index.xml" rel="self" type="application/rss+xml"/><item><title>如何写出高效的代码——编译器的优化能力和局限性</title><link>https://www.lyrical-wander.cn/p/%E5%A6%82%E4%BD%95%E5%86%99%E5%87%BA%E9%AB%98%E6%95%88%E7%9A%84%E4%BB%A3%E7%A0%81%E7%BC%96%E8%AF%91%E5%99%A8%E7%9A%84%E4%BC%98%E5%8C%96%E8%83%BD%E5%8A%9B%E5%92%8C%E5%B1%80%E9%99%90%E6%80%A7/</link><pubDate>Tue, 05 Aug 2025 21:14:00 +0800</pubDate><guid>https://www.lyrical-wander.cn/p/%E5%A6%82%E4%BD%95%E5%86%99%E5%87%BA%E9%AB%98%E6%95%88%E7%9A%84%E4%BB%A3%E7%A0%81%E7%BC%96%E8%AF%91%E5%99%A8%E7%9A%84%E4%BC%98%E5%8C%96%E8%83%BD%E5%8A%9B%E5%92%8C%E5%B1%80%E9%99%90%E6%80%A7/</guid><description>&lt;img src="https://www.lyrical-wander.cn/p/%E5%A6%82%E4%BD%95%E5%86%99%E5%87%BA%E9%AB%98%E6%95%88%E7%9A%84%E4%BB%A3%E7%A0%81%E7%BC%96%E8%AF%91%E5%99%A8%E7%9A%84%E4%BC%98%E5%8C%96%E8%83%BD%E5%8A%9B%E5%92%8C%E5%B1%80%E9%99%90%E6%80%A7/image.jpg" alt="Featured image of post 如何写出高效的代码——编译器的优化能力和局限性" /&gt;&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;今日技术梳理：《深入理解计算机系统》第五章第一节：优化编译器的能力和局限性。这个标题有一些拗口，根据中文的阅读习惯，我们可能会对标题误解为：“程序员对编译器进行优化，这种能力与这种能力的局限性”。其实不然，本节所讨论的是“编译器自身对程序的优化能力与其优化能力的局限性”。我们知道，编译器本身在将我们的C语言代码转换为机器码时，首先会通过自身复杂且精细的算法来对其进行一定程度上的优化，利用一些机会来简化表达式，而本节中，我们则需要了解编译器的这种优化能力及其局限性，它为什么能进行优化，又在什么情况下无法进行优化，以便我们能够写出让编译器更好优化的代码，即：效率更高的代码。&lt;/p&gt;
&lt;h2 id="编译器的优化等级"&gt;编译器的优化等级&lt;/h2&gt;
&lt;p&gt;在现代大多数编译器中，包括GCC，向用户提供了一些对它们所使用的优化的控制，使得我们可以控制编译器对代码的优化程度，例如：以命令行选项 &lt;strong&gt;&amp;quot;-Og&amp;quot;&lt;/strong&gt; 调用GCC会让GCC使用一组基本的优化，以 &lt;strong&gt;-O1&lt;/strong&gt;或更高 &lt;strong&gt;-O2&lt;/strong&gt;，&lt;strong&gt;-O3&lt;/strong&gt;等，调用会使得GCC使用更大量的优化，可以提高程序的性能，但相应的被编译器优化后的代码不一定与源代码相同，在某些情况下会使得调试更加困难。&lt;/p&gt;
&lt;h2 id="编译器的安全优化"&gt;编译器的安全优化&lt;/h2&gt;
&lt;p&gt;编译器必须只能对程序使用 &lt;strong&gt;安全的优化&lt;/strong&gt; 。&lt;/p&gt;
&lt;h3 id="什么是安全优化"&gt;什么是安全优化&lt;/h3&gt;
&lt;p&gt;安全优化是指：编译器在保证程序&lt;strong&gt;行为不变&lt;/strong&gt;（遵循C/C++标准中的&amp;quot;as-if&amp;quot;规则）的前提下进行的优化，即使优化后的代码逻辑变化（如删除冗余计算，内联函数的等），程序的&lt;strong&gt;可观测行为&lt;/strong&gt;必须和未优化版本一致。也就是说，对于程序可能遇到的所有情况下，优化后的程序必须保证和未优化的程序运行结果一样。&lt;/p&gt;
&lt;h3 id="优化等级与安全性的关系"&gt;优化等级与安全性的关系&lt;/h3&gt;
&lt;p&gt;就本质而言，所有的优化等级都在安全范围内，即符合安全规则，但不同成都的优化等级采取的优化策略激进程度不同。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;默认无优化&lt;/strong&gt;：&lt;strong&gt;-O0&lt;/strong&gt;，不进行任何优化，代码直接映射到源码，便于调试，安全性最高，行为完全符合源码，但性能最低&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删除未使用的变量&lt;/strong&gt;：该策略从 &lt;strong&gt;-O1&lt;/strong&gt; 起，更高等级的优化也会使用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常量折叠&lt;/strong&gt;：该策略从 &lt;strong&gt;-O1&lt;/strong&gt; 起，更高等级的优化也会使用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;循环展开&lt;/strong&gt;： &lt;strong&gt;-O2&lt;/strong&gt; 起会采用该策略，较为激进的策略&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内联函数&lt;/strong&gt;： &lt;strong&gt;-O2&lt;/strong&gt; 起会采用该策略&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向量化，并行化&lt;/strong&gt;：&lt;strong&gt;-O3&lt;/strong&gt; 会采用的策略，更加激进，但仍在安全范围内，但可能暴露源码中&lt;strong&gt;隐藏的未定义行为&lt;/strong&gt;（如越界访问），但这源于代码自身问题，而非优化引入。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总的而言，虽然GCC可以采取不同程度的优化策略，但都是处于安全范围内，遵循语言规范，如若程序因优化出现异常，需检查源码中未定义的行为。同时这也侧面反映出，优化的一大特性，即：优化等级越高，与源码可能越不相同，越难调试。&lt;/p&gt;
&lt;h2 id="编译器优化的局限性"&gt;编译器优化的局限性&lt;/h2&gt;
&lt;p&gt;请看下面的例子：&lt;/p&gt;
&lt;h3 id="指针未知导致的优化阻碍"&gt;指针未知导致的优化阻碍&lt;/h3&gt;
&lt;p&gt;下面给出两个函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;twiddle1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;yp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;yp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;yp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;twiddle2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;yp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;yp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这两个函数似乎在完成相同的行为，即将yp指针所指的值两次加到xp指针所指的值。&lt;/p&gt;
&lt;p&gt;但我们从机器运行的角度来考虑这两个代码运行的效率，首先 &lt;code&gt;twiddle1&lt;/code&gt;函数会在&lt;code&gt;*xp += *yp;&lt;/code&gt;处产生读*xp一次，读*yp一次，写*xp一次，共计3次操作，所以，整个函数将会产生6次对内存的引用操作。而&lt;code&gt;twiddle2&lt;/code&gt;仅需要1次读*xp，1次读*yp，1次写*xp，共计3次内存引用操作即可。&lt;/p&gt;
&lt;p&gt;由此我们可以看出，&lt;code&gt;twiddle2&lt;/code&gt;函数的效率更高一些。因此，如果编译器要编译&lt;code&gt;twiddle1&lt;/code&gt;，我们会认为，将其编译成&lt;code&gt;twiddle2&lt;/code&gt;的形式会产生更加高效的代码。&lt;/p&gt;
&lt;p&gt;但是如果我们考虑到，如果*xp与*yp引用的地址是同一个时，如下的情形：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;twiddle1&lt;/code&gt;函数会执行以下计算：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 此时xp指针所指的值会变为原来的2倍
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 此时xp指针所指的值会变为原来的4倍
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;twiddle2&lt;/code&gt;函数会执行以下计算：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 此时xp所指的值会变为原来的3倍
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这种两个指针可能指向同一个内存位置的情况被称为 &lt;strong&gt;内存别名使用(memory aliasing)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此，在某种特殊的情况下，&lt;code&gt;twiddle1&lt;/code&gt;与&lt;code&gt;twiddle2&lt;/code&gt;可能会产生不同的值，因此，根据我们上面所说：编译器必须只能对程序进行 &lt;strong&gt;安全的优化&lt;/strong&gt; ，在只执行安全的优化中，编译器必须假设不同的指针可能会只想内存中的同一位置，因而编译器不会将&lt;code&gt;twiddle1&lt;/code&gt;优化成&lt;code&gt;twiddle2&lt;/code&gt;的形式，即使在大部分情况下，&lt;code&gt;twiddle2&lt;/code&gt;的效率要比&lt;code&gt;twiddle1&lt;/code&gt;要高。&lt;/p&gt;
&lt;p&gt;我们再来考虑下面一个例子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;此程序看上去十分明确，但实际上，t1的计算值我们目前是无法直接确定的，t1的计算值依赖于指针p和q是否指向同一块内存区域，如果指针p和指针q&lt;strong&gt;不指向同一块内存&lt;/strong&gt;，则显然t1的值就是&lt;strong&gt;3000&lt;/strong&gt;，但如果指针p和指针q&lt;strong&gt;指向的内存区域相同&lt;/strong&gt;，那么指针q一开始赋值的3000就会被后面指针p的赋值给覆盖掉，因此此时t1的值为&lt;strong&gt;1000&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就造成了一个主要的妨碍优化的因素，这也是可能严重限制编译器产生优化代码机会的程序的一个方面，如果编译器不能确定两个指针是否指向同一个位置，就必须假设它们可能会指向同一个位置，这就限制了可能的优化策略。&lt;/p&gt;
&lt;h3 id="函数调用时产生的优化阻碍"&gt;函数调用时产生的优化阻碍&lt;/h3&gt;
&lt;p&gt;考虑下面两个函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ling&lt;/span&gt; &lt;span class="nf"&gt;func1&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;func2&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这两个函数看上去计算的都是相同的结果，但是 &lt;strong&gt;func2&lt;/strong&gt; 只调用 f 1次，而 &lt;strong&gt;func1&lt;/strong&gt; 调用了 f 4次，这显然 &lt;strong&gt;func1&lt;/strong&gt; 的开销要大于 &lt;strong&gt;func2&lt;/strong&gt; 。如果以 &lt;strong&gt;func1&lt;/strong&gt; 作为源代码时，编译器很想产生 &lt;strong&gt;func2&lt;/strong&gt;风格的代码，但考虑以下 f 函数的代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;聪明的程序员很快就反应过来了，这个 f 函数修改了一个全局变量 counter，并将其状态返回，改变调用 f 函数的次数会改变程序的行为，即返回值。假设开始时全局变量counter都设置为0，那么对 &lt;strong&gt;func1&lt;/strong&gt; 的调用会返回 0+1+2+3=6，而对于 &lt;strong&gt;func2&lt;/strong&gt; 的调用会返回 4*0=0。&lt;/p&gt;
&lt;p&gt;大多数编译器都会假设最糟糕的情况，因此不会产生 &lt;strong&gt;func2&lt;/strong&gt; 的样子，而是保持函数调用不变。&lt;/p&gt;
&lt;h2 id="用内联函数替换优化函数的调用"&gt;用内联函数替换优化函数的调用&lt;/h2&gt;
&lt;p&gt;包含函数调用的代码可以用一个称为内联函数替换(inline substitution, 简称为内联)的过程进行优化，此时，将函数调用替换为函数体。&lt;/p&gt;
&lt;p&gt;例如，对上面的例子，我们可以通过替换掉函数 f 的四次调用，展开 &lt;strong&gt;func1&lt;/strong&gt; 的代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;funclin&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这样的转换减少了函数调用的开销，也允许对展开的代码做进一步优化，比如编译器可以统一 &lt;strong&gt;funclin&lt;/strong&gt; 中对全局变量counter的更新，产生这个函数的一个优化版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;funclopt&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;上面的函数 &lt;strong&gt;funclopt&lt;/strong&gt; 忠实地重现了 f 的行为。&lt;/p&gt;
&lt;p&gt;在GCC中，如果你使用 &lt;strong&gt;-finline&lt;/strong&gt;或者优化等级 &lt;strong&gt;-o1&lt;/strong&gt;或更高时，编译器就会尝试进行这种形式的优化。但遗憾的是，GCC只尝试在单个文件中定义的函数进行内联。这就意味着它将无法应用于更常见的情况。&lt;/p&gt;
&lt;p&gt;在某些情况下，最好能阻止编译器执行内联替换。一种情况是用符号调试器来评估代码，比如GDB，如果一个函数被内联优化过了，那么任何对这个调用进行追踪或设置断点的尝试都会失败。还有一种情况是用代码剖析得方式来评估程序性能，用内联替换的函数调用是无法被正确剖析的。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;在本节中，我们指出了GCC本身具有对程序的优化能力以提升程序效率，但根据安全规定，编译器仅愿意对程序进行“安全的优化”，在考虑各种情况下，若程序的行为因为优化而发生改变，编译器便会放弃优化。就单论优化能力而言，GCC被认为是胜任的，但并不突出，原因是编译器会考虑各种最糟糕的情况而不会进行优化，或采取一些较为“离奇”的手段进行优化，保证提高效率的同时忠实地重现行为，但代价是牺牲了程序的可读性和调试能力。&lt;/p&gt;
&lt;p&gt;因此，在了解编译器的优化局限性后，希望同学们能够注意本文中提到的可能会给编译器带来的“困惑”，在后续的章节中，我们再来讨论如何编写出优秀的高效的代码。&lt;/p&gt;</description></item><item><title>C语言中关于数据类型带来的陷阱</title><link>https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/</link><pubDate>Fri, 22 Nov 2024 21:14:00 +0800</pubDate><guid>https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/</guid><description>&lt;img src="https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image.jpg" alt="Featured image of post C语言中关于数据类型带来的陷阱" /&gt;&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;笔者在阅读《深入理解计算机系统》时，理解了为何C语言被称为&lt;strong&gt;不安全语言&lt;/strong&gt;，C语言除了指针非常灵活可能会导致大量漏洞之外，C语言的无符号数据也可能带来致命性危害。&lt;/p&gt;
&lt;h2 id="扩展一个数据的表示"&gt;扩展一个数据的表示&lt;/h2&gt;
&lt;p&gt;将一个无符号数转换为更大的数据类型，我们只需要在表示的开头添加0，这种运算称之为&lt;strong&gt;0扩展(zero extension)&lt;/strong&gt;，将一个补码数字转换为一个更大的数据类型可以执行&lt;strong&gt;符号扩展(sign extension)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此我们可以看到，无符号表示的数和补码表示的数扩展方式不一样，这就会导致下面这种情况：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;sx = -12345: cf c7&lt;/p&gt;
&lt;p&gt;usx = 53191: cf c7&lt;/p&gt;
&lt;p&gt;x = -12345: ff ff cf c7&lt;/p&gt;
&lt;p&gt;ux = 53191: 00 00 cf c7&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;尽管-12345的补码表示和53191的无符号表示在16位字长时是相同的，但是在32位字长时却是不同的。前者使用的是符号拓展——最开头加了16位，都是最高有效位1，后者开头使用了16个0来扩展。&lt;/p&gt;
&lt;p&gt;在书中有&lt;strong&gt;符号扩展，数值不变&lt;/strong&gt;的数学证明过程，不感兴趣的小伙伴可以跳过这一部分。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;$B2T_{w+k}([x_{w-1},&amp;hellip;,x_{w-1},x_{w-1},x_{w-2},&amp;hellip;x_0])$ = $B2T_w([x_{w-1},x_{w-2},&amp;hellip;,x_0])$&lt;/p&gt;
&lt;p&gt;上面这一条表达式左边增加了K个$x_{w-1}$，下面的证明是对k进行的归纳，即，我们只需证明拓展1次，即可通过数学归纳法证明上式正确&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;要证明：&lt;/p&gt;
&lt;p&gt;$B2T_{w+1}([x_{w-1},x_{w-1},x_{w-2},&amp;hellip;,x_0])$ = $B2T_w([x_{w-1},x_{w-2},&amp;hellip;,x_0])$&lt;/p&gt;
&lt;p&gt;展开左边得到：&lt;/p&gt;
$$
\begin{aligned}
B2T_{w+1}([x_{w-1},x_{w-1},x_{w-2},...,x_0])
&amp;=\ -x_{w-1}2^{w}+\sum\limits^{w-1}_{i=0}x_{i}2^{i}\\
&amp;=\ -x_{w-1}2^{w} + x_{w-1}2^{w-1} + \sum\limits^{w-2}_{i=0}x_{i}2^{i}\\
&amp;=\ -x_{w-1}(2^{w} - 2^{w-1}) + \sum\limits^{w-2}_{i=0}x_{i}2^{i}\\
&amp;=\ B2T_w([x_{w-1},x_{w-2},...,x_0])\\
\end{aligned}
$$&lt;h2 id="不同大小的有无符号转变"&gt;不同大小的有无符号转变&lt;/h2&gt;
&lt;p&gt;当把&lt;code&gt;short&lt;/code&gt;转换成&lt;code&gt;unsigned&lt;/code&gt;时，我们先要改变大小，之后再完成从有符号到无符号的转变。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;unsigned sx&lt;/code&gt; = &lt;code&gt;unsigned int sx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;因为是先改变大小，因此是先按照有符号的规则进行&lt;strong&gt;符号扩展&lt;/strong&gt;，然后再从有符号转变成无符号。&lt;/p&gt;
&lt;h2 id="截断数字"&gt;截断数字&lt;/h2&gt;
&lt;p&gt;当我们强制类型转换将&lt;code&gt;int&lt;/code&gt;修改为&lt;code&gt;short&lt;/code&gt;，是直接将数字高位的相应部分直接截断，因此截断一个数字可能会改变它的值（溢出的部分没了）。截断它到k位的结果就相当于计算 $x mode 2^k$。&lt;/p&gt;
&lt;h2 id="关于有符号数与无符号数"&gt;关于有符号数与无符号数&lt;/h2&gt;
&lt;p&gt;有符号数到无符号数的隐式强制类型转换导致了某些非直观的行为。而这些非直观的特性经常导致程序错误，并且这种包含隐式强制类型转换细微差别的错误很难被发现。&lt;/p&gt;
&lt;p&gt;下面给出两个例题：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123100344653.png"
width="1066"
height="392"
srcset="https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123100344653_hu_249851a84d35b6c9.png 480w, https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123100344653_hu_88580e66f2a20fa9.png 1024w"
loading="lazy"
alt="例题 2.25"
class="gallery-image"
data-flex-grow="271"
data-flex-basis="652px"
&gt;&lt;/p&gt;
&lt;p&gt;根据上一篇文章得知，在无符号数与符号数之间的比较时，通通转换成无符号数比较。&lt;/p&gt;
&lt;p&gt;可以看出，当&lt;code&gt;usigned length&lt;/code&gt; 被设置为0时，在&lt;code&gt;length - 1&lt;/code&gt;中，无符号数的减法是通过模运算实现的，因此不会得到负数，该表达式会变成&lt;code&gt;UINT_MAX - 1&lt;/code&gt;（在32位系统中为0xFFFFFFFE，即4294967294），这么大的数进行遍历时，a数组的长度肯定是不够的，因此会遇到存储器错误，修改只需要将&lt;code&gt;unsigned length&lt;/code&gt;改为&lt;code&gt;int length&lt;/code&gt;即可&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123101300660.png"
width="1289"
height="675"
srcset="https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123101300660_hu_68404649757aa46a.png 480w, https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123101300660_hu_672c8958b4a329c.png 1024w"
loading="lazy"
alt="例题 2.26"
class="gallery-image"
data-flex-grow="190"
data-flex-basis="458px"
&gt;&lt;/p&gt;
&lt;p&gt;在此例题中，因为&lt;code&gt;strlen&lt;/code&gt;函数返回值的数据类型为&lt;code&gt;size_t&lt;/code&gt;即&lt;code&gt;unsigned int&lt;/code&gt;，因此涉及到无符号数据类型是要小心是否会转变成有符号类型，如果有，在转变时是否会发生与预期的数值产生偏差。&lt;/p&gt;
&lt;p&gt;在此处，&lt;code&gt;strlen(s)&lt;/code&gt;与&lt;code&gt;strlen(t)&lt;/code&gt;都是无符号类型，且进行大小比较，因此整个表达式都会转换成无符号类型进行比较。&lt;/p&gt;
&lt;p&gt;在无符号类型中，&lt;code&gt;0&lt;/code&gt;就是最小值，无论&lt;code&gt;strlen(s)&lt;/code&gt;与&lt;code&gt;strlen(t)&lt;/code&gt;谁大谁小，相减也不会小于0，因此这个表达式只有在两者相等时为&lt;code&gt;false&lt;/code&gt;，否则皆为&lt;code&gt;true&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="函数getpreername的安全漏洞"&gt;函数&lt;code&gt;getpreername&lt;/code&gt;的安全漏洞&lt;/h2&gt;
&lt;p&gt;2002 年，从事 FreeBSD 开源操作系统项目的程序员意识到，他们对 getpeername 函数的实现存在安全漏洞。代码的简化版本如下 ：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123101906044.png"
width="1072"
height="686"
srcset="https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123101906044_hu_bf1c0d92026573f2.png 480w, https://www.lyrical-wander.cn/p/c%E8%AF%AD%E8%A8%80%E4%B8%AD%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%99%B7%E9%98%B1/image-20241123101906044_hu_400f437dc7a4814b.png 1024w"
loading="lazy"
alt="getpeername"
class="gallery-image"
data-flex-grow="156"
data-flex-basis="375px"
&gt;&lt;/p&gt;
&lt;p&gt;​ 在这段代码里，第 7 行给出的是库函数 memcpy 的原型，这个函数是要将一段指定长度为 n的字节从存储器的一个区域复制到另一个区域。&lt;/p&gt;
&lt;p&gt;​ 从第 14 行开始的函数 copy_from_kernel 是要将一些操作系统内核维护的数据复制到指定的用户可以访问的存储器区域。对用户来说，大多数内核维护的数据结构应该是不可读的，因为这些数据结构可能包含其他用户和系统上运行的其他作业的敏感信息，但是显示为 kbuf 的区域是用户可以读的。参数 maxlen 给出的是分配给用户的缓冲区的长度，这个缓冲区是用参数user_dest 指示的。然后，第 16 行的计算确保复制的字节数据不会超出源或者目标缓冲区可用的范围。&lt;/p&gt;
&lt;p&gt;​ 不过，假设有些怀有恶意的程序员在调用 copy_from_kernel 的代码中对 maxlen 使用了负数值，那么，第 16 行的最小值计算会把这个值赋给 len，然后 len 会作为参数 n 被传递给 memcpy。不过，请注意参数 n 是被声明为数据类型 size_t 的。这个数据类型是在库文件stdio.h 中（通过 typedef）被声明的。典型地，在 32 位机器上被定义为 unsigned int。既然参数 n 是无符号的，那么 memcpy 会把它当作一个非常大的正整数，并且试图将这样多字节的数据从内核区域复制到用户的缓冲区。虽然复制这么多字节（至少 231 个）实际上不会完成，因为程序会遇到进程中非法地址的错误，但是程序还是能读到没有被授权的内核存储器区域。&lt;/p&gt;
&lt;p&gt;​ 我们可以看到，这个问题是由于数据类型的不匹配造成的 ：在一个地方，长度参数是有符号数 ；而另一个地方，它又是无符号数。正如这个例子表明的那样，这样的不匹配会成为缺陷的原因，甚至会导致安全漏洞。幸运的是，还没有案例报告有程序员在 FreeBSD 上利用了这个漏洞。他们发布了一个安全建议，“FreeBSD-SA-02:38.signed-error”，建议系统管理员如何应用补丁消除这个漏洞。要修正这个缺陷，只要将 copy_from_kernel 的参数 maxlen 声明为类型 size_t，也就是与 memcpy 的参数 n 一致。同时，我们也应该将本地变量 len 和返回值声明为 size_t。&lt;/p&gt;
&lt;p&gt;​ 实际上，除了C语言，很少有语言支持无符号整数，很明显，设计者们认为它们带来的麻烦要比益处多得多。&lt;/p&gt;
&lt;p&gt;​ 但当我们想把字仅仅看作时位的集合，并且没有任何数字意义时，无符号数值是非常有用的。例如，往一个字中放入描述各种布尔条件的标记时，就是这样。地址自然地就是无符号的，所以系统程序员发现无符号类型是很有帮助的。当实现模运算和多精度运算的数学包时，数字是由字的数组来表示的，无符号值也会非常有用。&lt;/p&gt;</description></item><item><title>计算机系统中信息数据的处理</title><link>https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/</link><pubDate>Thu, 21 Nov 2024 13:14:00 +0800</pubDate><guid>https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/</guid><description>&lt;img src="https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image.jpg" alt="Featured image of post 计算机系统中信息数据的处理" /&gt;&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;本文同样地是整理和归纳《深入理解计算机系统》这本书的内容，但本文不会继续长篇大论地去将所有内容都总结，而是总结笔者认为容易遗忘或混淆或表述不清的内容。&lt;/p&gt;
&lt;h2 id="字"&gt;字&lt;/h2&gt;
&lt;p&gt;首先&lt;strong&gt;字&lt;/strong&gt;这个概念对于学习过《计算机体系结构》或《操作系统》的同学都不陌生，但是笔者对于这个概念很容易遗忘，所以笔者还是记录下来。&lt;/p&gt;
&lt;p&gt;总的来说：&lt;strong&gt;字长&lt;/strong&gt;决定了虚拟地址空间的最大大小。也就是说，对于一个字长为 &lt;em&gt;w&lt;/em&gt; 位的机器而言，虚拟地址的范围为 0 ～ $2^w-1 $，程序最多访问 $2^w$ 个字节。&lt;/p&gt;
&lt;p&gt;如果一台计算机的字长为32位，这就限定了虚拟地址空间为4GB。也就是说，即使你的主存容量再大，处理器也只能寻址4G的范围，同样的道理，在嵌入式中，8位机的概念就是字长为8位的微型处理器。&lt;/p&gt;
&lt;p&gt;现在市面上有许多32位，64位机器，在不同机器之间程序会遇到数据长度，寻址能力等方面的挑战，一个强大的程序，应该兼容不同的机器。&lt;/p&gt;
&lt;h2 id="数据大小"&gt;数据大小&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121142443840.png"
width="644"
height="240"
srcset="https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121142443840_hu_16ffbfc682d85fce.png 480w, https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121142443840_hu_659905d39a736390.png 1024w"
loading="lazy"
alt="size of data"
class="gallery-image"
data-flex-grow="268"
data-flex-basis="644px"
&gt;&lt;/p&gt;
&lt;p&gt;可以看到，在C语言中，数据类型在不同的字长的机器上占有不同的字节数，程序员应该力图使它们的程序在不同的机器和编译器上是可移植的，可移植性的一个方面就是程序堆不同数据类型的确切大小不敏感。&lt;/p&gt;
&lt;h2 id="布尔代数"&gt;布尔代数&lt;/h2&gt;
&lt;p&gt;布尔代数实际上就是围绕数字0与1建立起来的一种代数体系，以研究逻辑推理和基本原则。&lt;/p&gt;
&lt;p&gt;布尔代数只有0和1两个数，拥有&lt;strong&gt;与（&amp;amp;）&lt;/strong&gt;、&lt;strong&gt;或（|）&lt;/strong&gt;、&lt;strong&gt;非（~）&lt;/strong&gt;、&lt;strong&gt;异或（^）&lt;/strong&gt; 等运算，且运算间也遵循某种分配律和交换律&lt;/p&gt;
&lt;h3 id="位级运算"&gt;位级运算&lt;/h3&gt;
&lt;p&gt;在这里我们值得一提的是：在异或（^）运算中，我们需要注意到：&lt;code&gt;a^a=0&lt;/code&gt;以及&lt;code&gt;a^b^a=b&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;正是由于异或拥有这种神奇的性质，我们可以完成以下脑经急转弯。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;请设计一个程序，在不适用第三个变量的情况下，交换x与y的值&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;inplace_swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// step1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// step2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// step3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;从上面这个函数可以看出，在step1时，y现在存储的值时 &lt;code&gt;x ^ y&lt;/code&gt; ，在step2时，x 的值变为 &lt;code&gt;x^ x ^ y = y&lt;/code&gt;这是根据&lt;code&gt;a^b^a=b&lt;/code&gt;的运算规则，step3同学们可以自己算一下。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a^b^a=b&lt;/code&gt;这是一个运算规则而非定理，即，这并非人为规定，而是客观规律。我们尝试以下运算&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a = 0110 0101&lt;/p&gt;
&lt;p&gt;b = 1100 1011&lt;/p&gt;
&lt;p&gt;根据异或的运算规则，相同则为0，不同则为1&lt;/p&gt;
&lt;p&gt;我们设c = a ^ b = 1010 1110&lt;/p&gt;
&lt;p&gt;此时再用c ^ a 得到&lt;/p&gt;
&lt;p&gt;d = c ^ a = 1100 1011&lt;/p&gt;
&lt;p&gt;你会发现，d = b ，即 a ^ b ^ a = b&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;除此之外，还有一个例题非常有意思&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;bis(位设置)：输入一个数据字x和一个掩码字m，生成一个结果z，z是由掩码m的位来修改x得到的，这种修改是在m为1的每个位置上，将z对应的位置设为1&lt;/p&gt;
&lt;p&gt;bic(位清除)：输入一个数据字x和一个掩码字m，生成一个结果z，z是由掩码m的位来修改x得到的，这种修改是在m为1的每个位置上，将z对应的位置设为0&lt;/p&gt;
&lt;p&gt;为了清楚因为这些运算与 C 语言位级运算的关系，假设我们有两个函数 bis 和 bic 来实现位设置和位清除操作。只想用这两个函数，而不使用任何其他 C 语言运算，来实现按位 | 和 ^ 运算。填写下列代码中缺失的代码。提示 ：写出 bis 和 bic 运算的 C 语言表达式。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 你可以使用的函数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;bis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;bic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 完善下列函数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;bool_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_______&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;bool_xor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_______&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;我们可以根据说明，模拟一下bis函数的运行输入输出：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当x = 0110 0101 m = 1100 1011时，&lt;/p&gt;
&lt;p&gt;根据bis的说明，m的某一位为1时，则x对应的位被修改为1，其它不变&lt;/p&gt;
&lt;p&gt;那么，res = bis(x,y) 则 res = 1110 1111&lt;/p&gt;
&lt;p&gt;根据bic的说明，m的某一位为1时，则x对应的位被修改为0，其它不变&lt;/p&gt;
&lt;p&gt;那么， res = bic(x,y) 则 res = 0010 0100&lt;/p&gt;
&lt;p&gt;那么， res = bic(y,x) 则 res = 1000 1010&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;根据上面的演算，我们可以发现，当你使用bis时，则x与m两者都是0的位才为0，否则即为1。当你使用bic时，则仅有m为0时且x为1时，才为1。&lt;/p&gt;
&lt;p&gt;那么或运算就好办了，因为或运算的规则就是只要有1则为1，逆命题就是两者为0才为0，即bis运算&lt;/p&gt;
&lt;p&gt;则第一个函数为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;bool_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;异或运算的规则为，只要不同则为1，否则为0。根据bic运算，&lt;strong&gt;当你使用bic时，则仅有m为0时且x为1时，才为1&lt;/strong&gt;，可以得到不同的其中一种情况，即&lt;strong&gt;m为0且x为1&lt;/strong&gt;，还要第二种情况，&lt;strong&gt;m为1且x为0&lt;/strong&gt;，那么只需要将m与x的位置调换，在运算以此即可获得，然后将两者进行或预算，也就是bis运算即可。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;bool_xor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nf"&gt;bic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="掩码"&gt;掩码&lt;/h2&gt;
&lt;p&gt;在数据处理中的掩码与计算机网络中的掩码概念有所不同，在这里的掩码你可以理解为是一种滤波器，它可以将特定位的数据通过而屏蔽掉其它位的数据，如&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;掩码：0x0000FF 与 a : 0x123456 进行运算，则会得到&lt;/p&gt;
&lt;p&gt;res = 0x000056&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="补码编码"&gt;补码编码&lt;/h2&gt;
&lt;p&gt;跳过了无符号数的编码&lt;/p&gt;
&lt;p&gt;在计算机中，有符号数的表示方式就是补码，将&lt;strong&gt;字&lt;/strong&gt;中最高有效位定义为&lt;strong&gt;负权&lt;/strong&gt;（negative weight），如&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1011 = -1*$2^3$+0*$2^2$+1*$2^1$+1*$2^0$ = -8 + 0 + 2 + 1 = -5&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在这种编码下，最高位变成了乘了一个&lt;code&gt;-1&lt;/code&gt;，因此，在这种情况下，我们要考虑在有有符号数补码编码的情况下的几种特殊情况：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;0000 = 0&lt;/p&gt;
&lt;p&gt;1111 = -1&lt;/p&gt;
&lt;p&gt;0001 = 1&lt;/p&gt;
&lt;p&gt;1000 = -8&lt;/p&gt;
&lt;p&gt;0111 = 7&lt;/p&gt;
&lt;p&gt;在字长未4的情况下，有符号数的补码编码中，取值范围为[-8:7]，1111并非最小值，而是-1，1000才是最小值为-8，0111是最大值7&lt;/p&gt;
&lt;p&gt;在0000，0001，0010，&amp;hellip;，0111，1000，&amp;hellip;，1111以此类推中，实际上的数值变化是，0，1，2，&amp;hellip;，7，-8，-7，&amp;hellip;，-1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由此我们可以看到，补码的范围是不对成的，即最小值没有与之对应的正数。这导致补码运算的某些特殊的属性，并且容易造成程序中细微的错误。&lt;/p&gt;
&lt;p&gt;以下表格中，*$UMax_w$&lt;em&gt;表示无符号数的最大值，&lt;/em&gt;$TMin_w$&lt;em&gt;表示有符号数的最小值，&lt;/em&gt;$TMax_w$*表示有符号数的最大值&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121162922727.png"
width="1135"
height="389"
srcset="https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121162922727_hu_f0d7a66a7627dc92.png 480w, https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121162922727_hu_a40306c4c5482202.png 1024w"
loading="lazy"
alt="number"
class="gallery-image"
data-flex-grow="291"
data-flex-basis="700px"
&gt;&lt;/p&gt;
&lt;h2 id="数据比较"&gt;数据比较&lt;/h2&gt;
&lt;p&gt;由于 C 语言对同时包含有符号和无符号数表达式的这种处理方式，出现了一些奇特的行为。当执行一个运算时，如果它的一个运算数是有符号的而另一个是无符号的，那么 C 语言会&lt;strong&gt;隐式地将有符号参数强制类型转换为无符号数，并假设这两个数都是非负的&lt;/strong&gt;，来执行这个运算。就像我们将要看到的，这种方法对于标准的算术运算来说并无多大差异，但是对于像 &amp;lt; 和 &amp;gt; 这样的关系运算符来说，它会导致非直观的结果。考虑比较式 -1&amp;lt;0U。因为第二个运算数是无符号的，第一个运算数就会被隐式地转换为无符号数，因此表达式就等价于4294967295U&amp;lt;0U，这个答案显然是错的。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121164052559.png"
width="1141"
height="356"
srcset="https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121164052559_hu_b65185eab555ed7d.png 480w, https://www.lyrical-wander.cn/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%A4%84%E7%90%86/image-20241121164052559_hu_ca40519b73789a1.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="320"
data-flex-basis="769px"
&gt;&lt;/p&gt;</description></item><item><title>程序在系统中是如何运行的</title><link>https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/</link><pubDate>Tue, 12 Nov 2024 14:20:00 +0800</pubDate><guid>https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/</guid><description>&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image.jpg" alt="Featured image of post 程序在系统中是如何运行的" /&gt;&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;今天来读一下《深入理解计算机系统》，在这篇文章中，笔者将这本书的内容做一些大概的总结与归纳，如果你希望学习：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何避免由计算机表示数字的方式导致的奇怪的数字错误&lt;/li&gt;
&lt;li&gt;怎样通过一些小聪明小窍门来优化你的C代码，以充分利用现代处理器和存储器系统的设计&lt;/li&gt;
&lt;li&gt;编译器是如何实现过程调用的&lt;/li&gt;
&lt;li&gt;如何避免缓冲区溢出错误带来的安全漏洞&lt;/li&gt;
&lt;li&gt;如何识别和避免链接时那些令人讨厌的错误&lt;/li&gt;
&lt;li&gt;如何编写自己的Unix shell，自己的动态存储分配包，甚至是自己的Web服务器&lt;/li&gt;
&lt;li&gt;了解并发带来的希望与陷阱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;强烈推荐去看一下原书。本文只能是对书本中笔者认为重要或者难以理解与记忆的地方做总结与推导，甚至部分内容加入了笔者自己的理解，理解不一定正确，仅供大家参考。&lt;/p&gt;
&lt;p&gt;最后，再一次推荐大家去看一遍原书，我们要直接对原著围观，尽可能不围观他人的围观，这中间难免有信息的减损甚至误导。&lt;/p&gt;
&lt;p&gt;我们将通过跟踪hello程序的生命周期，来开始对系统的学习。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hello world!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;当然，首先我们要对要出现的关键概念，专业术语与组成部分做一下介绍。&lt;/p&gt;
&lt;h2 id="信息就是位上下文"&gt;信息就是位+上下文&lt;/h2&gt;
&lt;p&gt;hello程序的生命周期是从一个源程序（源文件）开始的，源程序实际上就是一个值由0和1组成的位序列。大部分的现代系统都使用ASCII标准来表示文本。像hello.c这种只由ASCII字符构成的文件称为文本文件，其它的则称之为二进制文件，如：如经过汇编器后将汇编语言（文本）翻译成机器语言（二进制）后的可重定位目标程序。&lt;/p&gt;
&lt;h2 id="编译系统"&gt;编译系统&lt;/h2&gt;
&lt;h3 id="gnu"&gt;GNU&lt;/h3&gt;
&lt;p&gt;GNU（GNU&amp;rsquo;s Not Unix），是1984年由Richard Stallman发起的免税慈善项目，旨在开发一个完整的类Unix的系统，其源代码可不受限制地传播与修改。GNU已经开发出了一个包含出Unix所有的主要部件，出了内核（由Linux项目独立发展）。GCC便是其中一个有用的工具之一。&lt;/p&gt;
&lt;p&gt;GCC编译器可以编译包括C语言，C++，Objective-C，Python等多种语言。到现在也是Linux下非常重要的C语言编译工具。&lt;/p&gt;
&lt;h3 id="编译过程"&gt;编译过程&lt;/h3&gt;
&lt;p&gt;在Unix系统上，从源文件到目标文件的转化是由编译器驱动程序完成的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gcc -o hello hello.c
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这个编译系统由四个阶段组成：预处理器、编译器、汇编器、链接器。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112145538969.png"
width="1195"
height="217"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112145538969_hu_a7e41996c702f9fd.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112145538969_hu_4c5a98220eae5e03.png 1024w"
loading="lazy"
alt="image-20241112145538969"
class="gallery-image"
data-flex-grow="550"
data-flex-basis="1321px"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;预处理阶段&lt;/strong&gt;：预处理器（cpp）根据以字符#开头的命令，修改原始的C程序，比如hello程序中的&lt;code&gt;#include&amp;lt;stdio.h&amp;gt;&lt;/code&gt;会告诉预处理器读取系统头文件stdio.h的内容，并直接插入到程序文本中，结果就得到了另一个C程序，通常以&lt;code&gt;.i&lt;/code&gt;作为文件扩展名&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编译阶段&lt;/strong&gt;：编译器（cc1）将文本文件 hello.i 翻译成文本文件 hello.s，它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;汇编阶段&lt;/strong&gt;：汇编器（as）将 hello.s 翻译成机器语言指令，把这些指令打包成一种叫做可重定位目标程序（relocatable object program）的格式，并将结果保存在目标文件 hello.o 中。hello.o 文件是一个二进制文件，它的字节编码是机器语言指令而不是字符。 ==从此时开始，文件内容格式便从ASCII字符文本转换成二进制==&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链接阶段&lt;/strong&gt;：hello 程序调用了 printf 函数，它是每个 C 编译器都会提供的标准 C 库中的一个函数。printf 函数存在于一个名为 printf.o 的单独的预编译好了的目标文件中，而这个文件必须以某种方式合并到我们的 hello.o 程序中。链接器（ld）就负责处理这种合并。结果就得到 hello 文件，它是一个可执行目标文件（或者简称为可执行文件），可以被加载到内存中，由系统执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="处理器读并解释存储在存储器中的指令"&gt;处理器读并解释存储在存储器中的指令&lt;/h2&gt;
&lt;h3 id="了解系统的硬件组成"&gt;了解系统的硬件组成&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112150918278.png"
width="906"
height="710"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112150918278_hu_545d498f18246a0f.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112150918278_hu_74cac899088d07f1.png 1024w"
loading="lazy"
alt="image-20241112150918278"
class="gallery-image"
data-flex-grow="127"
data-flex-basis="306px"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;总线&lt;/strong&gt;：贯穿整个系统的是一组电子管道，称做总线，它携带信息字节并负责在各个部件间传递。通常总线被设计成传送定长的字节块，也就是字（word）。字中的字节数（即字长）是一个基本的系统参数，在各个系统中的情况都不尽相同。现在的大多数机器字长有的是 4 个字节（32 位），有的是 8 个字节（64 位）。&lt;/li&gt;
&lt;li&gt;I/O&lt;strong&gt;设备&lt;/strong&gt;：输入 / 输出（I/O）设备是系统与外部世界的联系通道。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主存&lt;/strong&gt;：主存是一个临时存储设备，在处理器执行程序时，用来存放程序和程序处理的数据。从物理上来说，主存是由一组动态随机存取存储器（DRAM）芯片组成的。从逻辑上来说，存储器是一个线性的字节数组，每个字节都有其唯一的地址（即数组索引），这些地址是从零开始的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理器&lt;/strong&gt;：中央处理单元（CPU），简称处理器，是解释（或执行）存储在主存中指令的引擎。处理器的核心是一个字长的存储设备（或寄存器），称为程序计数器（PC）。在任何时刻，PC 都指向主存中的某条机器语言指令（即含有该条指令的地址）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="运行hello程序"&gt;运行hello程序&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;初始时，外壳程序（shell）执行它的指令，等待我们输入一个命令。当我们在键盘上输入字符串“./hello”后，外壳程序将字符逐一读入寄存器，再把它存放到存储器中，如下图所示。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152309541.png"
width="879"
height="693"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152309541_hu_84f36450eee6ee28.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152309541_hu_bb8f4500c160cf7c.png 1024w"
loading="lazy"
alt="image-20241112152309541"
class="gallery-image"
data-flex-grow="126"
data-flex-basis="304px"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当我们在键盘上敲回车键时，外壳程序就知道我们已经结束了命令的输入。然后外壳执行一系列指令来加载可执行的 hello 文件，将 hello 目标文件中的代码和数据从磁盘复制到主存。数据包括最终会被输出的字符串“hello, world\n”。利用直接存储器存取（DMA）的技术，数据可以不通过处理器而直接从磁盘到达主存。这个步骤如下图所示。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152615817.png"
width="993"
height="627"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152615817_hu_4c802e337c7844a6.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152615817_hu_1e9f7773dae4d845.png 1024w"
loading="lazy"
alt="image-20241112152615817"
class="gallery-image"
data-flex-grow="158"
data-flex-basis="380px"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一旦目标文件 hello 中的代码和数据被加载到主存，处理器就开始执行 hello 程序的main 程序中的机器语言指令。这些指令将“hello, world\n”字符串中的字节从主存复制到寄存器文件，再从寄存器文件中复制到显示设备，最终显示在屏幕上。这个步骤如下图 所示。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152854683.png"
width="970"
height="605"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152854683_hu_6476472ad7a42cff.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241112152854683_hu_9b0417280230d69a.png 1024w"
loading="lazy"
alt="image-20241112152854683"
class="gallery-image"
data-flex-grow="160"
data-flex-basis="384px"
&gt;&lt;/p&gt;
&lt;p&gt;从这张图中可以看到，当CPU的工作需要使用到某些资源或数据时，从主存调取往往需要费一番周折，从主存出发，经过存储器总线，系统总线，总线接口，才堪堪到达寄存器，而这段时间CPU是无法进行下一步工作的，CPU的算力被白白浪费了。&lt;/p&gt;
&lt;h2 id="高速缓存"&gt;高速缓存&lt;/h2&gt;
&lt;p&gt;高速缓存就是&lt;code&gt;CPU&lt;/code&gt;与&lt;code&gt;主存&lt;/code&gt;（我们经常口头说的内存）之间的一个缓冲区，因为主存的读写速度要比CPU的运行速度低几个数量级，因此，每当CPU开始需要从主存中读写数据时，都需要停下来等待主存完成读写，这样CPU的算力被白白浪费了。因此高速缓存诞生了，它的存储空间没有主存大，但是运行速度比主存快，它的运行速度没有CPU内的寄存器快，但是存储空间比寄存器大，如下所示：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;存储空间&lt;/th&gt;
&lt;th&gt;读写速度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;寄存器&lt;/td&gt;
&lt;td&gt;小&lt;/td&gt;
&lt;td&gt;快&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;高速缓存&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主存&lt;/td&gt;
&lt;td&gt;大&lt;/td&gt;
&lt;td&gt;慢&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;从数学的角度来讲，在寄存器与主存中间插入了以及高速缓存，使读写速度的曲线更为平稳光滑，虽然高速缓存的速度依然没有寄存器快，但是介于成本（毕竟运行速度与存储空间在当下的物理材料与技术特性中依然属于不可兼得的）与运行效率的考量，高速缓存是最具性价比的。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121093737248.png"
width="902"
height="411"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121093737248_hu_a32710871d4b7c14.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121093737248_hu_7cf32870c66f54cf.png 1024w"
loading="lazy"
alt="cache"
class="gallery-image"
data-flex-grow="219"
data-flex-basis="526px"
&gt;&lt;/p&gt;
&lt;p&gt;首先了解什么叫&lt;code&gt;局部性原理&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;局部性原理的概念：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;局部性原理&lt;/strong&gt;是计算机科学中的一个重要概念，它描述了一个现象：==在一段时间内，程序倾向于仅使用一部分代码或数据。==这种倾向性分为两类：&lt;strong&gt;时间局部性&lt;/strong&gt;和&lt;strong&gt;空间局部性&lt;/strong&gt;。时间局部性指的是如果某个数据项被访问，那么它不久后可能会被再次访问。空间局部性则是指如果访问了某个存储单元，那么其附近的存储单元也很可能不久后会被访问&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;利用局部性原理，可能经常访问的数据提前调入高速缓存中，如此CPU在访问下一次访问时，大概率可以直接从高速缓存中进行访问，这样运行速度将大大提升。&lt;/p&gt;
&lt;p&gt;在编程实践中，理解局部性原理可以帮助开发者优化代码性能。例如，在处理数组或循环结构时，考虑数据的存储和访问模式可以显著影响程序的执行效率。数组通常在内存中顺序存储，因此按行访问数组元素通常比按列访问更高效，因为这符合空间局部性原理。&lt;/p&gt;
&lt;h2 id="存储设备的层次模型"&gt;存储设备的层次模型&lt;/h2&gt;
&lt;p&gt;这个模型比较熟悉了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121103500510.png"
width="1105"
height="636"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121103500510_hu_19088500195f77ac.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121103500510_hu_d5be0c1e03f89c30.png 1024w"
loading="lazy"
alt="存储设备层次模型"
class="gallery-image"
data-flex-grow="173"
data-flex-basis="416px"
&gt;&lt;/p&gt;
&lt;h2 id="操作系统管理硬件"&gt;操作系统管理硬件&lt;/h2&gt;
&lt;p&gt;应用程序并不直接接触硬件，操作系统介于软件与硬件之间。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121095442355.png"
width="691"
height="160"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121095442355_hu_e856f28621b4d3a3.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121095442355_hu_6eaaa68e57a260be.png 1024w"
loading="lazy"
alt="operation system level"
class="gallery-image"
data-flex-grow="431"
data-flex-basis="1036px"
&gt;&lt;/p&gt;
&lt;p&gt;操作系统为程序与硬件之间提供了一个虚拟接口，操作系统有两个基本功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;防止硬件被失控的应用程序滥用&lt;/li&gt;
&lt;li&gt;向应用程序提供简单一致的机制来控制复杂又大相径庭的硬件设备&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;操作系统通过将复杂的硬件接口抽象成几个基本概念（进程，虚拟存储器，文件）供应用程序使用，以此来完成上面两个功能。&lt;/p&gt;
&lt;p&gt;​ &lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121095521662.png"
width="650"
height="290"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121095521662_hu_5cee7b0c3bad1219.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121095521662_hu_5442a1085579f45d.png 1024w"
loading="lazy"
alt="abstract hardware interface"
class="gallery-image"
data-flex-grow="224"
data-flex-basis="537px"
&gt;&lt;/p&gt;
&lt;p&gt;操作系统将I/O设备抽象成文件，将主存+I/O设备抽象成虚拟存储器，将处理器+主存+I/O设备抽象成进程。&lt;/p&gt;
&lt;h3 id="文件"&gt;文件&lt;/h3&gt;
&lt;p&gt;文件就是字节序列，每一个I/O设备，磁盘，键盘，甚至是网络，都可以看作是一个文件，I/O设备的输入输出，其实就可以看作是对文件的读写操作。这样对程序员来说是方便的，你无需了解磁盘的各种技术，就可以使用磁盘的数据，因此，同一个程序可以在使用了不同磁盘技术的不同系统上运行。&lt;/p&gt;
&lt;h3 id="虚拟存储器"&gt;虚拟存储器&lt;/h3&gt;
&lt;p&gt;虚拟存储器是一个抽象概念，它为每一个进程提供了一种抽象，让进程以为自己独占内存，让每一个进程看到的是一致的内存，这对程序员是方便的，假设如果没有虚拟内存储器技术，程序员在编写程序时，需要提前对内存进行操作，因为你不知道你现在使用的内存地址是否被其它进程占用了。现在，对内存的操作交给了虚拟存储器，程序员只需编写相对地址，由操作系统自动分配实际的内存地址。以下是Linux进程的虚拟地址空间&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121101023478.png"
width="811"
height="672"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121101023478_hu_a24bf0336ee5da9c.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121101023478_hu_e2c38cc3d4d88b23.png 1024w"
loading="lazy"
alt="virtual address space"
class="gallery-image"
data-flex-grow="120"
data-flex-basis="289px"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;程序代码和数据&lt;/strong&gt;：对于所有进程来说，代码是从同一固定地址开始的，然后是全局变量与相对的数据位置。代码和数据是按照可执行目标文件的内容开始初始化的，当然还用这里还有链接与加载的事。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;堆&lt;/strong&gt;：下一块是运行时堆，与代码与数据区不同，代码与数据区在进程一开始时就确定好了大小，而堆的空间会随着程序调用&lt;code&gt;malloc&lt;/code&gt;或&lt;code&gt;free&lt;/code&gt;等操作，而增大或缩小空间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享库&lt;/strong&gt;：大约在地址空间的中间部分是一块用来存放像 C 标准库和数学库这样共享库的代码和数据的区域。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;栈&lt;/strong&gt;：位于用户虚拟地址空间顶部的是用户栈，编译器用它来实现函数调用。和堆一样，用户栈在程序执行期间可以动态地扩展和收缩。特别是每次我们调用一个函数时，栈就会增长 ；从一个函数返回时，栈就会收缩。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核虚拟存储器&lt;/strong&gt;：内核总是驻留在内存中，是操作系统的一部分。地址空间顶部的区域是为内核保留的，不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="进程"&gt;进程&lt;/h3&gt;
&lt;p&gt;当程序在操作系统上运行时，操作系统会提供一个假象，即系统上好像只有这一个程序在运行，它可以调用处理器，主存和I/O设备。&lt;/p&gt;
&lt;p&gt;进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程，而每个进程都好像在独占地使用硬件。而并发运行，则是说一个进程的指令和另一个进程的指令是交错执行的。在系统中，正在运行的进程往往是比CPU的个数还要多的，因此，无论是单核处理器还是多核处理器，都具备并发运行的能力。而并发运行是通过处理器在进程之间切换来实现的，操作系统实现这种交错机制称之为“上下文切换”。&lt;/p&gt;
&lt;p&gt;操作系统保持跟踪进程运行所需的所有状态信息。这种状态，也就是&lt;strong&gt;上下文&lt;/strong&gt;，它包括许多信息，例如 PC 和寄存器文件的当前值，以及主存的内容。&lt;/p&gt;
&lt;p&gt;当操作系统决定将处理器的控制权交给另外一个进程时，就会进行&lt;strong&gt;上下文切换&lt;/strong&gt;，保存当前进程的上下文，恢复新进程的上下文，将处理器控制权交给新进程。&lt;/p&gt;
&lt;p&gt;值得注意的是，在Linux中，上下文切换的读写数据在&lt;strong&gt;内核空间&lt;/strong&gt;发生，“内核空间”与“用户空间”是Linux类系统的重要概念。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121103135969.png"
width="890"
height="335"
srcset="https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121103135969_hu_ee9df820e33e46a0.png 480w, https://www.lyrical-wander.cn/p/%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E7%9A%84/image-20241121103135969_hu_c7b0df3f77248937.png 1024w"
loading="lazy"
alt="content switch"
class="gallery-image"
data-flex-grow="265"
data-flex-basis="637px"
&gt;&lt;/p&gt;
&lt;h2 id="小结"&gt;小结&lt;/h2&gt;
&lt;p&gt;计算机系统是由硬件和系统软件组成的，它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位，它们依据上下文有不同的解释方式。程序被其他程序翻译成不同的形式，开始时是 ASCII 文本，然后被编译器和链接器翻译成二进制可执行文件。&lt;/p&gt;
&lt;p&gt;处理器读取并解释存放在主存里的二进制指令。因为计算机把大量的时间用于存储器、I/O设备和 CPU 寄存器之间复制数据，所以将系统中的存储设备划分成层次结构—CPU 寄存器在顶部，接着是多层的硬件高速缓存存储器、DRAM 主存和磁盘存储器。在层次模型中，位于更高层的存储设备比低层的存储设备要更快，单位比特开销也更高。层次结构中较高层次存储设备可以作为较低层次设备的高速缓存。通过理解和运用这种存储层次结构的知识，程序员可以优化C 程序的性能。&lt;/p&gt;
&lt;p&gt;操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象 ：1）文件是对 I/O设备的抽象 ；2）虚拟存储器是对主存和磁盘的抽象 ；3）进程是对处理器、主存和 I/O 设备的抽象。&lt;/p&gt;</description></item></channel></rss>