☰
IDA反汇编数据类型还原:从db/dd到结构体与位域
2026/11/4 14:39:23 网站建设 项目流程

简介:这份文档面向逆向工程入门者与安全分析初学者,围绕交互式反汇编器 IDA Pro 的基础操作展开,帮助读者跨越二进制分析的第一道门槛。内容以十四章图文教程组织,从一段输出客户与产品信息的 C 语言小程序入手,依次讲解 IDB 数据库分析、byte/word/dword 等基本类型识别与按 D 键转换、操作数进制与符号格式调整、ASCII 字符串识别与风格设置、数组定义与元素宽度、重复字节合并、下标显示,以及枚举类型与位域在结构体还原中的用法,并延伸至函数分析、调试、图形视图等进阶方向。压缩包内仅 1 个 doc 文件,体积约 1.38MB,单文件结构便于顺序阅读与随时检索。已有 261 人学习,适合希望对照实例逐步熟悉 IDA 界面、快捷键与数据建模思路的读者,为后续漏洞挖掘与逆向分析打基础。

1. 从一堆 db、dd 开始:这份 14 章图例笔记解决的是「看不懂」而不是「不会用」

把二进制文件拖进 IDA、等自动分析跑完,绝大多数人第一眼的困惑并不是指令看不懂,而是数据区那片密密麻麻的十六进制:明明肉眼能认出是一句提示语,IDA 却按字节切成了一排db;明明是个取值范围只有 0、1、2 的类别字段,界面上只有0x0、0x1、0x2。这份《IDA 简易教程共十四章(图)》把突破口选得很实在——先写一个几十行的 C 小程序,自己掌握源码,再回到 IDA 里一章一个操作,把基本类型、操作数格式、字符串、数组、枚举、位域逐层还原。它适合刚能看懂简单汇编、但不会在 IDA 里「整理视图」的人,也适合要给团队整理一份内部上手材料、需要一个可对照靶子的人。

2. 拿自己写的 C 程序当靶子:反汇编视图里哪部分能信

逆向新手的常见做法是随便抓一个现成程序开刀,然后在几百个函数里迷路。更稳妥的路径是先造一个自己完全掌握源码的样本,让每一处反汇编都能和源码里的某一行对应上。这一章做的事就是把靶子立起来,同时把 IDA 自动分析产出的三类信息分清楚:哪些是它算出来的,哪些是它猜的,哪些根本还没动。

2.1 靶子程序要满足的三个条件

我给这份教程补的示例程序遵循一条经验:源码必须足够小、必须有明确的数据结构、必须有可打印字符串。前两条保证你能把注意力放在数据类型上,第三条保证你有一个稳定的「锚点」——字符串在二进制里是连续可打印字节,容易被自动识别,顺着它就能摸到引用它的函数。

/* demo.c —— 只为了在 IDA 里有个能逐字节对照的靶子 */ #include <stdio.h> /* 产品类别:一次只能属于一种,取值互斥 */ typedef enum { CAT_BOOK = 0, CAT_SOFTWARE = 1, CAT_HARDWARE = 2 } product_category_t; /* 平台组合位:一个产品可能同时跑在多个平台上,用按位或组合 */ #define PLAT_PC 0x01 #define PLAT_MAC 0x02 #define PLAT_LINUX 0x04 typedef struct { unsigned int plateform : 3; /* bit0..bit2 平台组合 */ unsigned int os : 3; /* bit3..bit5 操作系统组合 */ unsigned int category : 2; /* bit6..bit7 单一类别 */ char name[32]; char vendor[16]; } software_info_t; static software_info_t g_items[] = { { PLAT_PC, 0x01, CAT_BOOK, "IDA QuickStart Guide", "" }, { PLAT_PC, 0x02, CAT_SOFTWARE, "Demo Disassembler", "VendorA" }, { PLAT_PC | PLAT_MAC, 0x04, CAT_SOFTWARE, "PhotoRescue", "VendorB" }, }; int check_product(const software_info_t *p) { if (p->category == CAT_SOFTWARE && (p->plateform & PLAT_PC)) return 1; return 0; } int main(void) { printf("CUSTOMERS:\n"); printf("CUSTOMER 0001: Peter (m)\n"); printf("PRODUCTS:\n"); for (unsigned i = 0; i < sizeof(g_items)/sizeof(g_items[0]); i++) printf("PRODUCT %04u: %s\n", i + 1, g_items[i].name); return 0; }

