C/C++源字符集与执行字符集:乱码根源与配置指南
如果你写过C/C程序大概率遇到过这种事代码在编辑器里显示得清清楚楚注释里的中文也一切正常可一旦编译运行printf打印出来的中文字符串就变成了一堆“鏂囧瓧”之类的天书。还有更诡异的同一份源码在Linux上编译没问题放到Windows上用MSVC一编译蹦出一大堆C4819警告甚至程序直接无法运行。这些问题的源头往往不是你的代码逻辑而是“源文件编码”和“执行字符集”之间的对应关系没有处理好。今天这篇我就重点聊一聊source-charset和execution-charset到底是什么编译器是如何使用它们的以及你在日常工程中该怎么配置才能少踩坑。1. 概念基础源字符集与执行字符集的本质区别1.1 字符集、编码与代码页的基本概念在深入source-charset和execution-charset之前先把几个语义容易混淆的词理清。字符集Character Set是一个抽象的字符集合比如“英文字母”、“汉字”、“日文假名”都属于字符。编码Encoding则是把字符集中的每个字符映射成计算机可以存储的字节序列。同一个字符集可以有多种编码方式比如Unicode字符集就有UTF-8、UTF-16、UTF-32等编码同一个汉字“中”在GBK里是0xD6D0在UTF-8里是0xE4B8AD这不叫字符集不同而是编码规则不同。代码页Code Page是另一层概念常见于Windows系统它把一组编码规则编成一个编号比如936就是GBK65001就是UTF-8。程序员经常听到的“控制台代码页”就是指控制台输出时解释字节流所用的编码规则。这个细节很重要因为即使在同一个操作系统上编译器的“执行字符集”和运行终端的“显示编码”也不是一回事任何一个环节脱节都会呈现乱码。1.2 源字符集和执行字符集编译器眼中的两套编码大多数C/C初学者会把“源代码里写的是什么编码”和“程序运行时字符串用什么编码”当成一回事。实际上编译器在处理你的代码时必然要区分两个不同的字符集。源字符集source-charset指C/C源文件本身保存时所采用的字符编码。也就是你打开源文件编辑器里看到的那些字符包括注释、标识符、字符串字面量中的字符在磁盘上的字节表示。执行字符集execution-charset指编译器在生成的目标文件里为字符字面量和字符串字面量中的字符所选择使用的编码。程序运行时这些字符串在内存里就是执行字符集编码后的字节序列。举个例子你的源文件用UTF-8保存里面写了一句printf(中文);。如果编译器指定执行字符集为GBK那么在生成的机器码中中文实际上会被转换成一串GBK字节运行时输出的是GBK。如果终端恰好也用GBK显示那没问题如果终端是UTF-8那肯定乱码。这里你就明白了源字符集只是“输入”时的解读方式执行字符集才是程序运行时真正暴露给外部世界的编码。1.3 为什么差异会产生乱码乱码的本质是同一个字节流被错误的解码规则解读了。当source-charset和execution-charset没有正确配置或者说编译器在转换过程中丢失了信息就会出现以下几种情况编译器无法识别源文件中的字节序列。比如源文件是GBK编码但你让编译器按UTF-8去解析那么某些汉字字节序列可能被当作非法字符轻则警告重则直接报错。编译器虽然识别了源文件但在转换到执行字符集时目标字符集并不包含该字符。比如你的源文件里有特殊符号“”执行字符集是GBK里没有这个字符编译器就会报错或生成替代符。转换本身正确但运行时终端/控制台使用的编码与执行字符集不符比如执行字符集是GBK但终端是UTF-8。所以想要彻底掌控乱码就必须同时关注“源文件怎么存”、“编译器怎么转”、“运行时环境怎么显示”三个环节。source-charset和execution-charset正是前两个环节的开关。2. 编译器的字符集映射机制2.1 C/C翻译阶段中的编码转换C/C标准把编译过程划分为翻译阶段在早期阶段就涉及字符集处理。具体来说阶段1把物理源文件字符映射到源字符集阶段5开始处理字符字面量和字符串字面量并把它们转换为执行字符集中的对应字符。标准规定任何不在基本源字符集中的字符都要通过某种方式表示为以通用字符名UCN开头的转义序列但现代编译器一般都会用编码转换的方式直接处理。一个关键点在于编译器的“源字符集”和“执行字符集”设置并不改变源文件的物理字节而是负责“如何解读”和“如何输出”。如果把源文件比作一份手稿编译器就像一个翻译员它先按source-charset认识手稿上的字再按execution-charset把这些字重新写在另一本“可供程序运行的册子”上。如果翻译员不认识某些字或者目标册子不允许出现这些字那就会出问题。2.2 GCC的-finput-charset与-fexec-charset在GCC/Clang中相关选项是-finput-charset和-fexec-charset注意这里用的是“input/execution”而不是“source/execution”。默认情况下GCC会假设源文件是UTF-8但执行字符集在大多数平台上默认也是UTF-8。如果你的源文件确实是UTF-8那什么也不用管如果源文件是GBK就要加上-finput-charsetGBK否则编译器会按UTF-8去解读GBK字节轻则“武装乱码”重则编译报错。# 源文件是GBK想让程序运行时也输出GBK gcc -finput-charsetGBK -fexec-charsetGBK -o app main.c # 源文件是GBK想让程序运行时输出UTF-8 gcc -finput-charsetGBK -fexec-charsetUTF-8 -o app main.c-fexec-charset的作用范围仅限于字符字面量和字符串字面量不包括注释和普通代码。也就是说注释里的汉字无论什么编码只要编译器能跳过就行不会影响运行时的字符串。但-finput-charset会影响编译器对所有源文件字符的解读包括标识符和字符串内容。GCC底层通过iconv库来执行编码转换因此你可以在选项里填上iconv支持的任何编码名称比如GB18030、BIG5、SHIFT_JIS等。2.3 MSVC的/source-charset与/execution-charset在Windows开发中最常见的MSVC编译器对应概念使用不带f的/source-charset和/execution-charset选项。这两个选项在Visual Studio 2015 Update 2之后引入目的是让用户明确控制源文件和执行字符集。用法是cl /source-charset:utf-8 /execution-charset:gbk main.cpp比较实用的一个快捷选项是/utf-8它等价于同时指定/source-charset:utf-8 /execution-charset:utf-8。如果你的项目明确要统一使用UTF-8直接在“项目属性-C/C-命令行”中加上/utf-8就是最省事的方式。MSVC的默认行为相对复杂一点如果没有指定/source-charset编译器会尝试通过检测源文件是否带BOM字节顺序标记来判断编码。带UTF-8 BOM则按UTF-8处理否则按当前系统代码页例如简体中文Windows就是GBK处理。如果你用Visual Studio默认新建一个文件保存时往往带BOM所以不设置也能正确编译。但如果你用其他编辑器保存了不带BOM的UTF-8文件MSVC会误认为它是GBK接下来就会产生警告C4819甚至编译错误。2.4 编译器默认值与其不确定性这里想提醒一下很多问题的出现是因为“默认值”并不是绝对可靠的。GCC在Linux上默认-finput-charset为UTF-8但如果某个源文件是从Windows拷贝过来的保存成了GBK那你在Linux上编出来的程序运行时打印中文字符串大概率是乱的因为UTF-8源文件被错误地按GBK解读不恰恰相反GBK文件被当成了UTF-8解读字符串内容早就错了。MSVC在Windows上的默认值又跟系统区域相关同一个文件拷到不同语言的Windows上行为还不一样。因此一个负责任的工程必须在构建配置里显式声明源字符集和执行字符集而不是依赖“默认碰运气”。3. 实操正确配置source-charset与execution-charset3.1 Linux下GCC的完整配置示例先在Linux上完整演示一遍。假设你有一个hello.c源文件是GBK编码在Windows上经常见到。你可以用file命令查看实际编码file -i hello.c hello.c: text/plain; charsetunknown-8bit如果不确定具体是GBK还是GB2312可以用iconv命令来探测和转换。现在直接编译分别输出UTF-8和GBK的程序#include stdio.h int main() { printf(Hello中文世界\n); return 0; }第一步先用GBK源文件加参数编译gcc -finput-charsetGBK -fexec-charsetUTF-8 -o hello_utf8 hello.c ./hello_utf8如果终端是UTF-8输出正常。如果想让程序输出GBK字节流gcc -finput-charsetGBK -fexec-charsetGBK -o hello_gbk hello.c ./hello_gbk | iconv -f GBK -t UTF-8第二条命令通过iconv把输出转成UTF-8显示说明程序内部确实是GBK字节。这里要注意-fexec-charset只影响字符串和字符字面量不会影响源码里的普通字符。如果你需要把整个程序的内部处理当成UTF-8来写那源文件最好本身也是UTF-8这样才能减少转换中可能出现的损耗。3.2 Windows下MSVC的配置方法在Windows命令行中可以用cl.exe编译。先看一个不带BOM的UTF-8文件hello.cpp#include stdio.h int main() { printf(中文测试\n); return 0; }如果直接用cl /EHsc hello.cpp编译MSVC会按系统代码页解读文件。在这个场景下如果文件是UTF-8无BOM编译器会把UTF-8的字节误判为GBK导致字符串显示乱码。正确做法是cl /EHsc /utf-8 hello.cpp这会把源文件按UTF-8解读并让执行字符集也是UTF-8。但别忘了Windows控制台默认可能使用GBK或旧版代码页即使程序输出UTF-8控制台也可能显示乱码。这时除了设置编译器还需要在运行时调整控制台代码页#include stdio.h #include windows.h int main() { SetConsoleOutputCP(CP_UTF8); printf(中文测试\n); return 0; }或者在程序外部使用chcp 65001命令切换控制台代码页。这也印证了前文说的“运行时环境”是第三因素。如果你使用的是Visual Studio IDE可以在“项目属性”对话框中搜索“字符集”然后选择“使用多字节字符集”还是“使用Unicode字符集”但这两者控制的是Windows API的编码宏并不是直接的/execution-charset。要精确控制需要去“C/C-命令行”里手动添加/utf-8或/source-charset:xxx /execution-charset:xxx。3.3 跨平台项目的统一编码策略在跨平台Windows Linux/macOS项目中最省心的策略是“全链路UTF-8”。具体来说所有源文件统一保存为UTF-8推荐带BOM尤其使用MSVC时BOM能帮助编译器自动识别但带BOM在Linux上也可能引发个别工具链问题所以也需要统一管理。在GCC/Clang的构建参数中显式添加-finput-charsetUTF-8 -fexec-charsetUTF-8。在MSVC的构建参数中显式添加/utf-8。程序运行时在涉及与外设、终端、网络协议交互时明确转换编码而不是假设外部环境也是UTF-8。这三条做好你就能从源头上规避90%以上的编码乱码问题。另外建议在构建脚本中加入一项“编码检查”步骤用file命令或脚本扫描源文件确认它们的编码符合预期避免有同事用不同编辑器保存后导致编码漂移。3.4 字面量前缀窄字符串与宽字符串的编码关系别忘了字符字面量的prefix对执行字符集的影响。C/C中普通的...字符串会按execution-charset编码u8...在C11里强制UTF-8编码L...是宽字符串宽字符集通常取决于编译器GCC和MSVC都常见UTF-16或UCS-4u...和U...分别是UTF-16和UTF-32字符串。如果你设置了-fexec-charsetGBK那中文就是GBK字节u8中文仍然是UTF-8字节。这在混合编码环境中很有用比如你的程序内部统一用UTF-8字符串但某些系统接口需要GBK可以用-fexec-charsetGBK让普通字符串变成GBK同时用u8前缀确保兼容性字符串保持UTF-8。但是要小心不同C标准版本对u8的处理略有差异C20之前u8只能用于字符串字面量不能用于字符字面量u8a在C17之前不合法。另外宽字符字面量L在Windows下通常是UTF-16在Linux下通常是32位整数数组。如果你在Windows和Linux间共享带宽字符串的代码最好用u和U前缀或者直接避免宽字符改用UTF-8窄字符串。这是我在多平台模块开发中踩过好几次坑后总结出来的建议。4. 常见问题与排查技巧实录4.1 症状乱码、警告C4819与Linux下输出异常我把实际项目中遇到过的典型问题整理成一张表方便你快速对照。现象根源常见场景Windows下编译UTF-8无BOM文件时警告C4819MSVC默认按系统代码页解读源文件UTF-8字节被误解编辑器默认保存无BOM、Git导入文件Linux下编译GBK源文件出现奇怪字符字符串打印乱码GCC默认finput-charsetUTF-8错误解读GBK字节Windows上写的源文件拷到Linux程序输出在终端乱码但文件编码没错执行字符集与终端/控制台代码页不一致Linux终端UTF-8程序输出GBKWindows控制台GBK程序输出UTF-8编译报错\u5728 this position等源字符集不包含某些通用字符名映射或者转码失败特殊符号如Emoji映射到非Unicode执行字符集C4819特别有意思它其实是警告你的源文件中有某些字符无法在当前代码页中表示。很多人一看到这个警告就认为是代码BUG其实只要在项目中加上/utf-8并确保文件保存为UTF-8警告就会消失。4.2 排查流程三步定位编码问题遇到疑似编码问题我建议按以下步骤排查确认源文件真实编码。使用file -iLinux/macOS、Notepad右下角或VS Code右下角编码提示来查看。不要只看眼睛“像不像”一定要看字节级信息。确认编译参数。查看构建系统传给编译器实际的参数是直接命令行还是CMake/VS项目中的设置。重点确认是否指定了source-charset和execution-charset。确认运行时环境。运行程序的终端或目标设备使用什么编码。可以用localeLinux、chcpWindows查看代码页再对比程序输出的字节。很多时候问题不在编译器而是在目标设备。比如嵌入式设备串口打印如果设备端默认GBK而你的程序输出UTF-8那就算编译器参数全对串口助手上也照样乱码。此时要么让程序输出GBK要么在设备端初始化时重新配置串口控制台编码。4.3 避坑清单BOM、文件转换与构建系统提示说到实操我对BOM的体会最深。MSVC优点是不带BOM的UTF-8会误判而带UTF-8 BOM的文件它就能自动识别。但BOM在其他工具链里可能成为麻烦某些解释器或汇编器会把BOM当作未知字符处理。因此我比较建议的做法是Windows MSVC的项目统一使用UTF-8 with BOM保存源文件。Linux GCC/Clang的纯unix项目统一使用UTF-8 without BOM并在构建参数里强制指定-finput-charsetUTF-8。跨平台项目强制在构建脚本里加/utf-8或者-finput-charsetUTF-8不依赖BOM自动检测。这样无论源文件有没有BOM都能正确解析。如果遇到已有的源文件编码混乱可以用iconv批量转换# GBK转UTF-8 iconv -f GBK -t UTF-8 old.c new.c转换后务必再用file命令确认一下结果。此外有些构建系统会自动执行编码规范化操作但CMake本身并不关心编码它只是把编译选项传给编译器。因此要在CMakeLists.txt里显式添加if(MSVC) target_compile_options(your_target PRIVATE /utf-8) else() target_compile_options(your_target PRIVATE -finput-charsetUTF-8 -fexec-charsetUTF-8) endif()这样能保证团队协作时不同开发者的环境不会因编码设置不统一而悄悄改变行为。结尾我个人这些年跟字符编码打交道最深的感受是它看起来只是“一个选项设定”实际上是贯穿源码保存、编译器解析、程序运行、外部交互三个层面的系统性工程。source-charset和execution-charset不过是你驾驭这个系统的手柄真正重要的是你对项目中所有字节流向的掌控力。最后分享一个小技巧在项目里放一个build_env.md明确记录“源文件统一UTF-8 编译器参数统一UTF-8 目标环境必须按UTF-8设置”新人进组时花十分钟看一遍比日后排查上百个乱码问题划算得多。

