S19文件填充、格式互转与CRC校验一站式工具详解
简介这份资源是Vector官方HexView工具的完整安装包面向嵌入式开发与单片机编程人员用于处理S19、HEX、BIN三类常见固件文件。工具支持格式互相转换、数据填充、CRC校验和地址偏移适合在固件更新、调试分析、适应不同烧录器或项目间交换数据时使用。压缩包共19个文件体积约1.94MB内含hexview.exe主程序、多个动态链接库、官方参考手册PDF、示例hex文件以及ini配置、log运行日志和dll插件等结构简单可直接运行。已有3209人学习下载。借助附带的ReferenceManual_HexView.pdf与示例文件可快速熟悉S19记录结构、Intel HEX格式差异掌握文件转换与CRC计算的具体操作步骤并能通过expdat相关源码了解扩展接口的调用方式适合需要高效处理固件文件的开发调试场景。1. S19文件处理三件套填充、互转与CRC这份HexView.zip能省的不仅是一下午做嵌入式固件集成和发布的人几乎每天都要跟S19文件打交道。MCU编译器吐出来的S19通常是分段存储的代码段、只读数据段、配置字区各占各的地址段与段之间留着大片空白可Bootloader做整片校验时要的是连续镜像产测工装要的是BIN仿真器要的是HEX量产的固件末尾还得带上CRC。这些操作单独看都不难但靠记事本和脚本去逐段拼纯属浪费时间。我一直在用的这份HexView.zip把S19区间填充、S19/HEX/BIN三格式互转、CRC计算集成在同一个命令行工具里适合做固件发布、OTA差分和产测镜像的嵌入式工程师一条命令就能把分段S19变成可用于烧录和校验的完整镜像。2. S19格式速拆S0/S1/S2/S3的地址宽度、校验和规则与工具包入口说到底层格式之前先放个结论S19之所以难用不是因为它缺数据而是因为它“太老实”。每一行都带完整地址段与段之间的空白它一概不提导致任何按整片Flash做校验的Bootloader都看不懂。理解这一点后面所有填充、转换、CRC的步骤都顺了。2.1 一条S-record行里装了什么S19也叫Motorola S-record每一行都是可打印ASCII字符。标准的S-record行结构是Srecord_typebyte_countaddressdatachecksumrecord_type决定地址宽度byte_count是两位十六进制数它数的是“地址数据校验”三个部分的字节数不是整行长度。S0是文件头一般承载文件名不参与数据加载S1用2字节地址覆盖0x0000-0xFFFFS2用3字节地址覆盖16MBS3用4字节地址32位MCU的Flash地址比如0x08000000就得用S3来写S5是记录计数的辅助记录S7/S8/S9是终止记录标注起始执行地址。实操示例拆一行真实结构S1130200784200000000000000000000000000000030逐段拆解S116位地址记录13后面有19个字节2字节地址 16字节数据 1字节校验0200地址0x02007842 0000...16字节数据30校验字节注意byte_count是十六进制数“13”对应十进制19不要拿它当纯文本长度。32位MCU的S19文件里S3记录居多就是因为0x08000000这种地址必须用4字节才能表达完整。2.2 校验和的字节级计算规则S19的校验是“按字节求和后取反”checksum 0xFF - ((byte_count address data)的字节和 0xFF)也就是先把byte_count本身、地址字节、数据字节都当成无符号整数加起来取低8位再用0xFF减掉写成两位十六进制字符。很多工程师第一次算S19校验一头雾水其实是把byte_count漏了。用Python验证上面那行示例line S1130200784200000000000000000000000000000030 byte_count int(line[2:4], 16) # 0x13地址数据校验的总字节数 address bytes.fromhex(line[4:8]) # 2字节地址 data bytes.fromhex(line[8:40]) # 16字节数据 s byte_count sum(address) sum(data) checksum (0xFF - (s 0xFF)) 0xFF print(f低字节累加和: {hex(s 0xFF)}) print(f校验字节: {hex(checksum)}) print(校验一致 if checksum int(line[40:42], 16) else 校验不一致)这段代码里line[2:4]取的是第3到第4个字符即记录长度line[4:8]是地址的4个十六进制字符换算成2字节line[8:40]是数据的32个十六进制字符换算成16字节。累加时把长度字节也算进去这是S19校验和最容易漏的地方。输出结果里校验字节应该是0x30和行尾一致。2.3 工具包的文件构成与命令行入口HexView.zip解压后一般是图形界面主程序、命令行工具和示例脚本三样东西。我日常基本不用图形界面全部走命令行原因很简单产线脚本和CI构建没法点鼠标。命令行工具的通用调用方式如下hexview convert ./app.s19 -o ./app.bin --format bin --base 0x08000000 hexview convert ./app.bin -o ./app.s19 --format s19 --addr 0x08000000 --addr-size 32 hexview fill ./app.s19 -o ./app_filled.s19 --fill 0xFF --from 0x08000000 --to 0x08020000 hexview crc32 ./app_filled.bin --poly 0x04C11DB7 --init 0xFFFFFFFF --xorout 0xFFFFFFFF参数说明convert是格式互转入口--format指定输出格式--base指定BIN的起始地址fill是区间填充入口--fill给定填充字节值--from/--to给定填充范围crc32是校验计算入口--poly/--init/--xorout对应算法的三个核心参数。第一次用可以在终端敲hexview -h各子命令的参数会列全。3. 填充实操为Bootloader生成连续镜像一条命令补齐Flash空洞3.1 为什么要填充分段固件与Flash空白区间先说个具体场景。某跨平台系统的Bootloader只支持连续镜像读取它先整块读Flash再算CRC只要遇到空片0xFF就认为是镜像结束或非法数据。编译器输出的S19经常出现代码段结束地址和下一个段起始地址之间差了几KB甚至几十KBBootloader要么拒绝烧录要么CRC校验失败。另一个场景是OTA差分。差分包只包含改动区域远端Bootloader要先把旧镜像和差分包合并合并时旧镜像如果带空洞补丁就很难定位。先填充成连续镜像再做差分patch算法简单非常多。所以填充不是给工具面子是给Bootloader和OTA机制铺路。3.2 命令行填充步骤与参数说明hexview fill ./boot_app.s19 -o ./boot_app_filled.s19 --fill 0xFF --from 0x08000000 --to 0x0801FFFF这条命令的意义把0x08000000到0x0801FFFF整个128KB区间“看成一个连续镜像范围”范围里凡是S19没有定义数据的地方都用0xFF补上输出新的S19。已有数据不会被覆盖fill只写空洞。参数说明--from/--to是绝对地址单位字节十六进制。建议范围对齐Flash扇区大小常见4KB/8KB/64KB让Bootloader做扇区擦除时不越界。--fill只接受一个字节0xFF表示擦除态0x00常用于内部SRAM镜像。如果--to填到超过Flash实际容量的地址工具会直接产出超长记录或报错。填充后的S19文件会比原来大不少这是正常的。S1记录每行最多带32字节数据填满128KB需要四千多行记录看到文件从几十KB变几百KB别慌。3.3 填充值选0xFF还是0x00Flash空白区本来就是0xFF所以对Flash镜像填充用0xFF最直观擦除后就是全0xFF虚拟空间和物理空间一致。有一类项目会选0x00镜像要下载到外部SPI Flash或NOR Flash且Bootloader用“全0xFF或全0x00标记空块”的逻辑具体是哪个值要去翻协议定义。还有一类是配置字区EEPROM初始化值一般使能位为1未初始化就是0xFF。我之前在某项目里把填充值设成0x00结果Bootloader把填充区当成有效配置字读直接翻车。关键原则是不要凭感觉定去翻Bootloader源码里的空白判断宏通常是if (val 0xFF) continue;这种。3.4 填充边界由谁定常见做法是以链接脚本里的ROM区间为准比如0x08000000-0x08020000而不是单纯拿“已有数据的最小/最大地址”来定。某导师带的项目就吃过这个亏链接脚本定的Application区是64KBBootloader按64KB整块读只填充已有数据范围的后果是0x0800FFFF-0x08010000这些尾巴还是空CRC照样错。确定边界的顺序我一般是先看链接脚本ROM区间再对齐扇区边界最后用hexview info ./xxx.s19确认最小地址大于等于--from、最大地址小于等于--to。填充范围如果左边界设得比已有数据地址还大工具会直接把前面的数据截掉这个错最隐蔽。4. S19/HEX/BIN三格式互转转换脚本与四个边界坑4.1 三种格式能表达什么S19、Intel HEX、BIN三者的根本差异在于“地址信息放哪”。BIN是裸二进制不带任何地址文件偏移就是地址简单但容易丢上下文S19是文本流每一行都带完整地址一个文件可以描述多个不连续分段Intel HEX也是文本流用冒号开头地址4位高地址靠扩展记录补。下游工具对格式的偏好完全不同仿真器喜欢HEX量产工装喜欢固定长度的BINOTA打包喜欢S19。互转的核心就是把这些地址语义翻译对。4.2 S19转BIN数据截取与地址偏移hexview convert ./app.s19 -o ./app.bin --format bin --base 0x08000000 --length 0x20000 --pad-value 0xFF逻辑说明S19里的地址比如0x08002000处一段数据转成BIN后放到文件偏移多少这取决于--base。这里的--base0x08000000告诉工具“BIN的0偏移对应Flash地址0x08000000”那么0x08002000的数据自然落到BIN偏移0x2000。参数说明--base不填时默认取S19里最小地址这样转换结果最紧凑但可能丢失地址布局信息--length可选填了之后输出固定长度文件缺失空间用--pad-value填充。量产镜像最需要的就是这个BIN文件固定大小刷进去不会超也不会因为某段地址靠前导致后续偏移全错。4.3 BIN转S19地址回填与记录类型自动选择hexview convert ./app.bin -o ./app.s19 --format s19 --addr 0x08000000 --addr-size 32 --row-bytes 32逻辑说明BIN没有地址转S19必须指定加载地址。--addr给首地址--addr-size决定输出S116位地址还是S332位地址32位MCU就填32。--row-bytes控制每行数据量S19常见32字节一行Bootloader和烧录器基本都兼容。参数说明--addr不填工具会报错或默认从0开始这两个行为都会让固件变成“地址0的镜像”烧进MCU后中断向量表全错。转完建议用hexview info确认输出的S19记录类型和地址范围别直接丢给产线。4.4 S19转Intel HEX行宽与地址扩展记录hexview convert ./app.s19 -o ./app.hex --format ihex --row-bytes 16 --ihex-addr-mode linear逻辑说明Intel HEX以冒号开头00记录是数据01记录是文件结束04记录是扩展线性地址用来写入高16位地址。32位MCU场景下S19转Intel HEX的关键是04记录是否正确生成。--ihex-addr-mode选linear对应常见工具链如果选segment模式就用02记录但烧录驱动对segment模式的支持参差不齐。参数说明--row-bytes这里给16因为很多烧录驱动对Intel HEX每行超过16字节的容忍度不高。S19转HEX会丢掉一部分信息S5记录计数、S7/S9起始执行地址一般不会写进HEX如果烧录器有入口地址需求转完要用烧录软件单独设置或保留原始S19。5. 避坑记录五条让转换结果直接报废的细节这些坑都是我实际调固件集成时踩过的每条都按“现象→原因→解决”来写。工具本身没问题问题基本都出在没理解地址语义和校验范围上。5.1 填充后的S19被Bootloader拒收现象填充命令正常跑完文件也生成了但Bootloader报image invalid或CRC mismatch。原因填充时--from/--to只覆盖了已有数据范围段间空洞补了但范围尾巴没补到Bootloader校验的长度。比如已有数据最大地址是0x0800FFFF你--to填0x08010000Bootloader按0x08000000-0x0801FFFF整块校验0x08010000-0x0801FFFF依然是空洞。解决先看Bootloader对镜像长度的定义是整块Flash容量还是链接脚本ROM区大小。我一般先用hexview info ./xxx.s19看最小/最大地址再把--to对齐到Flash扇区边界而不是对齐到“最大地址”。5.2 CRC计算值总比Bootloader多4个字节现象本地算出的CRC32烧进去Bootloader校验失败每次差的就是末尾那4个字节。原因Bootloader的CRC计算范围是预留区里的“整段有效数据”而你的镜像末尾带了4字节的CRC自身或者工具把S19里S5记录、终止记录也读进了BIN。最常见的是把S19文本流直接丢给CRC函数算出来当然对不上。解决确认计算范围。常见做法是先转换/填充得到固定大小的BIN再按完整镜像算CRC最后把CRC追加写入固定偏移。不要把S19文本、HEX文本直接交给CRC函数。5.3 BIN文件开头多出0x800个零字节现象从S19转出来的BIN开头多了一堆00长度也不对。原因S19里第一个数据地址不是从0x08000000开始而是0x08000800--base没指定时工具取了最小地址0x08000800文件会很小或者指定base后工具按base前面补了pad。两种情况都会让人以为数据错位。解决明确--base就是“BIN的0偏移对应哪个Flash地址”。如果不想保留偏移就选S19最小地址做base如果想要固定布局就用--length让文件长度可控。转换完用十六进制查看器看文件头几个字节和S19首段的地址数据对一下。5.4 S19转HEX后地址被整体重排现象0x08000000开头的S19转成HEX后数据全在高地址烧录器提示地址溢出。原因Intel HEX的地址记录是分层的如果工具把04扩展线性地址当成普通数据行或者32位S3地址写入04记录时没处理好高16位烧录器解析就会把数据放到错误的地址空间。解决转完先用hexview info看HEX的地址区间再用支持HEX解析的烧录软件回读校验。命令行里确认--ihex-addr-mode选的是linear跨片区段很多时linear模式配合04记录是通用做法不要选segment。5.5 大范围填充后S19文件膨胀到几MB现象填充512KB Flash区间生成的S19文件从几十KB变成几MB打开都卡。如果再拿这个S19继续转换工具直接无响应。原因S19是文本格式每行最多带32字节数据512KB的填充数据会被拆成约1.5万行文本文件和索引都大处理慢是正常的。解决尽量不要在S19上反复做中间格式转换。常见做法是先转成BIN再用二进制方式处理填充最后转回S19。这比在S19文本里来回倒腾快一个数量级。6. 把CRC写回发布镜像计算范围、字节序与整体流程6.1 四类常见CRC参数对照算法多项式初值输出异或适用场景CRC320x04C11DB70xFFFFFFFF0xFFFFFFFF多数MCU BootloaderCRC32C0x1EDC6F410xFFFFFFFF0xFFFFFFFF新架构、存储校验CRC16-CCITT0x10210xFFFF0x0000低速总线协议CRC32/MPEG-20x04C11DB70xFFFFFFFF0x00000000部分产线定制选参数不要只看多项式初值和输出异或不对结果差十万八千里。拿到Bootloader源码时把里面CRC表的初值和最终异或翻出来核对。6.2 计算范围与预留位处理hexview crc32 ./app.bin --poly 0x04C11DB7 --init 0xFFFFFFFF --xorout 0xFFFFFFFF --align 4计算范围就是填充完的完整BIN而不是S19文件本身。--align 4是对齐4字节CRC按字走位时末字节好处理。如果Bootloader在镜像末尾预留了4字节CRC位置计算前要先把这4个字节填充为0xFFFFFFFF或0x00000000看协议算完再把结果写回否则就是把自己算进去了。字节序也要注意有的Bootloader按小端解析CRC有的按大端写回时把算出的4字节按协议要求的顺序排。这个坑我至少见过两次都是烧完打印校验值才发现的。6.3 发布流程串联hexview convert ./app.s19 -o ./app.bin --format bin --base 0x08000000 --length 0x20000 --pad-value 0xFF hexview crc32 ./app.bin --poly 0x04C11DB7 --init 0xFFFFFFFF --xorout 0xFFFFFFFF --output crc.txt hexview convert ./app.bin -o ./app_final.s19 --format s19 --addr 0x08000000 --addr-size 32 --row-bytes 32三条命令串起来就是发布流程先把分段S19变成固定长度的连续BIN再算CRC最后转回带地址的S19给Bootloader。从那以后我每次出固件都强制走一遍这个流程先info看地址边界再填充、转BIN、算CRC、回S19一步不跳。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