编译时把优化关掉,这一点很关键:-O0(或等价选项)能让结构体字段访问、数组下标计算都以最直白的形式出现在反汇编里,方便你逐条比对;开启优化后常量折叠和向量化会把很多中间步骤抹掉,新手很容易误以为自己看错了。同时保留调试信息,IDA 加载后能直接拿到函数名和部分符号,省去先做符号恢复的功夫。

# 关优化、带调试信息,保证结构体访问和数组遍历不被优化掉 gcc -O0 -g -o demo demo.c # 若用另一套工具链,等价做法是关闭优化并保留调试信息 # cl /Od /Zi demo.c

代码里那几个#define和位域不是随手写的,它们对应教程后半段要反复用到的两类场景:互斥取值(枚举能直接匹配)和可组合取值(必须拆到单个 bit)。很多人在这一步偷懒,随便写个结构体,到枚举和位域那一章就找不到可以对照的实例了。

2.2 反汇编视图里的三类信息,先分类再动手

IDA 完成自动分析后,界面上其实是混着三类东西的。第一类是它通过函数签名库匹配出来的库函数,这类函数通常会被标成库函数并染上特定颜色,边界基本可信;第二类是编译器生成的用户代码,函数边界大部分正确,但遇到尾调用、异常表、手写汇编时会切错;第三类是纯数据,包括字符串常量、跳转表、静态数组,这一类的识别率最低,也是最需要手工干预的地方。

我一般按这个顺序过一遍:先开字符串窗口(Shift+F12),在里面搜CUSTOMERS、PRODUCTS这类自己写的提示语;双击跳到数据地址;在该地址上按 X 看交叉引用,找到引用它的函数并双击进入;最后在函数里顺着参数往上看,找到g_items这个静态数组的地址。这一条链路走完,你手里就有了三个确定的锚点:一个字符串地址、一个引用点、一个数组起始地址。

提示:交叉引用窗口里如果出现多个引用,优先选落在用户代码段、且上下文看起来像初始化的那个,别急着选第一个。

2.3 按 D 之前,先搞清楚它在改什么

按 D 这个动作,很多人理解成「把数据改成另一种类型」,更准确的说法是:它改变的是 IDA 对从当前地址开始的一段字节的解释宽度,字节内容一个都没动。db、dw、dd分别对应 1、2、4 字节的解释单位,界面上显示的数值随宽度变化,但文件里的字节序列始终原样。

按 D 会在当前启用的类型集合里循环。这个集合不是固定的,Options菜单里的Setup data types对话框列出了所有候选类型,每个类型前面有一个标记位,只有被勾选的类型才会进入 D 键的循环序列。教程里特意提了这一点,因为默认集合通常只有 byte、word、dword 这几档,遇到 8 字节整数或者单精度浮点时,按多少下 D 都转不出来,不是操作错了,是类型压根没启用。

还有一个容易被忽略的行为:IDA 在决定转换后的默认宽度时,会参考当前地址的对齐情况、相邻已定义项、以及该位置是否被当作指针引用过。这就是教程里说的「依据数据自身的结构来转换」。实际影响是,同一个地址在不同上下文里按 D,落到哪个宽度可能不一样,所以别指望闭着眼睛连按就能对齐。

2.4 覆盖确认与 U 键回滚

如果目标字节已经被定义过(比如你之前按过一次 D,或者自动分析已经把它标成某个类型),再次按 D 时 IDA 会弹确认,问你是否要覆盖已有的定义。这个确认开关在Options菜单里可以关掉,教程里点名了对应的命令。我的建议是前期别关:这个弹窗其实是一个提醒,告诉你「这里已经有人做过判断了」,多数时候值得停下来看一眼原来是什么类型、为什么那么定。

撤销用 U 键,把选中的字节恢复成未定义的原始状态。这里有个省时间的用法:先用鼠标框选一整段,再按 U,一次可以把整片区域全部清掉,不用一个地址一个地址地按。清理完再从头按 D 重新定义,比在原有的一堆错误定义上修修补补要快得多。我处理陌生样本时,遇到数据区明显错乱,第一反应就是框选、按 U、重新来。

3. 按 D 之前先想清楚:byte/word/dword 与操作数格式的四个开关