相关新闻

插件原理与排障指南:从加载失败到开发实践

插件原理与排障指南:从加载失败到开发实践

做软件这些年,我发现自己经常要在一个单词上跟别人反复解释:plugins。它不是某个产品的功能,而是一整套架构思想加工程实践。最近看到一堆相关热搜,比如“iar plugins 是干什么的”、“failed to load plugins web boot: 2 entrie…

2026/10/5 3:52:15 阅读更多 →
Petalinux工程骨架详解:从XSA到BOOT.BIN的嵌入式Linux构建

Petalinux工程骨架详解:从XSA到BOOT.BIN的嵌入式Linux构建

1. 先把 petalinux 工程骨架这块拼图摆正如果你刚接触 Zynq 这类带 FPGA 的嵌入式平台,想用 petalinux 给板卡做一套 Linux 系统,第一反应大概率是找一份教程,敲几条命令,生成 BOOT.BIN,烧进 SD 卡,完事。我…

2026/10/5 3:52:14 阅读更多 →
Java仓库管理系统课设拆解:JDBC+MySQL+Swing实战开发

Java仓库管理系统课设拆解:JDBC+MySQL+Swing实战开发

简介:基于Java的仓库管理系统项目,是一份面向计算机相关专业学生和Java Web开发者的毕业设计完整参考。项目运用Spring框架、MyBatis持久层、Servlet与JSP等主流技术,实现了用户注册登录、商品信息维护、库存出入管理、价格设置等核心业务&am…

