汇编实验五深度解析:段寄存器与多段程序实战
1. 实验五到底在练什么先搞懂它的设计意图很多人翻到《汇编语言》王爽这本书的实验五第一反应是“怎么突然让我写这么多段代码”第二反应是“这些段和段之间到底什么关系”。我带过不少刚学汇编的朋友几乎所有人卡在实验五都不是因为指令不会写而是没搞清楚这个实验到底想训练什么能力。实验五的核心训练目标其实就三个第一让你亲手体会代码段、数据段、栈段在内存里是怎么分开摆放的第二让你理解段寄存器CS、DS、SS各自管什么以及它们和偏移地址配合起来怎么定位到具体的内存单元第三让你学会用assume伪指令做段与段寄存器的关联声明并且明白这个声明只是给汇编器看的CPU本身并不认。这三点听起来像老生常谈但真正动手写的时候你会发现每一个都藏着坑。我先说清楚这个实验适合谁看。如果你正在自学汇编或者上课跟到实验五觉得有点懵那这篇内容就是给你写的。如果你已经能闭着眼睛写出带多个段的完整程序那你可以直接跳到第四节的排查技巧那里有几个连老手都容易翻车的地方。整篇内容我会按照“设计思路—细节拆解—完整实操—问题排查”的顺序展开每一步都告诉你为什么这么做而不只是贴一段代码让你抄。在正式开始之前我需要先交代一个背景。实验五的题目通常要求你写一个包含多个段的程序比如定义数据段存放若干字节定义栈段预留空间然后在代码段里完成数据的搬运或者计算。这个过程中你会反复用到mov ax, 段名这种写法也会反复看到assume出现在程序开头。很多人抄代码的时候把assume当摆设结果一运行发现数据取错了这就是没理解设计意图的典型表现。提示实验五的代码量不大但它是后面所有复杂程序的基础。如果你在这里偷懒只抄代码不思考后面学到中断和端口操作时会加倍还回来。我个人的建议是在做实验五之前先把实验四的代码重新默写一遍。实验四让你把数据、代码、栈放在同一个段里实验五则是把它们拆开。这个从“合”到“分”的过程正是理解段式内存管理的关键跳跃。你如果能把实验四和实验五的代码放在一起对比会发现很多之前没注意到的细节比如栈顶指针SP的初始值到底该设多少比如数据段里的数据在内存中是从低地址往高地址排还是反过来。2. 段与段寄存器把“分开摆放”这件事讲透2.1 为什么要把数据、代码、栈拆成不同的段在实模式下内存地址由“段基址×16 偏移地址”得到。这个机制决定了你完全可以把所有东西塞进一个段里只要偏移地址不冲突就行。实验四就是这么干的代码、数据、栈挤在同一个64KB的空间里。那为什么实验五非要拆开原因有两个一个是逻辑清晰另一个是容量限制。先说逻辑清晰。当你把数据放在数据段、代码放在代码段、栈放在栈段程序的结构就变得非常直观。你看到DS就知道要去数据段取东西看到CS就知道当前执行到哪条指令看到SS就知道栈操作会影响哪里。这种分工在调试的时候尤其重要因为你可以单独检查某一个段的内容而不用在一大坨内存里大海捞针。再说容量限制。一个段最大64KB如果你把数据、代码、栈全塞在一起稍微大一点的程序就会溢出。拆成多个段之后每个段都有自己独立的64KB空间程序的规模上限就大大提高了。虽然实验五的程序很小远不到64KB但这个设计习惯必须从最开始就养成。注意拆段之后段与段之间并不是物理上隔离的它们仍然共享同一片内存空间。所谓“拆开”只是逻辑上的划分实际内存里它们可能紧挨着也可能隔很远取决于操作系统怎么分配。2.2 CS、DS、SS各自管什么别搞混CS是代码段寄存器它和IP配合指向CPU下一条要执行的指令。你不需要手动去设置CS因为CPU在执行程序时会自动根据入口地址设置好。DS是数据段寄存器它和偏移地址配合指向你要访问的数据。这个寄存器需要你手动设置通常是在程序开头用mov ax, data和mov ds, ax两句话完成。SS是栈段寄存器它和SP配合指向栈顶。同样需要你手动设置用mov ax, stack和mov ss, ax然后紧接着设置SP。这里有一个非常容易出错的地方设置SS和SP的时候中间不能插入其他指令。因为SS和SP是配合工作的如果你先改了SS还没来得及改SP这时候发生中断CPU会用旧的SP和新的SS去定位栈顶结果就是栈错位。所以正确的写法是连续两条指令中间不插任何东西。我见过有人写成这样mov ax, stack mov ss, ax mov sp, 16 mov ax, data mov ds, ax这段代码看起来没问题但实际上在mov ss, ax和mov sp, 16之间插了mov ax, data吗没有它是连续的。但如果你写成mov ax, stack mov ss, ax mov ax, data mov sp, 16 mov ds, ax这就出问题了。mov ax, data插在了SS和SP之间虽然大多数情况下不会立刻出错但这是一个隐患。正确的做法是把设置SS和SP的指令紧挨着写然后再去设置DS。2.3 assume是给谁看的别把它当CPU指令assume是汇编伪指令它的作用是告诉汇编器“我后面用到的某个段名对应的是某个段寄存器。”比如assume cs:code, ds:data, ss:stack意思是告诉汇编器code段对应CSdata段对应DSstack段对应SS。这样汇编器在编译的时候就能正确计算出每个段的偏移地址。但CPU根本不认识assume。你在程序里写了assume编译成机器码之后assume就消失了CPU看到的只有mov、add这些真实指令。所以如果你只写了assume却没有用mov去实际设置DS和SS那CPU还是不知道数据段和栈段在哪里。这就是为什么程序开头必须手动设置DS和SS的原因。我经常用一个类比来解释这件事assume就像你在快递单上填了收件人地址但快递员实际送货的时候还是要靠导航mov指令才能找到地方。填单子assume是给系统看的导航mov才是真正走路的依据。两者缺一不可但作用完全不同。3. 完整实操从零写一个多段程序3.1 题目要求与整体结构规划实验五的典型题目是定义数据段存放若干字节数据定义栈段预留一定空间在代码段中把数据段的内容逆序复制到另一个位置或者完成某种搬运。我以“将数据段中的8个字节逆序存放到数据段末尾”为例带你走一遍完整流程。整体结构规划如下数据段定义8个字节的源数据再预留8个字节的目标空间。栈段预留16字节空间用于临时存放数据。代码段设置DS和SS然后用栈作为中转把源数据逆序搬到目标空间。这个方案的好处是充分利用了栈的“后进先出”特性逆序操作天然适合用栈来完成。你如果不用栈就得用额外的寄存器做交换代码会更复杂。3.2 数据段和栈段的定义细节数据段的定义如下data segment db 1, 2, 3, 4, 5, 6, 7, 8 db 0, 0, 0, 0, 0, 0, 0, 0 data ends第一行定义了8个源数据第二行预留了8个字节的目标空间初始值全是0。这里用db而不是dw是因为题目要求按字节操作。如果你用dw每个数据占2个字节偏移地址的计算就不一样了。栈段的定义如下stack segment db 16 dup(0) stack ends16 dup(0)表示重复16次0也就是预留16个字节的空间。为什么是16因为我们要压入8个字节每个字节压栈时SP减2实模式下栈操作按字进行8个字节需要16字节的栈空间。如果你只预留8字节压到第5个字节的时候栈就溢出了。提示栈空间的预留一定要大于实际需要。我一般会预留实际需要的两倍防止中间有其他操作占用栈空间。3.3 代码段设置段寄存器与逆序搬运代码段的完整写法如下code segment start: mov ax, stack mov ss, ax mov sp, 16 mov ax, data mov ds, ax mov bx, 0 mov cx, 8 mov si, 0 push_loop: mov al, [si] mov ah, 0 push ax inc si loop push_loop mov si, 8 mov cx, 8 pop_loop: pop ax mov [si], al inc si loop pop_loop mov ax, 4c00h int 21h code ends end start这段代码的逻辑是先用一个循环把数据段的前8个字节依次压栈然后再用一个循环从栈中弹出依次存放到数据段的后8个字节。因为栈是后进先出所以弹出的顺序和压入的顺序正好相反实现了逆序。这里有几个细节需要展开讲。第一mov ah, 0的作用是清空AH寄存器。因为push ax是压入一个字16位而我们只需要低8位的AL高8位的AH如果不清零压入的就是垃圾数据。虽然弹出的时候我们只取AL但养成清零的习惯没坏处。第二mov si, 8是在弹出循环之前设置的。因为目标空间的起始偏移是8前8个字节是源数据所以SI从8开始每弹出一个字节就加1直到填满8个字节。第三loop指令使用的是CX寄存器所以两个循环都用了CX作为计数器。第一个循环CX8第二个循环CX重新设为8。如果你忘了重置CX第二个循环会直接跳过因为CX已经减到0了。3.4 编译、链接与调试的完整流程写完之后你需要用汇编器比如MASM编译成OBJ文件再用链接器比如LINK链接成EXE文件。具体命令如下masm test5.asm; link test5.obj;编译和链接过程中如果报错最常见的原因是段名拼写不一致或者assume语句写错了。比如你把data段定义成data但assume里写成了datas汇编器就会报“未定义的段”错误。链接成功之后用调试器比如DEBUG加载EXE文件单步执行观察寄存器和内存的变化。重点看三个地方第一DS是否指向了数据段的基址第二SS和SP是否设置正确第三压栈和弹栈过程中栈顶指针SP是否按预期变化。我个人的习惯是在DEBUG里用d ds:0查看数据段的内容用r查看寄存器状态用t单步执行。每执行几条指令就检查一次确保每一步都符合预期。这样即使出错也能立刻定位到是哪条指令的问题。4. 常见问题与排查技巧实录4.1 数据取错DS没设对或者assume写错了这是实验五最高频的问题。现象是你明明在数据段里定义了1,2,3,4,5,6,7,8但程序跑起来取到的却是别的值。原因通常有两个一是DS没有正确指向数据段二是assume语句写错了导致汇编器计算偏移地址时出错。排查方法在DEBUG里用r命令查看DS的值然后用d DS:0查看该地址开始的内存内容。如果内容和你在数据段里定义的不一样说明DS指向错了。这时候回去检查mov ax, data和mov ds, ax这两条指令看看data段名是否和定义的一致。assume写错的情况更隐蔽。比如你定义了data段但assume里写的是assume cs:code, ds:datas汇编器会认为datas是一个未定义的段编译时报错。但如果assume里写的是assume cs:code, ds:data, ss:stack而你的栈段名实际叫stacks那链接时可能不报错但运行时SS会指向错误的位置。注意assume里的段名必须和segment定义时的段名完全一致包括大小写。虽然汇编器通常不区分大小写但为了可读性建议统一用小写。4.2 栈溢出SP初始值设错了栈溢出的现象是程序运行到某一步突然崩溃或者弹出的数据和压入的数据对不上。原因通常是SP的初始值设得太小或者压栈次数超过了预留空间。排查方法在DEBUG里用r命令查看SP的值然后单步执行压栈指令观察SP是否在合理范围内变化。如果SP减到了0或者负数说明栈溢出了。这时候需要增大栈段的预留空间或者减少压栈的数据量。我个人的经验是栈段的预留空间至少要是实际需要的两倍。比如你需要压入8个字节那就预留16字节需要压入16个字节那就预留32字节。这样即使中间有中断或者其他操作占用了栈空间也不会溢出。4.3 死循环CX没重置或者loop用错了死循环的现象是程序卡在某一段代码里出不来DEBUG里看到IP一直在某个小范围内跳来跳去。原因通常是loop指令的计数器CX没有正确设置或者loop跳转的标签写错了。排查方法在DEBUG里单步执行观察CX的值。如果CX在循环体内被其他指令修改了loop就会提前结束或者永远不结束。比如你在循环体内用了mov cx, 8那loop的计数器就被覆盖了循环次数就不对了。正确的做法是在进入循环之前设置好CX循环体内不要修改CX除非你明确知道自己在做什么。如果循环体内必须用CX做其他事情那就先把CX压栈保存循环结束后再弹出来恢复。4.4 常见问题速查表问题现象可能原因排查方法解决方案数据取错DS未正确设置DEBUG查看DS和内存检查mov ds, ax指令数据取错assume段名写错检查assume语句统一段名大小写栈溢出SP初始值太小DEBUG查看SP变化增大栈段预留空间栈溢出压栈次数过多统计压栈指令数量减少压栈数据或增大栈死循环CX未重置DEBUG查看CX值循环前重新设置CX死循环loop标签写错检查loop跳转地址修正标签名编译报错段名不一致检查segment和assume统一段名链接报错缺少end start检查程序末尾添加end start4.5 几个连老手都容易翻车的细节第一个细节设置SS和SP之间不要插入其他指令。前面已经说过这里再强调一次。因为SS和SP是配合工作的中间插入指令可能导致中断发生时栈错位。虽然实验五的程序很简单中断概率很低但这个习惯必须从最开始就养成。第二个细节压栈时注意是压字还是压字节。实模式下push指令默认压入一个字16位如果你只想压入一个字节需要先把高8位清零然后push整个字。弹出的时候也要注意pop出来的是一个字你只取低8位的话高8位是垃圾数据。第三个细节数据段和栈段的顺序会影响偏移地址。如果你把数据段定义在栈段前面数据段的偏移地址从0开始如果定义在后面偏移地址就不一样了。虽然实验五的程序不依赖具体的偏移地址但养成关注段顺序的习惯对后面写复杂程序很有帮助。第四个细节DEBUG里的t命令和p命令区别很大。t命令是单步执行遇到loop会进入循环体p命令是单步执行遇到loop会直接执行完整个循环。调试循环的时候用p命令更高效但如果你想观察循环体内的细节就得用t命令。5. 从实验五延伸出去这些能力后面会反复用到实验五看起来只是一个小练习但它训练的能力在后面会反复出现。比如你学到中断的时候需要设置中断向量表那时候你会再次用到段寄存器的设置而且要求更严格因为中断向量表的地址是固定的。再比如你学到端口操作的时候需要把数据从内存搬到端口那时候你会再次用到数据段和偏移地址的配合。我个人的体会是实验五最大的价值不是让你写出一个能跑的程序而是让你养成“先规划段再写代码”的习惯。很多人写汇编的习惯是想到哪写到哪结果代码越写越乱段寄存器到处改最后自己都搞不清楚DS指向哪里。如果你从实验五开始就养成好习惯每个段各司其职段寄存器只在程序开头设置一次后面不再乱改那你的汇编代码会清晰很多。还有一个延伸点是栈的灵活运用。实验五用栈来做逆序这只是栈的一个简单应用。后面你学到子程序调用的时候栈会自动保存返回地址学到中断的时候栈会自动保存标志寄存器和CS、IP。如果你在实验五就把栈的机制吃透了后面这些内容理解起来会轻松很多。最后再分享一个小技巧如果你在DEBUG里调试多段程序可以用u命令反汇编代码段用d命令查看数据段用r命令查看寄存器。这三个命令配合使用基本上能定位到绝大多数问题。我调试汇编程序的时候通常会把这三个命令的窗口并排放在一起一边单步执行一边观察变化效率比盲目改代码高得多。这个实验后续还可以这样扩展把逆序搬运改成排序或者把数据段的内容加密后再搬运或者用多个数据段做数据交换。每扩展一次你对段式内存管理的理解就会深一层。等你把这些扩展都做完了再回头看实验五你会发现它其实是一个起点而不是一个终点。