类型定义解决的是「这段字节多宽」,操作数格式解决的是「这个数值怎么显示给人看」。两者经常被混为一谈,结果是有人反复按 D 想改进制,按了半天宽度变了、进制没变。这一章把这两层拆开,顺带把有符号、前导零、自定义进制这几个开关讲清楚,因为它们直接决定你在比较指令旁边看到的是0xFFFFFFFF还是-1。

3.1 Number 命令改的是显示,不是宽度

在反汇编视图里选中一个立即数,通过Operands工具栏的Number命令(或右键菜单里的同名项),可以在十六进制、十进制、八进制、二进制、字符常量之间切换。这个操作只影响显示形式,不改变数据宽度,也不改变指令编码。所以「按 D 没把0x10变成16」这种情况,是操作选错了,不是工具坏了。

什么时候该切、什么时候不该切,我按用途分:

场景推荐显示理由
地址、指针、文件偏移十六进制与调试器、十六进制编辑器里的地址保持一致,方便对照
位掩码、标志位常量十六进制或二进制能直接看出哪几位被置上
数组下标、循环计数、元素个数十进制与源码里的字面量一致,减少心算
与字符比较的常量字符常量0x41和'A'一眼分得出是不是字符串处理逻辑
有符号比较的边界值十进制并开启有符号避免把补码当成一个巨大的正数

这张表不是死规矩,但按它走能少犯一类错:把位掩码切成十进制之后,0x4000变成16384,你很难再从数字上看出它是第 14 位。

3.2 符号与前导零:两个小开关决定可读性

Toggle leading point这个选项的作用是给未占满当前宽度的数据在高位补 0。听起来是个纯显示细节,实际在对照结构体字段时有用:一个 4 字节字段如果真实值很小,不补零时显示可能是0x2A,补零后是0x0000002A,后者更容易让你确认「这里确实是一个 4 字节槽位,只是值小」。

有符号和无符号的切换更关键。补码表示下,0xFFFFFFFF作为无符号数是 4294967295,作为有符号数是 -1。当你在反汇编里看到cmp eax, 0xFFFFFFFF并且后面紧跟jl之类的条件跳转时,几乎可以断定源码写的是-1,这时候把操作数切成有符号,整段逻辑立刻通顺。反过来,如果这个常量是某个掩码,切成有符号反而会误导你,因为掩码本身没有正负概念。

注意:进制和符号是显示层的设置,不会改变 IDA 对数据类型的判断,也不会影响它后续的自动分析结果。别指望改完显示,交叉引用或结构体偏移跟着变。

3.3 自定义进制:少数派场景,但确实有人需要

教程里提到还可以自定义数据进制。这个功能的实用场景比想象中窄,主要集中在两类:一类是某些协议或文件格式用非标准的基数编码数值,需要在视图中直接读出来;另一类是历史遗留代码里大量使用八进制常量,切到八进制能和源码对上。

操作入口在Options菜单下,输入自定义基数即可。我一般只在确认目标程序确实使用该进制时才开,因为它是全局显示设置,开着会让其他区域的数值也变得不好读。如果只是个别位置需要,用Number命令单独切更合适,改完记得切回来。

3.4 把格式设置和结构体偏移对齐起来

真正让操作数格式产生价值的,是它和结构体偏移的配合。当你把一个局部变量识别成结构体指针后,IDA 会把[ebp-0x20]这类表达式替换成带字段名的形式,字段偏移的显示进制跟随当前设置。如果偏移显示成十进制,而你手里的结构体声明是按字节十六进制排的,对照起来就要多一步换算。

我的习惯是:结构体相关的地址和偏移统一保持十六进制,数值型字段(尤其是计数、索引、枚举值)切十进制。这样一眼扫过去,方括号里的是偏移,裸露的数字是值,两类信息不会混。这个习惯不写在任何手册里,但它能省掉大量来回换算的时间。

4. 字符串、数组、枚举、位域:把数据还原成接近源码的形态

前面几章做的是「把字节切对」,这一章做的是「给切好的字节赋予语义」。四类操作对应四种典型数据:字符串是连续可打印字节,数组是同宽度元素的重复,枚举是取值范围有限的整数,位域是打包在同一个存储单元里的多个小字段。它们的操作路径不同,坑也不同,混着用最容易翻车。

4.1 A 键不是万能的:字符串识别的三个前提

按 A 键会把从当前地址开始的一段字节转成字符串,使用的是当前默认的字符串风格。这个动作能成功,前提有三个:字节序列是可打印字符、能找到终止符、当前默认风格与目标字符串的实际编码方式一致。三个条件缺一个,结果就是长度不对或者内容截断。

C 风格字符串以0x00结尾,这是最常见的情况,也是默认风格。但二进制里还有大量非 C 风格的字符串:带长度前缀的、以特定字节结尾的、宽字符编码的。教程里点到了Options菜单下的ASCII string style命令,这个对话框允许你修改默认风格,也可以给某种风格指定快捷键,这样按 A 用默认的,按自定义快捷键用另一种,不用每次进菜单。

实际排查时的判断顺序:先看这段字节里有没有0x00,有就按 C 风格试;没有就看开头几个字节是不是长度前缀(第一个字节的值恰好等于后面可打印字符的数量,这种情况很常见);如果可打印字符之间夹杂大量0x00,那多半是宽字符编码,需要换对应的风格。判断错了的表现很典型:按 A 之后字符串只显示一个字符,或者把后面几个不相关的字段一起吞进来变成了乱码。

4.2 数组对话框:四个参数决定显示效果

在 C 里字符串本质是字符数组,所以字符串处理完,下一步自然是数组。操作路径是:先定义第一个元素的类型(比如设为 byte、格式为 char),然后按*键(或Edit菜单里的Array命令)打开数组对话框。这个对话框的参数不多,但每一个都影响最终显示,值得单独列出来。

参数作用常见取值建议
Array size元素总个数按实际数据长度填,或者在选中一段区域后让 IDA 自适应
Element width每个元素占几个字节与第一个元素的类型一致,byte 就是 1,dword 就是 4
Items on a line一行显示几个元素8 或 16,跟数据类型宽度配合,太宽会换行错乱
Use "dup" construct合并连续重复元素大量填充值(如全 0)时打开,能压缩视图
Display index显示数组下标需要按索引对照源码时打开
Signed elements按有符号显示元素元素可能为负时打开

教程里给的例子是 64 个元素、一行 8 个、元素宽度 4、不合并重复、显示下标。这个组合适合查看一个由 4 字节整数构成的表,一行 8 个正好是 32 字节,和常见的缓存行宽度对上,扫起来很顺。如果换成宽度为 1 的字节数组,还按一行 8 个显示,一屏信息量太少,改成 16 更合适。

还有一个细节值得注意:当 IDA 遇到未被识别、也没被定义过的字节时,会用红色高亮标出来。这个高亮不是错误提示,而是「这里我还没判断」的意思。数组定义完之后如果还有红色残留,说明数组长度或起始地址算错了,多半是漏了元素或者把前面的对齐填充算进去了。

4.3 枚举:让比较指令里的魔数有名字

到了这一步,你已经能把数据区切对、把字符串和数组认出来,剩下最影响阅读体验的就是那些孤零零的整数常量。check_product函数里的cmp [eax+6], 1这种指令,不看源码根本不知道1代表什么。枚举就是解决这个问题的。

操作步骤分三步。第一步,打开枚举窗口(View菜单下的子视图列表里能找到Enumerations),新建一个枚举,给它起个和源码一致的名字;第二步,在成员列表里逐条添加名字和数值,比如CAT_BOOK对应 0、CAT_SOFTWARE对应 1;第三步,回到反汇编,在对应的数值上右键,选择Symbolic constant,IDA 会列出所有数值匹配的枚举成员供你挑选。

这里有一个容易被误解的点:Symbolic constant匹配的是数值本身,不是位置。所以如果两个枚举里有相同的值,列表里会同时出现两个候选,选错不会报错,只会让反汇编显示一个语义错误的名字。我在处理大型样本时养成了一个习惯:给枚举命名时带上模块前缀,这样候选列表里一眼能分清来源。

另一个失败场景是数值宽度不匹配。如果枚举成员定义时按 4 字节存,而目标操作数实际是 1 字节,匹配可能不生效。遇到这种情况,先确认操作数宽度,再回去检查枚举的定义方式,而不是反复重试。

4.4 位域:为什么组合模式必须拆到单个 bit

教程里对位域的判断很到位:位域在 IDA 里被当作一种特殊的枚举类型,创建时勾选Bitfield选项即可。但真正关键的是它后面那句解释——一个位域枚举值在 IDA 里只能对应一个数值,无法表达「按位或」出来的组合。

