基于UPX源码的PE文件压缩与自解压技术实现详解

发布时间:2026/7/10 5:00:58

基于UPX源码的PE文件压缩与自解压技术实现详解
1. 项目概述为什么我们要啃UPX这块硬骨头如果你是一个C开发者或者对逆向工程、软件保护领域感兴趣那么“UPX”这个名字你一定不陌生。它全称是“Ultimate Packer for eXecutables”一个开源、免费、跨平台的可执行文件压缩工具。简单来说它能把你的EXE、DLL文件“压扁”让体积显著减小同时神奇地保持文件依然可以直接运行。这听起来有点像魔术而今天我们要做的就是亲手拆解这个魔术基于其3.05版本的源码用C语言来一场从理论到实战的深度探索。你可能会问市面上有那么多现成的UPX工具一键就能压缩为什么还要费劲去研究源码甚至自己实现这正是这个项目的核心价值所在。首先理解UPX的压缩与解压机制是深入理解PEPortable ExecutableWindows可执行文件格式文件结构的绝佳途径。你会接触到文件头、节区、导入表、重定位表等核心概念这些知识是安全分析、软件逆向、病毒查杀的基石。其次UPX的“运行时自解压”技术Stub本身就是一个精巧的工程范例它涉及内存管理、节区映射、API动态解析等底层操作是学习系统级编程的活教材。最后通过亲手实现核心流程你能获得对二进制文件操作、数据压缩算法如NRVLZMA最直观、最深刻的理解这种能力是阅读十篇理论文章也无法替代的。本次我们将聚焦于UPX 3.05这个经典版本。选择它一方面是因为其代码结构相对清晰核心逻辑已经非常成熟另一方面3.05版本支持的格式如PE、ELF和压缩算法具有代表性足以构建完整的知识体系。我们的目标不是简单地复刻一个UPX而是通过解析其架构提取核心思想并用现代C遵循C17/20的良好实践来实现一个具备基础压缩、解压功能的“教学版”UPX核心引擎。无论你是想夯实C底层编程能力还是为进入安全领域做准备亦或是纯粹对“程序如何压缩自己”感到好奇这篇长文都将为你提供一条清晰的路径。2. UPX 3.05 源码架构深度拆解要理解一个复杂的系统最好的方式就是先俯瞰它的全貌。UPX的源码组织体现了典型的功能模块化思想。正如我们在相关资料中看到的其源码主要位于/src目录下几个关键的子目录构成了它的骨架。2.1 核心模块职责分析/src/compress目录这是UPX的心脏负责所有压缩算法的实现。UPX支持多种压缩算法例如LZMA、NRVUPX自定义的基于LZ77的变种。在这个目录里你会找到像compress.cpp、nrv2b.cpp、nrv2d.cpp、nrv2e.cpp以及lzma相关的源文件。每个算法类通常继承自一个抽象的Compressor基类定义了compress和decompress等虚函数。这种设计使得增加新的压缩算法变得非常容易符合开闭原则。研究这部分代码你能学到数据压缩的核心思想比如滑动窗口、字典编码以及如何在内存受限的环境下高效处理数据流。/src/stub目录这是UPX的灵魂即“自解压存根”。当一个可执行文件被UPX压缩后其入口点Entry Point会被修改为指向这个stub。stub是一段极其精炼的机器码它的任务是在运行时将压缩后的主体数据解压到内存的正确位置修复必要的重定位信息然后将控制权跳转到原始的程序入口点OEP。stub代码针对不同平台如stub/src/i386-dos32、stub/src/amd64-linux有不同的实现。分析stub是理解自解压技术的关键你会看到它如何在不依赖外部库的情况下仅用汇编或内联汇编完成内存拷贝、解压算法调用和重定位修复。/src/filter目录预处理与后处理过滤器。某些类型的可执行文件数据特别是代码段经过压缩后其统计特性可能不理想或者解压后需要特殊处理。filter的作用就是在压缩前对数据进行变换预处理或在解压后进行逆变换后处理以提高压缩率或确保程序正确运行。例如针对x86指令的call和jump指令的相对地址进行特殊处理。理解过滤器需要一定的汇编指令知识。/src/pack与/src/unpack目录打包与解包的核心逻辑。这是连接压缩算法、stub和文件格式的“总控中心”。packer类如packpe.cpp负责读取原始可执行文件分析其结构调用压缩器压缩各个节区生成新的节区布局将stub代码嵌入并构建新的文件头。unpacker则相反它模拟了stub在内存中的行为用于静态分析或解压文件。这部分代码最复杂因为它需要深入处理特定文件格式PE、ELF等的所有细节。/src/check目录完整性校验。用于在压缩或解压前后进行校验确保数据一致性。/src/console目录命令行界面。处理用户输入参数调用相应的packer或unpacker。2.2 关键数据结构与流程交互整个UPX的工作流程可以概括为一条清晰的流水线解析阶段packer读取输入文件将其解析为内部表示如PeFile对象包含文件头、节区表、导入表、重定位表等信息。过滤与压缩阶段对需要压缩的节区数据通常是代码和数据段应用可选的filter进行预处理然后调用compress模块中的算法进行压缩。重组阶段计算压缩后数据的大小规划新的内存布局。将压缩后的数据、解压stub、以及必要的元数据如解压参数、原始OEP组装成一个新的节区通常叫UPX1或修改现有节区。重构文件头更新PE文件头中的入口点地址指向stub的起始位置。修改节区表描述新的节区布局和属性如可读、可写、可执行。处理导入表因为stub可能需要调用少数系统API如LoadLibraryA,GetProcAddress这些依赖需要被保留或内联到stub中。写入阶段将新的文件头和节区数据写入到输出文件。在这个过程中几个核心的数据结构贯穿始终upx_byte/upx_uint等类型定义确保跨平台的数据类型一致性。Packer基类及其派生类如PackPe封装了对特定文件格式的所有操作。Compressor类层次结构提供了统一的压缩/解压接口。Filter类定义了数据变换的接口。注意阅读UPX源码时你会遇到大量针对不同编译器如GCC, MSVC和平台的宏定义#ifdef。这是跨平台项目的典型特征。建议初次阅读时可以暂时聚焦于你主要关心的平台如Windows PE忽略其他分支以降低复杂度。3. 动手实战用C实现一个简易PE压缩器理解了架构我们开始动手。我们的目标是实现一个“迷你UPX”它能够压缩一个简单的Windows控制台程序PE32格式并生成一个可以自解压运行的新文件。我们将遵循UPX的核心思想但会做大量简化以便于理解和实现。3.1 环境准备与基础工具类首先你需要一个C开发环境。推荐使用Visual Studio 2022或更高版本社区版免费或者Clang/LLVM on Windows。确保你使用的是C17或更高标准的编译器。我们将创建几个基础的头文件来定义类型和工具函数// types.hpp #pragma once #include cstdint #include vector #include string using u8 uint8_t; using u16 uint16_t; using u32 uint32_t; using u64 uint64_t; using ByteArray std::vectoru8; // PE文件相关结构定义简化版仅包含必要部分 struct Pe32DosHeader { /* ... */ }; struct Pe32FileHeader { /* ... */ }; struct Pe32OptionalHeader { /* ... */ }; struct Pe32SectionHeader { /* ... */ }; // 工具函数读取文件、写入文件、内存对齐计算等 namespace utils { ByteArray readFile(const std::string path); void writeFile(const std::string path, const ByteArray data); u32 alignUp(u32 value, u32 alignment); }utils::readFile和writeFile用于二进制文件的读写。alignUp函数至关重要因为PE文件中的许多字段如节区大小、文件大小都需要按特定值如0x2000x1000对齐。3.2 实现一个简易的LZ77压缩器UPX的NRV算法是LZ77的变种。为了教学我们实现一个最基础的LZ77压缩算法。LZ77的核心思想是用距离长度对来表示当前数据与历史窗口中重复数据的关系。// simple_lz77.hpp #pragma once #include types.hpp #include tuple #include vector class SimpleLZ77Compressor { public: struct Match { u32 distance; // 匹配距离回溯长度 u32 length; // 匹配长度 u8 literal; // 如果不匹配存储原字符 bool isMatch; // 标记是匹配对还是原字符 }; // 压缩函数 static std::vectorMatch compress(const ByteArray input, u32 windowSize 32768, u32 maxMatchLen 258); // 将匹配序列编码为字节流简化编码用1字节头区分后跟数据 static ByteArray encodeMatches(const std::vectorMatch matches); // 解压函数 static ByteArray decompress(const ByteArray compressedData); };在compress函数中我们需要滑动一个查找窗口对于输入流中的每个位置在历史窗口中寻找最长的匹配串。这是一个计算密集型的操作优化查找速度如使用哈希表是工业级实现的关键但我们的教学版本可以使用简单的暴力搜索以便代码更清晰。3.3 解析PE文件结构这是最核心也是最繁琐的一步。我们需要读取一个PE文件并提取出所有必要信息。我们将创建一个PeFile类。// pe_file.hpp #pragma once #include types.hpp #include memory #include vector class PeFile { public: bool load(const std::string path); bool save(const std::string path); // 获取需要压缩的节区数据通常是.text和.data std::vectorstd::pairconst Pe32SectionHeader*, ByteArray getSectionsToCompress() const; // 获取原始入口点OEP u32 getOriginalEntryPoint() const { return optionalHeader.AddressOfEntryPoint; } // 在压缩后用新的节区布局和入口点重建PE文件 void rebuild(const ByteArray stubCode, const ByteArray compressedData, u32 decompressedSize); private: // 解析PE头 bool parseHeaders(const ByteArray data); // 加载节区数据 bool loadSections(const ByteArray data); // 成员变量存储解析出的头信息和节区数据 Pe32DosHeader dosHeader; Pe32FileHeader fileHeader; Pe32OptionalHeader optionalHeader; std::vectorPe32SectionHeader sectionHeaders; std::vectorByteArray sectionData; ByteArray overlayData; // 附加数据 };在load函数中你需要按顺序解析DOS头、PE签名、文件头、可选头然后是节区表。每个节区头包含了该节区在文件中的偏移、大小、在内存中的虚拟地址、大小等关键信息。getSectionsToCompress函数需要根据节区的特性例如包含代码、可执行来判断哪些节区是需要压缩的。3.4 构建自解压存根Stubstub是一段小型机器码。为了简化我们不直接用汇编编写而是用C编写解压逻辑然后将其编译成一个独立的、位置无关的代码块再作为数据嵌入到新PE文件中。这个stub需要做以下几件事获取自身的运行基址。找到紧随其后的压缩数据块。调用解压函数我们实现的SimpleLZ77Compressor::decompress将数据解压到目标内存地址。修复重定位在简易版中我们可以要求原始程序是基址无关的或者跳过此步这限制了适用范围。跳转到解压后的原始入口点OEP。我们可以这样设计stub// stub_generator.cpp #include simple_lz77.hpp #include Windows.h // 仅用于获取API实际stub中需动态获取 // 这是一个将被注入到新PE文件中的函数 // 它必须使用很少的外部依赖且代码尽可能紧凑 extern C __declspec(naked) void stub_entry() { // 内联汇编或纯C代码实现上述步骤1-5。 // 步骤1通过调用下一条指令的地址来获取当前EIP/RIP从而计算基址。 // 步骤2基址固定偏移 压缩数据位置。 // 步骤3调用链接进来的decompress函数。 // 步骤4简易版可省略重定位修复。 // 步骤5计算OEP地址并跳转。 // 注意整个函数体最终会被提取为二进制机器码。 }在实际操作中更可行的方案是预先用汇编或C写好stub编译成.obj或.bin文件然后在我们的打包程序中以二进制资源的形式链接进去。我们的PeFile::rebuild函数需要将这个stub的二进制代码作为一个新的节区例如.upx0插入并计算好压缩数据相对于stub的偏移。3.5 整合实现Packer类现在我们将所有模块串联起来实现一个简化的MiniPacker。// mini_packer.hpp #pragma once #include pe_file.hpp #include simple_lz77.hpp #include stub_generator.hpp class MiniPacker { public: bool pack(const std::string inputPath, const std::string outputPath); private: ByteArray generateStub(u32 oep, u32 compressedDataRVA, u32 decompressedSize); // ... 其他辅助函数 }; bool MiniPacker::pack(const std::string inputPath, const std::string outputPath) { // 1. 加载并解析原始PE文件 PeFile peFile; if (!peFile.load(inputPath)) return false; // 2. 提取需要压缩的节区数据合并成一个连续的数据块 auto sections peFile.getSectionsToCompress(); ByteArray dataToCompress; for (const auto [header, data] : sections) { dataToCompress.insert(dataToCompress.end(), data.begin(), data.end()); } // 3. 压缩数据 auto matches SimpleLZ77Compressor::compress(dataToCompress); ByteArray compressedData SimpleLZ77Compressor::encodeMatches(matches); // 4. 生成自解压存根 // 计算存根加载后的RVA虚拟地址以及压缩数据在存根后的偏移 u32 stubRVA /* 计算新的节区虚拟地址 */; u32 compressedDataOffset /* stub代码大小 一些元数据 */; ByteArray stubCode generateStub(peFile.getOriginalEntryPoint(), stubRVA compressedDataOffset, dataToCompress.size()); // 5. 使用存根代码、压缩数据等信息重建PE文件 peFile.rebuild(stubCode, compressedData, dataToCompress.size()); // 6. 保存新的PE文件 return peFile.save(outputPath); }generateStub函数负责生成最终的存根二进制码。它需要将原始OEP、压缩数据的位置、解压后大小等参数“硬编码”到存根代码的特定位置。这通常通过准备一个存根模板然后在特定偏移处写入这些参数值来实现。4. 调试、问题排查与进阶思考实现这样一个项目几乎一定会遇到各种问题。下面是一些常见坑点和排查思路。4.1 常见问题与解决方案速查表问题现象可能原因排查步骤与解决方案压缩后的程序无法运行提示“不是有效的Win32应用程序”。新的PE文件头或节区表被破坏。1. 使用PE编辑工具如CFF Explorer或010 Editor对比压缩前后文件的头部。检查MZ签名、PE签名、文件头、可选头是否完整。2. 重点检查入口点地址是否指向了存根节区的有效RVA。3. 检查节区对齐SectionAlignment,FileAlignment是否设置正确节区的PointerToRawData和SizeOfRawData是否与文件实际数据匹配。程序运行瞬间崩溃或触发数据执行保护DEP错误。存根节区的内存属性设置错误。1. 检查存根节区如.upx0的Characteristics字段。它通常需要包含IMAGE_SCN_MEM_EXECUTE可执行、IMAGE_SCN_MEM_READ可读和IMAGE_SCN_CNT_CODE包含代码。2. 确保在可选头中设置了IMAGE_DLLCHARACTERISTICS_NX_COMPAT标志以兼容DEP同时存根代码所在页必须具有执行权限。程序运行后解压过程似乎成功了但跳转到OEP后行为异常或崩溃。1. 解压目标地址错误。2. 重定位信息丢失或未修复。3. 压缩/解压算法有bug数据损坏。1.调试存根在存根代码开始处插入一个调试断点如int 3指令对应机器码0xCC用调试器如x64dbg附加到进程单步跟踪存根执行观察解压函数调用前后内存数据变化。2.检查重定位原始程序如果不是基址无关的即编译时使用了固定基址那么它的代码中会有绝对地址引用。压缩后这些代码被移动到了新的内存地址必须修复。UPX的完整实现会处理重定位表。我们的简易版可以暂时只处理/DYNAMICBASE随机基址的程序或者跳过此步这限制了可压缩的程序范围。3.验证数据完整性在解压函数内部对比解压后的数据与原始节区数据是否完全一致。可以在内存中计算CRC32校验和。压缩率极低甚至体积变大。1. 压缩算法实现有误。2. 压缩了不该压缩的数据如已压缩的资源。3. 存根本身过大。1. 单独测试你的SimpleLZ77Compressor用已知的文本和二进制数据验证其压缩/解压的正确性和比率。2. 优化getSectionsToCompress逻辑排除像.rsrc资源、.reloc重定位这类通常压缩效果不好或不能移动的节区。3. 优化存根代码体积使用更紧凑的汇编移除不必要的逻辑。4.2 调试技巧与工具推荐静态分析工具CFF Explorer强大的PE编辑器可视化查看和修改所有PE结构是排查头文件问题的首选。010 Editor with PE Template以十六进制和模板解析的方式深入查看文件每一个字节适合深度分析。IDA Pro / Ghidra反汇编工具用于分析存根代码和原始程序的逻辑。动态调试工具x64dbg / OllyDbgWindows下的主流动态调试器。在存根开始处下断点可以一步步观察解压过程查看寄存器、内存的变化是解决运行时问题的终极武器。Visual Studio Debugger如果你用C实现了存根的逻辑尽管不推荐用于最终版本可以用它来调试你的stub生成和调用逻辑。验证工具PEiD / Detect It Easy查壳工具可以用来验证你的“压缩”程序是否被识别为未知的打包器或者检测原始程序的信息。4.3 从简易版到进阶的思考我们的简易实现省略了许多UPX完整版的关键特性这也指明了深入学习的方向支持更多文件格式尝试解析和打包Linux的ELF文件。这需要你学习ELF文件格式并实现对应的ElfFile类和PackElf逻辑。实现完整的重定位修复深入研究PE重定位表.reloc节区的结构在存根中实现重定位项的遍历与修复。这是支持压缩绝大多数Windows程序的关键。集成更强的压缩算法将LZMA SDK集成到你的压缩器模块中替代简易的LZ77以获得更高的压缩率。反调试与混淆许多打包器会集成简单的反调试技巧如检查BeingDebugged标志、NtGlobalFlag等让逆向分析更困难。你可以研究并在存根中加入这些技巧。处理TLS线程局部存储和异常处理完整的可执行文件可能包含TLS回调和异常处理目录这些在压缩后都需要被正确保留或转移。通过这个从源码解析到动手实现的过程你收获的将不仅仅是一个“压缩工具”的代码。你深入理解了PE文件的生命周期、程序在内存中的加载与执行原理、数据压缩算法的应用以及如何用C进行系统级的二进制操作。这些知识构成了软件安全、逆向工程、性能优化等多个领域的坚实基石。当你再次使用UPX或类似工具时你看到的将不再是一个黑盒而是一个由精妙模块组成的、清晰可见的工程杰作。这就是阅读源码和动手实践的最大魅力。