相关新闻

Python+Django+Vue视频点播系统开发实战:从架构到部署避坑

Python+Django+Vue视频点播系统开发实战:从架构到部署避坑

简介:基于 Python 语言,结合 Django 框架与 Vue.js 前端技术,构建了一套完整的视频点播网站毕业设计项目。系统围绕用户认证、视频分类、在线播放与管理等核心功能展开,涵盖后端逻辑、前端展示、数据库管理和媒体资源存储等关键环…

2026/10/14 23:17:13 阅读更多 →
AI逆向工程实战:31.2K Star开源项目如何用大模型辅助二进制分析

AI逆向工程实战:31.2K Star开源项目如何用大模型辅助二进制分析

1. 当AI把目光投向二进制:逆向工程的门槛正在被重新定义第一次看到“AI开始搞逆向工程”这个说法,我的反应是:终于有人把这件事摆到台面上了。逆向工程这个领域,长期以来都是少数人的游戏——你得懂汇编,得熟悉PE/ELF文…

2026/10/14 23:16:08 阅读更多 →
ARM架构CentOS 7.6网口bond配置实战与优化指南

ARM架构CentOS 7.6网口bond配置实战与优化指南

1. 项目背景与需求拆解1.1 为什么要在ARM架构的CentOS 7.6上做网口bond在ARM服务器上跑CentOS 7.6,这个组合本身就有点意思。ARM架构的服务器这几年在数据中心里越来越常见,尤其是那些对功耗敏感、需要高密度部署的场景。但ARM平台跟传统的x86平台在硬件…