2026/10/5 3:51:14 阅读更多 →

最新新闻

LCL型三电平并网逆变器FCS-MPC控制实战:从模型到样机调试

LCL型三电平并网逆变器FCS-MPC控制实战:从模型到样机调试

搞并网逆变器的同行,尤其是碰过三电平NPC拓扑的人,大概率都有过类似的体验:PI参数调到头秃,LCL谐振峰压不下去,好不容易加上有源阻尼,动态响应又变得拖泥带水。我这两年多时间一直在做LCL型三相并网逆变器的…

2026/10/5 4:26:31 阅读更多 →
线上OOM排查实战:从Heap Dump到根因定位的完整指南

线上OOM排查实战:从Heap Dump到根因定位的完整指南

1. 项目概述:别把OOM当事故,把它当线索猿辅导二面问到线上OOM排查,这几乎是JVM后端面试的保留节目。表面问的是内存溢出怎么查,实际上问的是你面对线上故障时的整套作战方法论——从报警响应、信息采集、Heap Dump分析&#xff0c…

2026/10/5 4:26:31 阅读更多 →
Java线上OOM排查实战:从堆转储到GC Roots的根因定位

Java线上OOM排查实战:从堆转储到GC Roots的根因定位

做了几年Java后端,最让人心头一紧的告警不是CPU彪了,不是RT降不下来,而是监控屏幕上突然弹出一行红字:OutOfMemoryError。OOM这四个字母一出现,意味着某个JVM进程已经处于极度缺内存的状态,接下来大概率是G…

