2 lines
6.0 KiB
JavaScript
2 lines
6.0 KiB
JavaScript
import{_ as e,c as l,o,a4 as i}from"./chunks/framework.DtvhUNIn.js";const f=JSON.parse('{"title":"调试理论","description":"","frontmatter":{},"headers":[],"relativePath":"技术资源汇总(杭电支持版)/3.编程思维体系构建/3.4.7.1.1调试理论.md","filePath":"技术资源汇总(杭电支持版)/3.编程思维体系构建/3.4.7.1.1调试理论.md"}'),a={name:"技术资源汇总(杭电支持版)/3.编程思维体系构建/3.4.7.1.1调试理论.md"},r=i('<h1 id="调试理论" tabindex="-1">调试理论 <a class="header-anchor" href="#调试理论" aria-label="Permalink to "调试理论""></a></h1><div class="warning custom-block"><p class="custom-block-title">🌲 调试公理</p><ul><li><p>The machine is always right. (机器永远是对的)</p><ul><li>Corollary: If the program does not produce the desired output, it is the programmer's fault.</li></ul></li><li><p>Every line of untested code is always wrong. (未测试代码永远是错的)</p><ul><li>Corollary: Mistakes are likely to appear in the "must-be-correct" code.</li></ul></li></ul><p>这两条公理的意思是:抱怨是没有用的,接受代码有 bug 的现实,耐心调试.</p></div><div class="warning custom-block"><p class="custom-block-title">😋 如何调试</p><ul><li><p>不要使用"目光调试法", 要思考如何用正确的工具和方法帮助调试</p><ul><li>程序设计课上盯着几十行的程序,你或许还能在大脑中像 NEMU 那样模拟程序的执行过程; 但程序规模大了之后,很快你就会放弃的:你的大脑不可能模拟得了一个巨大的状态机</li><li>我们学习计算机是为了学习计算机的工作原理,而不是学习如何像计算机那样机械地工作</li></ul></li><li><p>使用 <code>assert()</code> 设置检查点,拦截非预期情况</p><ul><li>例如 <code>assert(p != NULL)</code> 就可以拦截由空指针解引用引起的段错误</li></ul></li><li><p>结合对程序执行行为的理解,使用 <code>printf()</code> 查看程序执行的情况 (注意字符串要换行)</p><ul><li><code>printf()</code> 输出任意信息可以检查代码可达性:输出了相应信息,当且仅当相应的代码块被执行</li><li><code>printf()</code> 输出变量的值,可以检查其变化过程与原因</li></ul></li><li><p>使用 GDB 观察程序的任意状态和行为</p><ul><li>打印变量,断点,监视点,函数调用栈...</li></ul></li></ul></div><p><img src="https://cdn.xyxsw.site/boxcnaqLMfwqNMTcYEPuF3vFjqg.png" alt=""></p><h2 id="什么是调试理论" tabindex="-1">什么是调试理论 <a class="header-anchor" href="#什么是调试理论" aria-label="Permalink to "什么是调试理论""></a></h2><p>如果我们能判定任意程序状态的正确性,那么给定一个 failure,我们可以通过二分查找定位到第一个 error 的状态,此时的代码就是 fault (bug)。</p><h2 id="正确的方法-理解程序的执行过程-弄清楚到底为何导致了-bug" tabindex="-1">正确的方法:理解程序的执行过程,弄清楚到底为何导致了 bug <a class="header-anchor" href="#正确的方法-理解程序的执行过程-弄清楚到底为何导致了-bug" aria-label="Permalink to "正确的方法:理解程序的执行过程,弄清楚到底为何导致了 bug""></a></h2><ul><li><p><code>ssh</code>:使用 <code>-v</code> 选项检查日志</p></li><li><p><code>gcc</code>:使用 <code>-v</code> 选项打印各种过程</p></li><li><p><code>make</code>:使用 <code>-n</code> 选项查看完整命令</p><ul><li><code>make -nB | grep -ve '^\\(echo\\|mkdir\\)'</code> 可以查看完整编译 nemu 的编译过程</li></ul></li></ul><p>各个工具普遍提供调试功能,帮助用户/开发者了解程序的行为</p><h2 id="错误概念" tabindex="-1">错误概念 <a class="header-anchor" href="#错误概念" aria-label="Permalink to "错误概念""></a></h2><p>我们来简单梳理一下段错误发生的原因。首先,机器永远是对的。如果程序出了错,先怀疑自己的代码有 bug . 比如由于你的疏忽,你编写了 <code>if (p = NULL)</code> 这样的代码。但执行到这行代码的时候,也只是 <code>p</code> 被赋值成 <code>NULL</code>, 程序还会往下执行。然而等到将来对 <code>p</code> 进行了解引用的时候,才会触发段错误,程序彻底崩溃。</p><p>我们可以从上面的这个例子中抽象出一些软件工程相关的概念:</p><ul><li>Fault: 实现错误的代码,例如 <code>if (p = NULL)</code></li><li>Error: 程序执行时不符合预期的状态,例如 <code>p</code> 被错误地赋值成 <code>NULL</code></li><li>Failure: 能直接观测到的错误,例如程序触发了段错误</li></ul><p>调试其实就是从观测到的 failure 一步一步回溯寻找 fault 的过程,找到了 fault 之后,我们就很快知道应该如何修改错误的代码了。但从上面的例子也可以看出,调试之所以不容易,恰恰是因为:</p><ul><li>fault 不一定马上触发 error</li><li>触发了 error 也不一定马上转变成可观测的 failure</li><li>error 会像滚雪球一般越积越多,当我们观测到 failure 的时候,其实已经距离 fault 非常遥远了</li></ul><p>理解了这些原因之后,我们就可以制定相应的策略了:</p><ul><li>尽可能把 fault 转变成 error. 这其实就是测试做的事情,所以我们在上一节中加入了表达式生成器的内容,来帮助大家进行测试,后面的实验内容也会提供丰富的测试用例。但并不是有了测试用例就能把所有 fault 都转变成 error 了,因为这取决于测试的覆盖度。要设计出一套全覆盖的测试并不是一件简单的事情,越是复杂的系统,全覆盖的测试就越难设计。但是,如何提高测试的覆盖度,是学术界一直以来都在关注的问题。</li></ul>',17),t=[r];function c(d,u,p,s,n,h){return o(),l("div",null,t)}const m=e(a,[["render",c]]);export{f as __pageData,m as default};
|