admin管理员组

文章数量:1599276

一篇blog,显得略长,本文对应第1-4章,第5-8章请参考Part 2。

宏观策略

Bug追踪系统

Issue Tracking System,可供选择的工具:GitHub、GitLab、JIRA、Bugzilla、Launchpad、OTRS、Redmine、Trac。

使用Bug追踪系统的好处:

  • 可看见调试工作所取得的进展;
  • 可对软件的发行进行追踪与规划;
  • 协助确定各种工作项(work item)之间的优先次序;
  • 协助把常见的事务及其解决方案整理成文档;
  • 防止遗漏某些问题;
  • 可以自动生成发行说明,即release note;
  • 可用作知识库,对软件中的缺陷进行估量及反思,并从中总结经验。

有些公司规定:在修改代码之前,必须先指明这次修改所涉及的事务(对应英文是Issue,可理解为Bug)。

SSCCE:每一项事务都能够精确地描述问题的重现方式。最好能在其中给出一个简短(short)、自足(self-contained)且正确(correct,可正确编译并运行)的例子(example)。参考SSCCE。

错误报告必须具备如下几方面内容:

  • Precise Title:精准的标题,方便在事务汇总报告中迅速找出某个Bug;
  • Severity:严重程度,用于判断优先级。如:数据丢失有关是严重级别;
  • Priority:优先级,一般由开发者或PM(项目主管,或产品经理)来设置;
  • Stakeholder:利益相关者,可帮助团队获知与该事务有关信息,并帮助产品拥有者来决定事务的优先次序;
  • Environment:情境描述,对于某些难以捕获的Bug,可提供线索,方便重现Bug。

Google

不要用百度,用Google。Chrome搜索技巧。

Black Duck Open Hub CodeSearch:专门用来搜索代码的引擎,该服务已停止运营,可考虑使用SearchCode。

善用StackOverflow。

你不太可能是第一个遇到某问题的人。如果万一真的遇上,大概率是以下几种情形之一:

  • 你使用的搜索关键词不够精确;
  • 你使用的软件太冷门,如付费的;
  • 你对问题的判断有偏差,方式不对等。

如果实在找不到答案,可考虑在GitHub或StackOverflow新增问题。提问是一门艺术。最好要遵循上面提到的SSCCE原则。

在线IDE,很多。

确保前置条件与后置条件都能够得到满足

入口点:entry point,前置条件,precondition,指的是程序在即将执行例程时所具备的状态,以及传递给该例程的输入值。如果前置条件得不到满足,那说明用来设置这些前置条件的代码里面有错误;

出口点:exit poit,后置条件,postcondition,指的是程序执行完例程之后的状态及其返回值。若是后置条件得不到满足,则说明该例程本身有问题。

为了判断前置条件是否得到满足,应该仔细检查算法的参数,包括传入的参数值,调用方法时所针对的对象,以及可疑代码所使用的全局状态。注意点:

  • 找出那些本来不应为null,但实际上却为null的值;
  • 调用数学函数时,确保传入的值位于该函数的定义域之内;
  • 查看对象、结构体与数组的内部细节,确保其内容符合要求。可查出无效指针;
  • 检查变量的取值是否在合理范围之内。如果变量具有可疑取值,那通常表明它还没有初始化;
  • 检查传给例程的数据结构是否正确;

出口点检查要点:

  • 计算出来的结果看上去合理吗?有没有处在预期的范围之内?
  • 如果结果合理,而且位于预期的范围之内,那么实际的值是否正确?我们可以通过手算来演练相应的代码,以验证计算机的执行结果是否正确,也可以将执行结果与已知的正确值相对比,或是采用其他工具或方法来进行验算;
  • 例程的副作用是否符合预期?可疑代码所接触到的其他数据是否遭到破坏或拥有不正确的取值?有些算法在遍历数据结构时,会把一些维护其工作所用的信息记录在数据结构中,对于这些算法来说,尤其应该进行这样的检查;
  • 算法所获得的资源,如文件句柄及锁,有没有正确地释放?

必须要亲自验证(verify),而不能想当然地接受假设(assume)。

从下往上与从上往下

要想确定问题的来源,通常有两种办法:

  • 从下往上:从问题的具体表现入手,向上追查其来源;
  • 从上往下:从应用程序或系统的顶层入手,逐步向下探查,直至找到其根源。