2026/10/5 4:26:31 阅读更多 →
Web开发、安全与嵌入式实战:一张热词表拆解三大战场

Web开发、安全与嵌入式实战:一张热词表拆解三大战场

我平时用“web”当搜索词的时候,经常会把一大批奇奇怪怪的零散需求搜到一起。比如有一天我想查“web页面pdf打印”,结果后面的关联词里跟着“esp32内嵌web网页”“海康威视视频web插件”“b站首页web推荐算法”“acunetix web漏洞扫描器”,还…

2026/10/5 4:26:31 阅读更多 →
基于SpringBoot的高校学生公寓智能分配平台设计与实现

基于SpringBoot的高校学生公寓智能分配平台设计与实现

每年九月的开学季,最让宿管科头疼的往往不是床位不够,而是“怎么把几千个新生快速、合理地塞进不同的房间”。表格导来导去、辅导员来回商量、学生群里的作息冲突投诉接二连三——宿舍分配管理这件事,看起来只是排个床位,实际上牵…

2026/10/5 4:26:31 阅读更多 →
Colmap中PatchMatch源码实战:从编译报错到深度图调优

Colmap中PatchMatch源码实战:从编译报错到深度图调优

1. 这不是“看懂就行”的源码阅读,而是要让PatchMatch在Colmap里真正跑起来如果你搜“Colmap中 patchmatch源码”,大概率正卡在某个具体环节:编译报错说找不到patch_match_stereo.h、调试时发现PatchMatch::ComputeCostVolume函数里cost volu…

2026/10/5 4:25:30 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

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

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 AI 编程工具,尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手,那你大概率绕不开一个词——plugins。这个词本身不新鲜,从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

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

2026/10/5 0:00:23 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/5 1:10:22 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 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/4 11:40:45 阅读更多 →
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/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练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/4 20:14:29 阅读更多 →