合成数据能否持续带来收益:质量、覆盖度与分布退化

合成数据能否持续带来收益:质量、覆盖度与分布退化

合成数据能否持续带来收益:质量、覆盖度与分布退化 系列:大模型研究争议:从论文结论到证据边界日期:2026-10-13 摘要:数据更多,为什么仍可能越学越窄? 模型能不断生成新题目、新答案&#xff…

2026/10/14 18:59:14 阅读更多 →
企业微信RPA自动化如何对接业务系统,实现消息驱动业务闭环

企业微信RPA自动化如何对接业务系统,实现消息驱动业务闭环

做企业微信外部群自动化开发久了,发现很多开发者容易踩同一个坑:只完成消息收发,却没有打通内部业务系统。消息能力单独跑起来看着没问题,但没法落地到真实业务流程,最后自动化脚本只能闲置。 之前项目里,…

2026/10/14 18:59:14 阅读更多 →
STM32F103C8T6驱动DHT11时序调试与稳定读取实战

STM32F103C8T6驱动DHT11时序调试与稳定读取实战

简介:本资源是一套面向STM32初学者的DHT11温湿度传感器实战开发包,聚焦单总线通信原理与嵌入式外设驱动实践,适用于ARM Cortex-M3架构下的STM32F103C8T6最小系统学习与项目验证。压缩包共377个文件,涵盖76个头文件(h&a…

2026/10/14 18:59:14 阅读更多 →

最新新闻

AI提效后的人往哪放?转向获客侧完整落地指南

AI提效后的人往哪放?转向获客侧完整落地指南

我去年一直在折腾一个特别实际的问题:AI 把团队的重复劳动吃掉之后,省下来的这批人到底应该往哪放?一开始我也跟大多数人想的一样,觉得放着就行,反正效率高了嘛。后来发现完全不是这么回事,人一旦没有明确的…

2026/10/14 23:49:10 阅读更多 →
Python multiprocessing Pool:apply 与 apply_async 性能差异深度实测

Python multiprocessing Pool:apply 与 apply_async 性能差异深度实测

这几天有朋友在群里问了一个很有意思的问题:Python 的multiprocessing.Pool里,apply和apply_async到底差在哪,为什么感觉一个快一个慢,尤其是把multiprocessing.Pool、pool.Pool、pool.ThreadPool这几个概念混在一起时&#xff0c…

2026/10/14 23:49:10 阅读更多 →
酷秒神马9.0 2025版源码拆解:高并发秒杀系统的缓存、削峰与防超卖实战

酷秒神马9.0 2025版源码拆解:高并发秒杀系统的缓存、削峰与防超卖实战

做高并发秒杀系统的人,应该都听过“酷秒神马”这个开源源码系统的名字。9.0 2025版发布之后,我第一时间把源码拉下来完整过了一遍。这套系统主打“低代码配置 高并发底座”,核心思路是把秒杀场景里最难的几个点——热点数据缓存、流量削峰、…

2026/10/14 23:49:10 阅读更多 →
用Go实现doomsday算法:纯计算推算任意日期星期几

用Go实现doomsday算法:纯计算推算任意日期星期几

第一次看到 doomsday 这个词的时候,我第一反应是:这该不会是某种末日倒计时程序吧。后来在日历算法的资料堆里翻了一圈才发现,这是一套相当精巧的日期推算套路,可以让你不看日历、不查手机,也能快速说出任意日期是星期…

2026/10/14 23:49:10 阅读更多 →
基于MATLAB Simulink的MIL与SIL单元测试实战指南

基于MATLAB Simulink的MIL与SIL单元测试实战指南

1. 项目概述:为什么要做MIL和SIL单元测试搞过嵌入式控制开发的朋友应该都有体会,模型越来越大、逻辑越来越复杂之后,光靠眼睛看和手点仿真已经撑不住了。尤其做汽车电控、无人机飞控、机器人控制这类对安全性要求高的项目,一个不起…

2026/10/14 23:49:10 阅读更多 →
304与316不锈钢紧固件怎么选?成分差异、耐腐蚀对比与采购验收全解

304与316不锈钢紧固件怎么选?成分差异、耐腐蚀对比与采购验收全解

看到这个标题,我猜很多人第一反应是"不就是螺丝嘛,304还是316,哪个贵买哪个"。但实际接触过工业采购、设备维护或者出海项目的人,都知道这个选择经常让人头疼——选贵了,成本上去了;选错了&#…

2026/10/14 23:48:05 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →