ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

进制转换实战指南:从二进制到十六进制的底层原理与避坑技巧

进制转换实战指南:从二进制到十六进制的底层原理与避坑技巧 进制这个东西很多人第一次接触是在计算机课上老师讲了一堆逢二进一逢八进一然后考试考完就还给老师了。但只要你真正开始碰底层的东西——看二进制文件、调寄存器、分析协议报文、甚至只是想在调试器里改一个字节——进制就会像影子一样跟着你。我见过太多人卡在0x45到底是多大这种问题上也见过有人把十六进制文件整体改反了方向还浑然不觉。这篇内容不打算写成教科书而是把我这些年跟进制打交道时踩过的坑、总结的规律、以及真正用得上的换算技巧一次性讲清楚。不管你是刚学编程的新手还是已经工作几年但进制换算全靠计算器的老手都能从里面找到点有用的东西。1. 进制到底在解决什么问题1.1 从数数这件事说起我们先抛开所有技术术语回到最原始的场景数数。你有一堆石子怎么表示它们的数量最直接的办法是画竖线一根代表一个。但石子多了以后画满一屏也数不清。于是人类发明了进位——每数到一定数量就归为一组用一个新符号表示。这个一定数量就是基数也就是我们说的几进制。十进制之所以成为日常主流纯粹是因为人有十根手指。这不是数学上的必然只是生理上的巧合。如果人类有八根手指我们大概率会用八进制如果有十六根那十六进制就是日常。所以当你觉得二进制反人类的时候要明白它反的只是十进制习惯而不是逻辑。二进制在逻辑上反而是最简洁的——只有两个状态对应电路的通和断对应逻辑的真和假。理解这一点很关键进制不是数字本身而是数字的表示方式。同一个数量用不同进制写出来长得不一样但它代表的多少是固定的。十进制里的 255二进制写成 11111111十六进制写成 FF它们说的是同一件事。1.2 为什么计算机偏偏选了二进制这个问题我被问过无数次。答案其实不复杂物理器件的稳定性。早期计算机尝试过十进制、三进制甚至模拟计算。但电子器件最容易实现的稳定状态就是两种——有电压和无电压或者高电平和低电平。要在一个电路里稳定区分十种不同的电压等级对噪声、温度、器件一致性的要求高得离谱成本也下不来。而两种状态之间的容错空间大得多一个 0.3V 的波动不会把高误判成低。三进制在理论上其实有优势信息密度比二进制高苏联当年也做过三进制计算机。但工程上三态器件的制造和可靠性控制比二态难最终没能成为主流。所以二进制胜出不是因为它数学上最优而是因为它工程上最省心。这个逻辑在技术选型里反复出现理论最优往往输给工程可行。1.3 八进制和十六进制是偷懒的产物既然计算机用二进制为什么还要搞出八进制和十六进制答案就一个字短。一个 32 位的二进制数写出来是 32 个 0 和 1人眼根本没法快速阅读和比对。而 8 和 16 都是 2 的幂2³ 和 2⁴这意味着二进制和它们之间的转换可以按位分组、直接映射不需要做除法。具体来说3 位二进制正好对应 1 位八进制4 位二进制正好对应 1 位十六进制。这种整除关系让转换变得极其机械看一眼就能换不用算。这就是八进制和十六进制存在的全部理由——它们是二进制的压缩写法方便人读写仅此而已。八进制现在用得少了主要出现在 Unix 文件权限比如 chmod 755和一些老系统里十六进制则是绝对主流内存地址、文件内容、颜色值、哈希值到处都是它。2. 四种进制的换算逻辑与手算方法2.1 十进制转其他进制除基取余这是最基础的换算核心就四个字除基取余。把十进制数不断除以目标进制的基数记录每次的余数直到商为 0然后把余数从下往上读出来。举个例子把 217 转成二进制217 ÷ 2 108 ... 余 1 108 ÷ 2 54 ... 余 0 54 ÷ 2 27 ... 余 0 27 ÷ 2 13 ... 余 1 13 ÷ 2 6 ... 余 1 6 ÷ 2 3 ... 余 0 3 ÷ 2 1 ... 余 1 1 ÷ 2 0 ... 余 1余数从下往上读11011001。验证一下128641681 217对的。转十六进制同理只是除数是 16。217 ÷ 16 13 余 913 ÷ 16 0 余 13也就是 D所以结果是 D9。这里要注意余数超过 9 的时候要用字母 A-F 表示10 是 A11 是 B一直到 15 是 F。这个映射必须记牢否则换算时容易卡壳。提示手算的时候除基取余是最不容易出错的方法因为它每一步都是机械的除法。心算快的人可以用凑幂次的方法但对大多数人来说老老实实列竖式更稳。2.2 其他进制转十进制按权展开反过来把任意进制转成十进制用的是按权展开求和。每一位数字乘以基数的位权次方然后全部加起来。位权从右往左数最右边是 0 次方。比如二进制 11011001 转十进制1×2⁷ 1×2⁶ 0×2⁵ 1×2⁴ 1×2³ 0×2² 0×2¹ 1×2⁰ 128 64 0 16 8 0 0 1 217十六进制 D9 转十进制D×16¹ 9×16⁰ 13×16 9 208 9 217这个方法对任何进制都通用。八进制 331 转十进制就是 3×64 3×8 1 192 24 1 217。你会发现它们都指向同一个数这就是前面说的表示方式不同数量相同。2.3 二进制与十六进制的四位一组速换法这是实战中最常用的技巧必须掌握。因为 4 位二进制正好等于 1 位十六进制所以转换时从右往左每 4 位分一组每组直接查表换成十六进制字符。对照表如下建议背下来二进制十六进制二进制十六进制00000100080001110019001021010A001131011B010041100C010151101D011061110E011171111F拿 11011001 举例从右往左分组1101 和 1001。查表1101 是 D1001 是 9合起来就是 D9。整个过程不需要任何除法纯查表几秒钟搞定。反过来十六进制转二进制就是把每一位拆成 4 位。比如 0x454 拆成 01005 拆成 0101合起来 01000101。注意高位补零不能写成 1000101那样位数就错了。注意分组时如果最左边一组不足 4 位要在前面补 0 凑够 4 位。比如二进制 101 转十六进制要看成 0101结果是 5而不是直接当成 101 去查。2.4 八进制的三位一组与它的现实用途八进制的逻辑和十六进制一样只是分组变成 3 位一组。二进制 11011001 转八进制从右往左分 011、011、001最左边补 0查表得 3、3、1结果是 331。八进制现在最实际的用途就是Unix/Linux 文件权限。chmod 755 里的 7、5、5 分别对应读写执行读执行读执行。每一位八进制数展开成 3 位二进制正好对应 rwx 三个权限位。7 111rwx5 101r-x0 000---。理解了这层映射权限设置就不再是死记硬背了。3. 十六进制在真实工作场景里的样子3.1 内存地址与调试器里的十六进制只要你用过调试器就一定见过十六进制地址。为什么地址用十六进制而不是十进制因为内存地址本质上是二进制而十六进制和二进制是 4 位对 1 位的直接映射。一个 32 位地址写成十六进制是 8 个字符写成十进制是 10 个字符而且十六进制能让你一眼看出哪些位是高位、哪些是低位。比如地址 0x7FFF5FBFF8C0你能直接看出它的结构换成十进制 140734799806144你根本看不出任何规律。在调试器里看内存 dump左边是地址中间是十六进制字节右边是 ASCII 字符这个布局是几十年来的标准因为十六进制让字节这个概念变得直观——两个十六进制字符就是一个字节。3.2 文件头识别常见格式的十六进制特征分析文件格式时文件头magic number是最先要看的东西。这些头都是固定的十六进制字节序列认出来就能判断文件类型。下面这张表是我平时查得最多的文件类型十六进制文件头ASCII 表现PNG 图片89 50 4E 47 0D 0A 1A 0A.PNG....JPEG 图片FF D8 FF...PDF 文档25 50 44 46%PDFZIP 压缩包50 4B 03 04PK..ELF 可执行文件7F 45 4C 46.ELFGIF 图片47 49 46 38GIF8用十六进制编辑器打开一个文件看开头几个字节基本就能确定它是什么。这个技能在处理文件后缀名被改错或者下载的二进制程序包找不到类型这类问题时特别有用。我遇到过好几次下载下来的文件没有后缀用十六进制一看文件头是 7F 45 4C 46就知道是 ELF 可执行文件直接 chmod x 就能跑。3.3 十六进制编辑器改字节的必备工具十六进制编辑器hex editor是处理二进制文件的瑞士军刀。它的界面通常分三栏偏移地址、十六进制字节、ASCII 预览。你可以直接定位到某个偏移修改某个字节的值。用十六进制编辑器时有几个坑必须注意。第一修改前一定要备份因为二进制文件没有撤销的通用保障改错了可能整个文件就废了。第二注意字节序多字节数值在文件里可能是小端序低位在前也可能是大端序高位在前改的时候方向搞反了值就完全错了。第三注意文件长度字段很多格式在头部记录了文件长度或校验和你改了内容但没更新这些字段文件就会被认为是损坏的。提示如果你发现十六进制文件全反了大概率是字节序理解错了或者用了错误的编码方式打开。先确认文件本身的字节序约定再动手改。3.4 在十六进制模式下搜索字符串有时候你需要在一个二进制文件里找某个特定的字符串比如配置项moz_require_signingtrue。普通文本搜索工具可能因为编码问题找不到这时候就要用十六进制模式搜索。做法是把目标字符串转成十六进制字节序列然后在编辑器里搜这个序列。ASCII 字符的十六进制很好转每个字符对应一个字节比如m是 6Do是 6Fz是 7A。所以moz就是 6D 6F 7A。搜索时输入这串十六进制就能精确定位。这个技巧在逆向分析、协议调试、配置文件定位时非常实用。因为二进制文件里可能夹杂着各种不可见字符纯文本搜索容易漏掉或被干扰而十六进制搜索是精确匹配字节不会出错。4. 二进制运算与那些容易翻车的细节4.1 二进制加减法其实和十进制一个道理二进制加法规则很简单0000111011110进位。本质上和十进制加法一样只是逢十进一变成了逢二进一。1011 0110 ------ 10001从右往左加1011110写0进101110写0进110110写0进1最后进位得1结果是 10001。减法稍微麻烦一点因为涉及借位。二进制借位是借一当二而不是借一当十。这个区别在手动计算时特别容易搞混。我的建议是减法统一转成加法来做用补码。这样就不用单独处理借位逻辑而且补码本身就是计算机做减法的实际方式。4.2 二进制除法与二进制扩展法二进制除法就是反复的移位减法和十进制长除法结构一样。比如 1100 ÷ 10也就是 12 ÷ 2看被除数前两位 11比 10 大商 1余 1拉下一位 0 得 10正好等于除数商 1余 0拉下一位 0 得 0比除数小商 0。结果是 1106。所谓二进制扩展法我理解是指把二进制数按位权展开来做运算或者转换的方法本质还是按权展开求和那一套。这个方法在处理二进制表示十进制小数时特别重要。比如 0.6 的二进制表示是无限循环的 0.100110011001...因为 0.6 不能表示成 2 的负幂次之和的有限项。这就是为什么浮点数会有精度问题——不是计算机算错了而是有些十进制小数在二进制里根本表示不精确。4.3 位运算与、或、异或、移位位运算是在二进制层面直接操作效率极高在底层编程、加密、压缩、图形处理里到处都是。与两位都是 1 才得 1常用来掩码提取特定位。或|有一位是 1 就得 1常用来置位。异或^两位不同得 1相同得 0常用来翻转或做简单加密。左移整体左移右边补 0相当于乘 2。右移整体右移相当于除以 2对无符号数。举个例子判断一个数的第 3 位是不是 1用n 0b1000结果非零就是 1。把第 3 位置 1用n | 0b1000。翻转第 3 位用n ^ 0b1000。这些操作在写驱动、处理寄存器、优化性能时是家常便饭。4.4 校验码里的二进制以 LRC 为例LRC纵向冗余校验是一种简单的校验方式原理就是把所有字节做异或或者求和得到一个校验字节。计算过程完全在二进制层面进行。比如一串字节 0x01、0x02、0x03做异或校验01 ^ 02 0303 ^ 03 00所以 LRC 是 0x00。接收方收到数据后重新算一遍如果结果对不上就说明传输过程中有字节出错了。这里有个常见疑问需要换算成二进制相加吗答案是异或运算本身就是逐位进行的不需要你先手动转成二进制再算。工具和代码会自动处理位级操作。你只需要理解它的原理是逐位比较具体计算交给程序就行。但如果你要手算验证那就得把每个字节展开成 8 位二进制逐位做异或再合回去。5. 学习进制的实用路径与常见误区5.1 不要死记硬背要建立位权直觉很多人学进制的方式是背转换公式结果一到实际用就懵。我的建议是先建立位权直觉。什么是位权直觉就是看到 2 的幂次能条件反射地反应出数值2⁰12¹22²42³82⁴162⁵322⁶642⁷1282⁸2562⁹5122¹⁰1024。这组数字要熟到像乘法口诀一样。因为二进制转十进制本质上就是把为 1 的位对应的权值加起来。你看到 1010立刻反应出是 8210而不是去列竖式。这个直觉建立起来之后换算速度会快一个数量级。同理十六进制的位权也要熟16⁰116¹1616²25616³4096。看到 0x100 就知道是 256看到 0x1000 就知道是 4096。5.2 用工具验证但别依赖工具计算器和在线转换工具当然要用但前提是你自己能手算验证。我见过有人用计算器算完结果输错了一位自己完全没察觉因为他不具备结果大概是多少的判断力。正确的做法是先手算或者心算估个范围再用工具精确计算两者对不上就说明有问题。比如你要把 0x45 转十进制心算 4×16569然后用工具验证如果工具给出别的结果那肯定是输入错了。这种双向验证的习惯能帮你避免大量低级错误。5.3 常见误区进制、编码、字节序是三件事这是最容易混淆的地方必须掰清楚进制数字的表示方式是怎么写的问题。编码字符和字节的映射关系比如 ASCII、UTF-8是字符怎么变成字节的问题。字节序多字节数值的存储顺序大端还是小端是字节怎么排列的问题。这三者互相独立。一个十六进制数它可以是任何编码的字节也可以按任何字节序存储。搞混了这三者就会出现文件全反了字符串搜不到数值对不上这类问题。排查的时候要一个一个排除先确认进制对不对再确认编码最后确认字节序。5.4 从看得懂到用得上的练习建议光看理论没用得动手。我的练习路径是这样的每天手算 5 个十进制转二进制坚持两周位权直觉基本就建立了。用十六进制编辑器打开一个图片文件找到文件头对照表格确认格式。写一段代码做进制转换不用库函数自己实现除基取余和按权展开。找一个二进制文件搜索一个已知字符串练习十六进制模式搜索。手动计算一次 LRC 校验理解逐位运算的过程。这几步走下来进制就不再是考试题而是你手里的工具了。6. 几个真实场景的排查记录6.1 下载的二进制程序包找不到怎么定位有次同事下载了一个工具文件名没有后缀双击没反应以为是下载坏了。我让他用十六进制编辑器打开看头几个字节结果是 7F 45 4C 46标准的 ELF 可执行文件。问题不是文件坏了而是没有执行权限。chmod x 之后就能跑了。这个案例的教训是文件类型看内容不看后缀。后缀可以随便改但文件头是格式规范的一部分改不了。遇到打不开找不到类型的文件先看文件头。6.2 十六进制文件全反了的排查链路这个问题的排查要按顺序来确认是不是字节序问题。如果文件里多字节数值的字节顺序和你预期相反那就是大小端搞反了。确认是不是位序问题。有些场景比如某些硬件寄存器位序是从左往右数的和常规相反。确认是不是编码问题。用错误的编码打开文件字符会显示成乱码看起来像反了。确认是不是工具设置问题。有些编辑器有反转字节的显示选项可能被误开了。按这个顺序排查基本能定位到原因。最怕的是一上来就乱改把原本正确的文件改坏了。6.3 在二进制内存里搜索字符串的注意事项前面提到用十六进制模式搜索moz_require_signingtrue。这里补充几个细节注意大小写。十六进制搜索是精确匹配大小写不同字节就不同。注意字符串结尾。C 风格字符串以 0x00 结尾搜索时可能要把结尾的 00 也算进去。注意对齐。有些数据结构有对齐要求字符串可能不在你以为的偏移上。注意编码。如果字符串是 UTF-16 编码的每个字符占两个字节十六进制序列会完全不同。这些细节决定了搜索能不能成功。搜不到的时候不要急着怀疑工具先检查这几个点。6.4 关于e进制和三进制的闲聊偶尔会看到有人讨论e进制自然常数 e 约等于 2.718是不是最优进制。从信息论角度e 进制确实是单位成本信息量最大的进制但工程上没法实现因为进制数必须是整数。最接近 e 的整数是 3所以三进制在理论上比二进制更高效。但前面说过三态器件的工程难度大所以二进制胜出。至于三进制相关的硬件项目那是另一个层面的探索和日常开发关系不大。了解这个背景知识有助于理解理论最优不等于工程最优这个道理但不必在这上面花太多精力。7. 把进制变成肌肉记忆进制这东西说到底是个熟练度问题。理论就那么几条谁都能看懂但真正用起来顺不顺手取决于你练了多少。我的经验是不要等到需要用的时候才去学平时就保持手感。具体怎么做我自己的习惯是看到任何数字都下意识地想一下它的二进制和十六进制长什么样。看到 255 想到 FF看到 1024 想到 400看到 0x7F 想到 127。这种条件反射一旦建立处理底层问题时就不会被进制拖后腿。还有一个习惯是读文档时留意进制标注。技术文档里经常混用十进制、十六进制、二进制比如寄存器地址 0x20掩码 0b0011超时 500ms。读的时候主动区分时间长了自然就敏感了。最后说一个我踩过的坑不要假设所有工具都用同一种进制显示。有的调试器默认十六进制有的默认十进制有的颜色值用 #RRGGBB有的用 0xAARRGGBB。用之前先确认显示格式否则很容易把 0x1016当成 10 来理解差出 6 去。这种错误在调参数、算偏移的时候特别致命而且因为数值看起来合理往往要排查很久才能发现。养成先确认进制再读数的习惯能省下大量调试时间。
返回列表