如果问题表现得很明确,则应该从发生问题的地方入手,向上追查Bug。分三种情况:

  • 程序崩溃:可用调试器来运行程序,或取得内存转储信息。可通过0xBAADF00D(代表bad food)这样的特殊字节值来找出尚未初始化的变量。
  • 程序冻结:freeze,
  • 错误消息:

如果无法确定与故障有关的代码到底在哪里,就应该从顶层系统开始,逐步向下查找故障原因。这种故障通常属于系统的涌现属性(emergent property),也就是无法与某个具体部分直接对应起来的属性,如性能、安全、可靠性问题等。

由上而下地排查错误时,需要把整个程序分成多个部分,然后分别判断每一部分在引发当前故障的各种因素中可能占多大的比例。

在能够正常运作的系统与发生故障的系统之间寻找差别

之所以能根据新旧系统间的差距来进行调试,其原因在于:尽管各人所经历的问题有所不同,但计算机的底层运作方式却是十分确定的,也就是说,同样的输入会产生同样的输出。

工具:DTrace和SystemTap;追踪对操作系统的调用(strace、truss、Procmon)、对动态链接库的调用(ltrace、Procmon)、网络包(tcpdump、Wireshark)、SQL数据库调用。

在两个系统的输入文件之间进行对比,如果它们都比较庞大并且离得比较远,那可以考虑对比它们的MD5校验和。

ldd:Unix系统下的命令,用于打印程序或者库文件所依赖的共享库列表。

在进行更深层次的探查之前,应该先考虑一下有没有其他因素会影响程序的执行情况,环境变量就是这样一个容易忽视的因素,即便是没有特权的用户,也依然可以通过设置环境变量来破坏程序的正常执行。

如果正常系统和故障系统的区别位于源代码中,对这两个版本之间的历次修改进行二分搜索,以确定问题所在。要熟悉git bisect命令的使用。

comm:在排好顺序的两个集合中找寻不同的元素。

使用软件自身的调试机制

调试机制的好处:

  • 可以通过禁用后台执行或多线程执行等特性来简化程序的调试工作;
  • 可以有选择地执行其中某一部分功能,以便通过测试用例来精确地再现相关的故障;
  • 程序可以给我们提供与性能有关的报表及其他信息;
  • 程序可以把更多的信息记录在日志文件中。

调试机制也可以帮助排查性能方面的问题,如MySQL explain。

主机投递电子邮件消息失败,调用Postfix的sendmail命令,以-v选项来开启详细输出模式,并以-M选项来指定那条消息的标识符。

用多种工具构建软件,并将其放在不同的环境下执行

对于C和C++这样较为接近硬件的语言来说,底层的处理器架构会对程序的行为造成影响。

几种处理器架构:

  • Intel x86:桌面市场,Windows系统;
  • ARM:移动市场,Android;
  • SPARC、PowerPC:字节序;
  • VAX:对空指针的解引用。

x86架构与ARM架构在处理未对齐的内存访问及内存布局时依然有所区别。如果在奇数内存地址处访问两字节的值,就有可能令某些ARM架构的CPU出错,或令CPU表现出非原子的(non-atomic)行为。在其他架构上面进行未对齐的内存访问,可能会严重影响程序性能。此外,结构体的大小,以及其中各成员距离结构体开始处的偏移量,在这两种架构中也是有区别的,对于老版本的编译器来说,这种区别尤其突出。一个更重要的问题:把代码从32位架构移植到64位架构,或是从一个操作系统移植到另一个操作系统时,长整数(long)及指针值等原始类型所占据的大小可能也会有所改变。

由此可见,把软件放在其他架构或操作系统中运行,可以帮助我们对其进行调试,并检测出移植方面的问题。

在其他执行环境中调试代码,主要有三种方式:

  • 在工作站安装虚拟机软件,并且用虚拟机来运行各种不同的操作系统;
  • 使用小型的廉价计算机。如Raspberry Pi,树莓派。
  • 租用基于云端的主机,并在上面运行你想使用的操作系统。

任务优先级

下列几类问题应该赋予较高的优先级:

  • 数据丢失:数据是最核心的资产;
  • 数据安全:即数据的保密性与完整性,可能会影响软件系统完整性或服务可用性;
  • 服务可用性降低:遭受资金损失;
  • 使用安全:可能导致用户伤亡,财产损失;
  • 程序崩溃或冻结:可能导致数据丢失或服务下线;
  • 代码质量:code hygiene,编译器给出的警告信息和断言失败信息,以及未处理的异常与内存泄漏等问题。