2026/10/14 23:16:08 阅读更多 →

最新新闻

铝合金仿菠萝格木纹构件|复刻珍木肌理 以铝代木筑就恒久中式雅境

铝合金仿菠萝格木纹构件|复刻珍木肌理 以铝代木筑就恒久中式雅境

在中式院墅、古建廊道与高端商业中式营造中,菠萝格木素来以温润醇厚的棕红肌理、沉稳致密的质感著称,是檐廊、立柱、门楣、雀替等构件的优选名贵硬木。然而天然菠萝格造价高昂,户外应用易开裂变形、虫蛀腐朽,常年刷漆维护成本高,让许多项目难以兼得木韵质感与长久耐用。铝合金仿…

2026/10/15 0:02:51 阅读更多 →
影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

2026/10/15 0:02:51 阅读更多 →
2026钢铁行业AI转型系统选型对比:多基地排产、大宗原燃料采购与质量追溯,六家厂商能力差异在哪里

2026钢铁行业AI转型系统选型对比:多基地排产、大宗原燃料采购与质量追溯,六家厂商能力差异在哪里

2026钢铁行业AI转型系统选型对比:多基地排产、大宗原燃料采购与质量追溯,六家厂商能力差异在哪里钢铁企业的AI转型采购决策在2026年面对三项刚性约束:吨钢利润承压,要求AI直接作用于采购成本与排产效率;多基地、多产线…

2026/10/15 0:02:51 阅读更多 →
防关联浏览器哪个好?三款产品实测差异解析

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:51 阅读更多 →
SpringBoot+Vue3社区养老服务平台全栈项目实战解析

SpringBoot+Vue3社区养老服务平台全栈项目实战解析

1. 项目背景与整体技术选型近两年社区养老信息化一直是个热门的落地方向,特别是“人口老龄化社区服务与管理平台”这种系统,典型的SpringBoot 2 Vue 3组合,配合MyBatis-Plus和MySQL 8.0,基本就是Java Web全栈项目的标准答案。我前…

2026/10/15 0:00:46 阅读更多 →
示波器实战笔记:从选型参数到测量排查的完整指南

示波器实战笔记:从选型参数到测量排查的完整指南

做硬件这些年,如果只允许工位上留一台仪器,我会毫不犹豫留下示波器。数字万用表能告诉你电压对不对,但很难告诉你为什么不对;逻辑分析仪告诉你高低电平的时序,却看不到毛刺和边沿过冲;唯有示波器把“状态”…

2026/10/15 0:00:46 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战: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 阅读更多 →