相关新闻

ROS 跨版本跨网卡通信终极方案:Noetic+Foxy↔Humble 双机互通、DDS 冲突根治、双网段失联解决

ROS 跨版本跨网卡通信终极方案:Noetic+Foxy↔Humble 双机互通、DDS 冲突根治、双网段失联解决

2026/7/10 5:00:58

摘要:做ROS机器人开发,90%的跨机看不见节点、话题丢失、桥接不通问题,根源都不是代码问题,而是网络底层认知不清。本文从零通俗讲解网卡、路由、网关、网段、ARP、二层/三层网络核心原理,结合真实工程 ROS1 Noetic+ROS2 Foxy↔ROS2 Humble 跨版本双机通信架构,详解ros1_b…

从餐饮到具身智能,普渡机器人百亿入场后如何应对新考验?

从餐饮到具身智能,普渡机器人百亿入场后如何应对新考验?

2026/7/10 5:00:58

具身智能热潮下的普渡机器人 今年上半年,具身智能的故事越讲越大,科技语境中不断流出新的具身智能投融资消息,抬高了产业预期。然而,赛道呈现出叙事与落地的割裂,多数玩家停留在 Demo、小规模交付阶段,手握…

九号控制器二次开发:从CAN总线通信到极飞A12测试实践

九号控制器二次开发:从CAN总线通信到极飞A12测试实践

2026/7/10 5:00:58

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 那天下午,我在车库里盯着那台九号电动车控制器,旁边是极飞A12测试设备,一堆线缆散落着。这已经不是…

从零开始学网络安全第四天

从零开始学网络安全第四天

2026/7/10 6:31:02

Windows 服务器安全全解|NTFS 权限深度剖析 IIS/FTP/DNS/DHCP 渗透打点核心笔记 前言 做内网渗透、等保测评、Windows 运维必备干货!今天一次性吃透两大核心板块:NTFS 安全权限底层逻辑 实操提权技巧、Windows 四大高危服务 Web/FTP/DNS/…

nRF Connect GATT Server 配置实战:从克隆到自定义服务的 5 个关键步骤

nRF Connect GATT Server 配置实战:从克隆到自定义服务的 5 个关键步骤

2026/7/10 6:31:02

nRF Connect GATT Server 配置实战:从克隆到自定义服务的 5 个关键步骤 在物联网和嵌入式开发领域,蓝牙低功耗(BLE)技术已成为设备间通信的重要标准。作为开发者的得力助手,nRF Connect 提供了强大的 GATT Server 配置…

LTM4644 国产替代方案对比:3款国产4644模块性能实测与选型指南

LTM4644 国产替代方案对比:3款国产4644模块性能实测与选型指南