优先级低一些的问题:

  • 对遗留事物的支持:能够支持过时的硬件、API或文件格式;
  • 向后兼容:对老客户的支持;
  • 美观问题:UI;
  • 使用临时解决方案的问题:不能一直放着;
  • 少有人用的特性:做下线处理,删除这部分代码,减少项目工程复杂度;

通用的方法

相信自己能够把问题调试好

你的心理状态会对调试的结果造成影响,专家们把这叫做感受到的挑战与自身技能之间的一场对抗

调试时,需要有两个重要的支持:

  • 对数据的访问权,使我们能够访问到所需的全部数据;
  • 功能强大的计算机,它使我们能够对这些数据进行处理。

排除一切干扰,力争进入心流状态。

遇到难题时,带着问题先睡一觉。

调试时需要的四项基础设施:

  • 把健壮的最小测试用例准备好;
  • 对Bug的重现加以自动化;
  • 用脚本来分析日志文件;
  • 了解API或语言特性的实际运作方式。

高效地重现程序中的问题

要想高效地调试程序问题,最关键的就是要能够可靠且方便地重现它。能够复现的问题,基本上就不是什么太难的问题。当然前提是复现不会带来经济损失等等。

最小范例准则:Minimal Example,创建短小的范例或测试用例​,以便重现问题。为了缩短范例的长度,可考虑自上而下与自下而上这两种办法。

tellg:C++函数。用于输入流,返回流中get指针当前的位置;在读取文件时,可返回当前位置在文件流中的偏移量。

二分搜索技术:定位问题根源在哪个文件,类,方法,甚至哪一行的技巧。

修改完代码之后,要能够尽快看到结果

构建工具。

软件自动化应用程序,如适用于网页浏览器的Selenium、适用于Windows的AutoHotkey、适用于OS X的Automator,以及适用于Linux的AutoKey。

要点:

  • 设法在修改代码后尽快看到其结果,以提升调试效率;
  • 配置一套快速的自动化构建及部署流程;
  • 测试软件时,要令其尽快地将故障暴露出来。

将复杂的测试场景自动化

jq,一款在命令行下解析JSON的工具,参考JSON解析神器-jq。

自动化的方式有很多种:

  • 如果是要对处理流程与文件进行编排,则可考虑Unix Shell提供的大量实用工具;
  • curl + jq,可实现用Shell来测试Web服务;
  • 牵涉API访问及状态维护等事宜的复杂场景来说,可求助于功能更为丰富的脚本语言,如:Python、Ruby或Perl。

使自己尽可能多地观察到与调试有关的数据

尽量扩大显示区域,比如外接更多大屏高清显示器。

要点:

  • 如果能够同时看到比较多的数据,那我们就可以更加专注地进行调试,从而找到数据所体现出的模式以及数据之间的相互关系;
  • 尽可能地将显示区域扩至最大;
  • 把相对静态的数据打印到纸上。

考虑对软件进行更新

现在是开源世界。我们的软件使用很多第三方的程序库、数据库、应用服务器、操作系统。说不定遇到的问题,刚好就是这些外部依赖的问题,比如使用的版本过低。

JDK 23已经发布,但可悲的是,90%以上的公司还在使用JDK8,甚至JDK7。

有经验的用户通常会较为谨慎地进行升级。

查看第三方组件的源代码,以了解其用法

为了能够迅速地查询函数或方法,可用ctags或etags程序为代码编制索引(大多数编辑器都支持这两个程序所输出的索引),也可采用IDE来查看代码。与ctags相比,IDE能够更好地处理复杂的语言特性,如重载、覆盖、模板等,而ctags的优势则在于支持的语言数量比较多,5.8版本支持41种语言。在源代码的目录下运行命令ctags -R .可给其中的所有文件创建索引。

把自己的代码与这个调试版的第三方程序库相链接,在第三方程序库的代码里面进行单步调试。使用符号调试器(Symbolic Debugger)来检视其中的变量。点评:对Java而言,调试三方库是最常见不过的事情。

迫不得已,才修改源码;三方库发布新版本后,如果还存在相同问题,都需要做修改;修改面及影响面可能会非常大;还要确保不会违背相关的法律条款。