拿示例程序里的字段说:category是互斥的,一个产品要么是书、要么是软件,取值只有 0、1、2,这种用一个普通枚举就能完整描述。而plateform是可组合的,PC 和 MAC 可以同时置位,组合出来的值有 8 种可能(含空集),如果你只按PLAT_PC、PLAT_MAC三个值定义枚举,遇到0x03就匹配不上任何成员,显示还是原始数字。

正确的做法是把可组合字段的每一个 bit 单独定义成位域枚举的一个成员:bit0 叫PLAT_PC、bit1 叫PLAT_MAC、bit2 叫PLAT_LINUX。这样当值是0x03时,IDA 能同时标出两个 bit 被置上,语义就完整了。互斥字段反而没必要拆 bit,用普通成员值更直观。

/* 在 Local types 里落地结构体声明,字段顺序与位宽必须和二进制一致 */ typedef struct { unsigned int plateform : 3; unsigned int os : 3; unsigned int category : 2; char name[32]; char vendor[16]; } software_info_t;

把这段声明粘进Local types视图并解析之后,就能在反汇编里把对应的内存区域整体应用成这个结构体类型,字段访问会自动带上名字和偏移。这里最容易出错的地方是位域的顺序和底层存储单元大小:不同编译器对位域的排布规则不完全一致,声明里的字段顺序如果和二进制里的实际布局相反,应用之后每个字段都会错位。验证方法很简单,回到g_items数组的初始化数据,逐个字段比对值,对不上就调顺序。

5. 六个翻车现场:类型改错、字符串吞字节、位域错位怎么排查

这一章集中处理动手过程中真正会卡住人的问题。每一条都按「看到什么现象 → 底层原因 → 具体怎么解决」来写,顺序大致对应前面的操作章节,遇到问题可以按顺序往下对。

5.1 按 D 没反应,或者弹窗问是否覆盖

现象:在一个地址上反复按 D,宽度在 byte 和 word 之间来回跳,就是到不了想要的 dword;或者按下去直接弹出一个确认框,选了取消就没变化。

原因:两种可能。一是目标类型没有出现在 D 键的循环集合里,Setup data types里没勾上;二是这段字节已经被定义过,覆盖确认开关拦住了本次操作。前者表现为宽度循环不到,后者表现为每次都要确认。

解决:先进Setup data types把需要的类型勾上,尤其是 8 字节整数和浮点类型;覆盖确认则看情况,如果确认这段旧定义确实是错的,就在对话框里选择继续,或者在Options菜单里关掉确认。批量重做时更快的路径是框选整段按 U 全部清掉,再从第一个地址开始重新定义。

5.2 按 A 生成的字符串长度不对,把后面的字段吃掉了

现象:按 A 之后字符串确实出现了,但末尾粘上了几个不可见字符,或者把紧邻的下一个字段也包含进来了,长度明显长于预期。

原因:这段数据里没有0x00终止符,或者终止符出现的位置比实际字符串结尾更靠后。IDA 按当前默认风格一直往后找终止符,找不到就持续吞字节。也可能目标字符串根本不是 C 风格,而是带长度前缀或者定长数组。

解决:先按 U 撤销,然后用十六进制视图人工确认终止位置,选中精确范围再按 A。如果是长度前缀风格,切到对应的字符串风格后再操作。定长数组的情况更简单,直接按数组处理,元素宽度 1、元素个数等于数组长度,不要用字符串转换。

5.3 枚举的 Symbolic constant 列表里找不到匹配项

现象:枚举已经建好,成员值和反汇编里的数值肉眼看着完全一样,但右键Symbolic constant之后列表是空的,或者候选里没有想要的那个。

原因:数值宽度不一致是最常见的原因。枚举成员按某种宽度存储,而目标操作数实际占用的字节数不同,匹配逻辑对不上。其次是枚举成员的值重复,列表里出现了多个同值项但你只认名字,容易漏看。

解决:先确认操作数的实际宽度(回到字节视图看它占几个字节),再检查枚举定义时的宽度设置是否一致。如果是重复值导致的困惑,给成员名加上模块前缀,让候选列表可读。实在匹配不上,退一步用普通注释在指令旁标出含义,比强行改类型稳妥。

5.4 数组元素宽度设错,显示成乱序

现象:定义完数组后,元素值看起来是乱的,或者每两个元素里只有一个看着有意义的数值。