2026/7/10 6:31:02

LTM4644国产替代方案深度评测:3款主流模块实战对比与选型策略在FPGA和ASIC供电系统设计中,四通道DC-DC电源模块的选择往往决定着整个硬件平台的稳定性和能效表现。随着国产化替代进程加速,市场上涌现出多款对标ADI LTM4644的国产电源模块&…

STM32F103 标准库按键消抖实战:3种软件方案对比与10ms延时实测

STM32F103 标准库按键消抖实战:3种软件方案对比与10ms延时实测

2026/7/10 6:31:02

STM32F103按键消抖技术深度解析:从原理到三种高效实现方案在嵌入式系统开发中,按键作为最基本的人机交互接口,其稳定性和响应速度直接影响用户体验。许多开发者在使用STM32处理按键时都曾遇到过这样的困扰:明明已经检测到按键按下…

macOS源码深度解析:从Darwin内核到系统调用的工程实践

macOS源码深度解析:从Darwin内核到系统调用的工程实践

2026/7/10 6:31:02

1. 项目概述:这不是一个“安装包”,而是一份操作系统源码的完整解剖图谱“macOS (source)”这五个字,乍看像某个下载页面上不起眼的括号备注,甚至可能被误认为是某个精简版或开发者预览版的代号。但如果你在苹果官方开发者文档、X…