要点:

  • 如果你依赖某个三方组件,那么就应该获取其源代码;
  • 通过查看第三方组件的源代码探寻与第三方API及一些奇怪的错误消息有关的问题;
  • 要和第三方程序库的debug版本相链接;
  • 只有当其他办法都不可行的时候,才需要对第三方的源代码进行修改。

使用专门的监测及测试设备

逻辑分析仪:能够以每秒数百万次的采样率来捕获、存储并分析数字信号。基于USB,能够监测主板上面的所有数字信号,而且还能对组件之间进行通信时所采用的一些高层通信协议进行监测。

CAN:Controller Area Network,控制器局域网。

想调试的代码,以嵌入式软件的形式运行,并且运行该软件的设备又缺乏适当的I/O机制,那么可以考虑采用下面这些技巧来与正在调试的软件进行通信:

  • 如果设备有状态指示灯或能够发出响声,那么可以用特定的闪烁方式或发声方式来表达软件当前的状况;
  • 把输出到日志文件中的信息先保存到非易失性的存储器(non-volatile storage)中(甚至可以保存到外接的U盘中)​,然后将其导入自己的计算机,并对其进行分析。
  • 实现一个简单的串行数据编码器,将数据写入某个尚未使用的I/O针脚,然后对信号进行电平转换,将其转为RS-232标准,并通过串口转USB口的适配器及终端应用程序来在计算机上读取这些数据。
  • 如果设备有网络连接,那么显然可以通过该连接来通信。如果设备缺乏对网络日志或远程shell访问提供支持的软件,那么可以考虑通过HTTP甚至DNS请求来与外界通信。

要点:

  • 逻辑分析器、总线分析器或协议分析器可以帮你锁定接近于硬件层面的问题;
  • 可以通过自制的设备来探查与硬件有关的问题;
  • 可以通过将Wireshark与以太网集线器相结合、使用管理型交换机或进行命令行捕获等办法来监测网络数据包。

使故障更加突出

使用JMeter这样的负载测试(Load Test)或压力测试(Stress Test)工具,把软件推进到一种行为有可能开始出现异常的地步。

要点:

  • 迫使软件去执行那些可疑的路径;
  • 提升某些效果的幅度,令其变得更加突出,以便于我们进行研究;
  • 对软件加压,迫使它走出能够从容应对负载的那种舒适状态;
  • 在版本管理系统中临时创建一个分支,并把所有的修改都放在这个分支上面来做。

从自己的桌面计算机上调试那些不太好用的系统

设备模拟器:Device Emulator,以便通过计算机屏幕和键盘来调试移动App。

软件楔子:Software Shim,以便使用自己计算机中的工具来调试嵌入式代码。

远程访问:TeamViewer,以便能够远程调试客户的计算机。

KVM over IP设备:能够通过IP网络来远程访问服务器的键盘(keyboard)、显示器(video)及鼠标(mouse)。

使调试任务自动化

由于which命令会在路径中的每个元素后面补充一个斜线,因此,本来只包含一个斜线的那个元素,在搜索时就相当于变成双斜线//​,而Windows系统如果遇到这种以双斜线开头的路径,则会触发查找网络驱动器的流程。

脚本能力很重要,不管是Windows系统下的bat脚本,还是Linux系统下的shell脚本。

调试前与调试后都要把程序清理干净

调试时,应该优先关注当前区域中最容易解决的问题。

为了调试某个重大Bug,会阅读很多个类文件、几十个方法,这些代码看起来可能不够整洁甚至有问题,要不要优化或解决问题,需要加以权衡。

找到并修复程序Bug后,不要急着去做其他事情,还有两项任务未完成:

  • 在代码中寻找类似错误,并将其修复;
  • 要把寻找问题时所做的那些修改整理好,分两种情况:
    • 临时改动的,不能提交的代码,予以还原;
    • 方法重命名、重复代码片段提取等优化,或加日志、断言等性质的代码,清理干净并提交上去。

要点:

  • 在开始调试重大Bug前,先要确保代码能够达到一定整洁程度;
  • 调试完毕后,要把调试过程中对代码所做的临时改动还原回去,有用的代码提交到代码库。

把属于同一个类型的所有问题全都修复好