原因:Element width和实际元素大小不一致。比如实际是 4 字节整数表,宽度却设成了 1,那每个 4 字节值会被切成 4 个字节分别显示,只有最低字节看起来像样。反过来,实际是字节数组却把宽度设成 4,就会把 4 个独立元素合并成一个巨大的数。

解决:回到数组的第一个元素,确认它被定义成什么类型、占几个字节,让Element width和它保持一致。改宽度不需要删掉重建,重新选中第一个元素再按一次*,把参数改对即可。改完检查红色高亮区域,如果有残留说明起始地址或长度还有问题。

5.5 位域结构体应用后字段全部错位

现象:结构体声明写好了,也应用到了内存区域,但显示的字段值和预期完全对不上,比如category显示成了 3,而源码里这个字段最大只到 2。

原因:位域的排布顺序和底层存储单元大小与编译器实际生成的不一致。声明里字段的先后顺序、每个字段的位宽、以及是否跨存储单元,都会影响最终布局。位宽总和超过一个存储单元时,还会涉及跨单元拆分。

解决:先算总位宽,确认没有超出存储单元(示例里三个字段加起来正好 8 位,落在一个字节内,是最简单的情况)。再回到初始化数据处逐字段比对,用实际值反推顺序。顺序错的调整声明里字段的先后,宽度错的改冒号后面的数字。改完重新解析声明,不需要重建整个结构体。

5.6 类型改完,反汇编视图没刷新

现象:明明已经把某个地址定义成新类型,反汇编里显示的还是旧样子,或者结构体字段名没出现在指令操作数上。

原因:IDA 的反汇编文本是缓存过的,类型变更后部分视图需要重新分析才会更新。另外,如果变更的是数据区而当前看的是代码区,两个视图本来就不同步。

解决:在目标地址上触发一次重新分析,通常右键菜单里能找到对应命令;或者对该地址按一下 U 再重新定义,强制刷新。结构体应用之后,需要重新进入引用该内存的函数,字段名才会在操作数上体现出来。养成「改完类型就重新进函数看一遍」的习惯,能避免很多「改了但没生效」的错觉。

6. 结构体落地后的交叉验证:一份可复用的检查清单

结构体一旦应用错,后面所有字段阅读都是错的,而且错得很隐蔽——界面上有名字、有偏移,看起来一切正常。我现在的做法是每次落地结构体之后,固定做一轮交叉验证,不跳过任何一项。

第一项是总大小核对。把结构体声明里的字段宽度相加,再考虑对齐填充,得到的结果必须和二进制里两个相邻结构体实例之间的间距一致。示例里三个位域字段共 1 字节,加上 32 字节和 16 字节的字符数组,再算上对齐,总大小是个固定值;在数组里量一下相邻元素的地址差,就能验证。第二项是字段偏移核对,逐个字段确认它在反汇编里的访问偏移与声明一致,尤其是字符数组之后的字段,最容易因为填充差异而错位。第三项是值域核对,拿初始化数据里的实际值去比对每个字段的显示,出现越界值就说明宽度或顺序有问题。

import idc # 在一个结构体字段的访问点上,抽样确认操作数宽度与声明是否一致 ea = 0x00401000 for i in range(3): optype = idc.get_operand_type(ea, i) # 操作数类型 opval = idc.get_operand_value(ea, i) # 操作数数值 print(i, optype, hex(opval))

这段脚本的作用不是自动化判断对错,而是把「我以为它是 4 字节」变成可打印的数字,逼着自己逐条核对。函数名以当前 SDK 为准,报错就按提示调整,逻辑本身很简单:遍历操作数,看清类型和值。

第四项是反向验证:从结构体字段出发,回到每一处引用它的代码,确认访问模式符合语义。比如category字段只应该和 0、1、2 比较,如果某处出现了和 255 比较,要么是结构体偏移错了,要么那里根本不是这个结构体。第五项是收尾检查,把所有临时命名、临时类型清理一遍,避免下一轮分析时被自己的旧标注误导。

血泪经验是有一回我省了第一项,结构体总大小算少了 4 字节,结果整个数组的第二个元素开始全部错位,字段名看着都对,值全是乱的,排查了两个小时才发现问题在声明本身。从那以后我每次落地结构体,都强制先把总大小和相邻实例间距量一遍再往下做。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询