LingBot-Vision与LingBot-Depth 2.0:掩码深度建模的实践指南

LingBot-Vision与LingBot-Depth 2.0:掩码深度建模的实践指南

2026/7/10 6:21:01

1. LingBot-Vision与LingBot-Depth 2.0解决了什么实际问题 如果你正在处理视觉任务,特别是需要从图像中提取深度信息或进行视觉基础模型开发,LingBot-Vision和LingBot-Depth 2.0这两个开源项目值得先看环境兼容性和任务适配性。它们不是通用大模型&#…

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南

2026/7/9 19:40:56

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://gi…

6个月转型AI工程师:实战路径与核心技能

6个月转型AI工程师:实战路径与核心技能

2026/7/9 18:28:30

1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公…

YOLOv5模型剪枝与量化实战:边缘设备部署优化

YOLOv5模型剪枝与量化实战:边缘设备部署优化

2026/7/8 14:04:34

1. 项目背景与核心价值在计算机视觉领域,YOLOv5因其出色的实时检测性能成为工业界宠儿。但当我们尝试将其部署到边缘设备(如树莓派、Jetson Nano或手机终端)时,立刻会遇到两个致命问题:模型体积庞大(原始YO…

终极原神FPS解锁器完整指南:轻松突破60帧限制

终极原神FPS解锁器完整指南:轻松突破60帧限制

2026/7/10 0:00:42

终极原神FPS解锁器完整指南:轻松突破60帧限制 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 原神FPS解锁器是一款专为《原神》玩家设计的开源工具,通过先进的Wri…

YesPlayMusic:如何用高颜值播放器重塑你的网易云音乐体验?[特殊字符]

YesPlayMusic:如何用高颜值播放器重塑你的网易云音乐体验?[特殊字符]

2026/7/10 0:00:42

YesPlayMusic:如何用高颜值播放器重塑你的网易云音乐体验?🎵 【免费下载链接】YesPlayMusic 高颜值的第三方网易云播放器,支持 Windows / macOS / Linux :electron: 项目地址: https://gitcode.com/gh_mirrors/ye/YesPlayMusic…

从零实现红黑树:手写C++的set与map容器

从零实现红黑树:手写C++的set与map容器

2026/7/10 0:00:42

1. 项目概述:从STL容器到自研轮子在C的日常开发中,std::set和std::map是我们再熟悉不过的伙伴了。它们一个负责管理不重复的集合,一个负责维护键值对映射,底层都依赖一颗高效的红黑树来保证数据的有序性和操作的性能。但你是否曾想…