相同的问题会多次出现:

  • 开发者采用相同思路来编程:不局限于个人,开发团队里也存在相同思路;
  • 使用某个很容易被误用的API:没有看文档或API封装有问题;
  • Ctrl + C/V工程师:错误代码多次复制。

搜索能力很重要:Google搜索技巧,Windows可安装Everything加速搜索文件,IDE搜索并替换快捷键,Linux下grep命令必会。

通用的工具与技术

命令行的艺术。

用Unix命令行工具对调试数据进行分析

掌握命令行工具的各种选项及习惯用法

用编辑器对调试程序时所需的数据进行浏览

搜索词根而不搜索原词。

优化工作环境

包括:

  • 确保PATH环境变量中含有你所需的各种目录,使得你要运行的所有程序都能够涵盖在这些目录的范围之内。
  • 对shell和编辑器进行配置,使其可以推断出你要输入的内容,并自动把命令补齐。
  • 对shell界面的命令提示符进行设置,使其可以显示出登录所用的身份、当前的目录以及主机的名称。
  • 把各种命令行编辑按键,设置得与你习惯的编辑器相符。
  • 为常用或是容易打错的命令设置别名或快捷方式。
  • 把与各种实用工具有关的环境变量设置好。
  • 把输入的命令都记录到历史文件里面,方便后续搜索到当时所执行的调试命令。
  • 使shell的路径名扩展机制(即globbing机制,如对*进行扩展)能够涵盖子目录中的文件。

调试时要操作多台计算机的技巧:

  • 无需输入密码,即可登录远程主机(或可执行命令)
  • 给主机设置既简短又好记的别名
  • 设法寻找能够从桌面直接登录远程主机,并在其中执行GUI应用程序的方式

用VCS寻找Bug发生的原因及经过

很多Bug都与软件的改动有关,改动就是一个新版本,一定要确保项目在使用某种VCS。

每一次修改都应该单独提交,并附加有意义的提交消息,如果有可能,还应该链接到对应的Bug ID。

一定要熟知git log、git blame、git show、git diff、git rev-list、git bisect等命令的使用。

监测工具

被动记录系统,passive recording system:collectd、RRDtool;
主动记录系统,active recording system:Nagios。

Nagios功能:具有经过测试的服务检查程序及通知程序(被动与主动的都有)、管理面板、轮询事件数据库、非侵入式的监测计划安排功能、可扩展性、丰富的插件。

全方位的监测:

  • 从最底层的资源开始,要监测每一台主机的健康状况,包括:CPU负载、内存使用量、网络是否可达、正在执行的进程数量、已登录用户数量、可更新的软件、磁盘剩余空间、已打开的文件描述符、已经占用的网络带宽与磁盘带宽、系统日志、安全性以及远程访问。
  • 软件服务:数据库、电子邮件服务器、应用程序服务器、缓存、网络连接、备份、队列、消息传递、软件授权、Web服务器以及目录。
  • 应用程序:
    • 能否正确处理整个流程;
    • 各个部分是否正常,如Web服务、数据库表格、静态网页、交互式Web表单及报告机制
    • 某些关键指标是否正常,如响应延迟、已加入队列和已完成的订单、活跃用户数量、失败交易、发生错误及得到报告的程序故障等。

调试器使用技巧

每种语言都有各自可使用的各种IDE,IDE自带调试器。

命令行调试器:gdb。

编译代码时把符号信息包含进来,以便于调试

单步调试

熟悉常用的IDE的调试快捷键。

要点:

  • 通过单步调试来查看语句的执行顺序及程序的状态;
  • 为了提升调试速度,直接跳过某些与Bug无关的部分,而不用进入其中;
  • 如果发现程序所经过的某个例程有问题,那就给该例程设置断点,重新运行程序,并进入例程中进行单步调试,以求缩小有待排查的范围。

设置代码断点和数据断点

有些Bug,只有当程序沿着特定的路径执行时,才会表现出来。即需要满足特定的前置条件。

还有一类代码断点也很有用,它们令你能够在程序崩溃之前,有机会查看当前的状态。

迷途指针:stray/wild/dangling pointer,流浪/野/悬挂指针,可能会改动内存中任意位置上面的数据。

所幸当前的CPU都提供适当的机制,使得调试器能够通过指定内存位置及对应的变量大小来设置数据断点。每次发生内存写入操作时,CPU就检查正在写入的这个地址,是否位于指定的数据断点范围之内,如果确实位于该范围内,那么就打断程序的执行过程,并将其控制权交给调试器。由于CPU的这种检查机制与普通的内存写入操作是同时发生的,因此通常不会拖慢程序的执行速度。

调试器还能设置带有条件的断点(必须满足一定条件)、带有命中次数的断点(其出现次数必须达到一定值),以及带有过滤器的断点(必须位于特定的线程之内)。这些断点机制在某些情况下很好用,但如果你过份依赖它们,那就说明你应该考虑改用更为强大的调试手段,如精准的测试用例或丰富的日志记录等。

反向调试

一般来说,为了实现逆向执行(反向调试),调试器需要把每条指令对程序状态所做的修改记录下来,然后在逆向执行时,撤销这些修改。

gdb的几条命令:

  • reverse-next:反向执行
  • reverse-step:不会直接把某个例程执行完,而是会进入那个例程里面进行单步执行
  • reverse-continue:可使程序一直反向执行下去,直至遇到断点

反向执行的距离有限度。

查看例程之间的相互调用情况

查看变量及表达式的值,以寻找程序中的错误

了解怎样把调试器连接到正在运行的进程上

很难重现的Bug,生产可重现,开发环境无法重现。对于Java来说,可远程调试。

要点:

  • 把调试器连接到正在运行的进程上面,以便对其进行调试;
  • 通过远程调试机制,对运行在资源受限设备上面的应用程序进行调试。

核心转储信息

运行在Unix系统上面的原生应用程序能够生成核心转储文件,这是系统对应用程序崩溃时的情况所做的内存镜像。

把调试工具设置好

一些思路:

  • 使用图形化的用户界面;
  • 把调试时所用到的一些实用命令保存到文件里面,以后每次开始调试时,都把这份文件执行一遍。

DDD:Data Display Debugger,基于Unix的前端工具,含有完备的图形界面,支持gdb,还支持其他一些运行在命令行界面中的调试器,还可以很好地展示程序的数据结构。

要点:

  • 使用带有图形界面的调试器;
  • 对gdb进行配置,使它能够把输入过的命令保存下来,并设置一套符合自己使用习惯的快捷键;
  • 把常用的命令放在gdb脚本中;
  • 修改完源代码之后,可以不重新启动gdb,而是直接在gdb里面构建程序,以便保留你在这次调试会话中所输入过的命令。

学会查看汇编代码及原始内存

在调试程序时或许会发现:即便是很简单的一行代码,也依然有可能无法像预期的那样运作。要想找到问题的原因,可以去看看程序的底层代码究竟是如何执行的,在这个层面,所看到的内容就是程序真正执行的内容,所看到的每一条机器指令,都对应一项简单的操作,而不会包含一些夹杂着隐晦问题的抽象层。

可帮助发现各种各样的错误:

  • 执行不必要的类型转换
  • 误解操作符的优先级规则
  • 无意中使用重载后的操作符
  • 使用没有配对的括号
  • 使用错误的数值类型
  • 使用不当的多态例程等。

其中某些问题可以通过单步调试来进行深入探查,但代码内联(code inlining)机制或许会使你无法这样做。

不同的处理器架构有不同的汇编语法(assembler syntax),不过大多数汇编指令都是顾名思义的。

局部变量与例程参数,是根据其与栈中某个内存地址之间的偏移量来访问的,这个参照地址,称为frame pointer(帧指针)寄存器(即ebp寄存器或fp寄存器)。

用gdb调试程序时,可以执行display/i$pc命令,以便在每一步的调试过程中显示出反汇编之后的指令,然后可用stepinexti命令来单步调试。可通过info registers命令查看寄存器的值,也可以通过display$r0display$eax命令来持续地显示某个具体的寄存器。

查看数据在内存中的表示形式时,需要注意同一个整数可用两种顺序来保存:

  • Little Endian:小端序,小端在前,先保存最低有效字节(least significant byte)​,然后依次保存权重较高的字节。Intel及大多数ARM CPU,都采用这种格式;
  • Big Endian:大端序,大端在前,也称为网络序格式。只有SPARC及PowerPC等较为少见的CPU架构才会采用这种格式。TCP/IP等协议,Java在读取和写入二进制数据时,也会采用这种格式。

本文标签: 读书笔记方法系统软件Effective