
LaTeX 编译报这个错多数时候不是你公式写错了而是某个宏包的内部栈机制被打乱了。我先说个实际场景。你用 Beamer 做 slides或者用 article 写论文在\section或者\caption里放了一个\url、\cite、\verb一编译直接弹出一句! You cant pop an empty literal stack for entry xxxxx后面可能还跟着一个行号指向你文档里某个标题行。第一次遇到的人很容易懵什么叫 literal stackempty literal stack 又是什么这跟我的标题有什么关系这篇文章就写这个报错的排查思路。我会先讲清楚它背后的机制再说怎么定位是哪一行代码触发的最后给出几种常见场景的具体修法。如果你正被这个报错卡住按文章顺序走一遍大概率十分钟内能解决。1. 报错拆解先搞懂“empty literal stack”到底在说什么1.1 宏展开与“文字缓冲栈”的工作机制LaTeX 在编译时并不是像 Word 那样“看到什么就排版什么”。它是先把文档内容读成一串串记号token然后不断做宏展开一层层把\section、\frametitle这种命令替换成底层排版指令最终才生成页面。问题在于有些排版动作是分阶段完成的。最典型的就是“标题”\section{...}里的内容不仅要在正文当前位置排版出来还要被“搬运”到目录、页眉、PDF 书签这些完全不同的输出位置里去。同一个内容在不同位置需要不同的处理方式比如 PDF 书签里不能用数学公式目录里要省略某些格式命令。为了解决这个“同一内容多份输出”的需求很多宏包会在内部维护一个栈结构专门用来暂存“当前阶段还不适合立即处理的文字内容”。你可以把它理解成厨房里的备菜台有的菜要下锅有的菜要先放一边等另一只锅空出来。宏在展开时会把暂时不能处理的字面内容塞进这个栈里等到合适的时机再弹出来继续处理。empty literal stack的意思就是某个宏包内部的备菜台是空的可程序却还要从中取东西于是运转不下去。严格来说这往往不是栈本身“空”了而是宏包的调用逻辑出了岔子——某个命令在错误的时机被触发导致出栈动作发生时栈里没有东西可给。1.2 最常触发这个错误的几类场景根据我的实际排查经验几乎所有触发这个报错的场景都能归进下面这几类在标题类命令\section、\subsection、\caption、\frametitle里使用了“脆弱命令”。Beamer 的帧标题里放了\verb、\lstinline这类无法移动的逐字命令。hyperref宏包和某些宏包存在加载顺序冲突尤其在处理书签字符串时。标题文字里包含了特殊 Unicode 字符比如从网页直接粘贴了 emoji 或者全角符号。用\edef或类似机制强行展开一个含有不可展开命令的参数导致内部栈状态错乱。其中第一类和第二类占了八成以上。后面我会分别给修法但你得先掌握正确的定位手段否则修法用不上。2. 定位问题从报错现场到最小复现样本2.1 第一步翻 .log 文件从报错点附近找线索遇到这个报错我建议你先别急着改代码。先打开编译生成的.log文件定位到报错位置往上翻几行看有没有附带信息。! You cant pop an empty literal stack for entry xxxxx to be read again \leavevmode l.56 \frametitle{使用 \texttt{LaTeX} 的注意事项}这里有两样东西非常关键一是to be read again后面跟着的记号它往往能告诉你“当前正要展开什么命令”二是行号指向的源代码位置它直接定位到出错那一行。有些宏包还会在报错前打印出最近处理的几个宏名比如-hyperref-或beamer的内部宏。看到这类名字你就知道责任方在哪个宏包里了接下来排查范围会小很多。2.2 第二步最小示例法MWE逐段“隔离”排查如果报错信息不够清楚就走最笨也最可靠的路最小示例法。我自己的习惯是复制一份源文件另存为debug.tex。在\begin{document}后第一行写一个注释标记把后面的正文全部注释掉只留一页空文档编译看能否通过。如果能通过逐步放回一小段正文内容每次编译一次直到问题复现。一旦复现就把召回的那一段孤出来再尝试精简比如去掉一半文字或者单独抽取其中的命令放到标题里测。这样做的好处是你能把问题从“复杂的真实文档”压缩成“最短的触发样本”。我在处理这类报错时经常把问题压缩到三五行代码然后一眼就能看出是哪类冲突。强烈建议你把压缩后的样本保留下来后面改宏包版本或者升级环境时还能拿回来回归验证。2.3 用 \tracing 和 \listfiles 加深诊断如果最小示例都做好了还是看不出名堂那就上两个诊断工具。第一个是\errorcontextlines。在导言区加一行\errorcontextlines100这会让报错信息显示更多宏展开上下文。默认值是 -1很多时候只给你看最后一层加长之后能看清整个调用链对理解“哪个宏在什么时候触发了出栈”很有帮助。第二个是\listfiles。在导言区加一行\listfiles编译后.log文件末尾会列出全部宏包的版本清单。很多时候报错是宏包版本不匹配造成的比如某些更新版的hyperref和旧版beamer配合就会出怪问题。看到具体版本号你就能去查是不是已知冲突。我实测过很多次\errorcontextlines一加很多“看不懂的报错”立马变得清楚比盲猜命令要高效得多。3. 分场景修复方案与代码示例3.1 标题中的脆弱命令\protect 与 \texorpdfstring脆弱命令是指那些在“移动参数”里不能正常展开的命令典型的有\cite、\ref、\footnote、\url等。它们本身要产生副作用或依赖当前上下文当标题内容被搬运到目录或 PDF 书签时上下文已经变了于是内部栈机制就乱了。最常见的修法有三种按优先级排列方法一给命令加\protect\section{基于\protect\cite{key2024}的研究}\protect的本质是“临时禁止展开”让命令先原样保存下来等真正该执行的地方再执行。这个写法简单、直接对\cite、\ref这类命令尤其好使。方法二用\texorpdfstring区分不同输出这个命令来自hyperref宏包专门用来给“正文输出”和“PDF 书签输出”分别指定内容\section{\texorpdfstring{基于$\beta$-分布的研究}{基于beta分布的研究}}正文里正常显示公式PDF 书签里则显示纯文本避免公式符号被搬进书签时把宏包内部栈搞乱。方法三把复杂内容抽成带参数的新命令如果你发现标题里的脆弱命令特别多每个都加\protect太啰嗦那就自定义一个外壳命令\newcommand{\seccite}[1]{\protect\cite{#1}} \section{相关工作\seccite{key2024}}这样既保持了源代码可读性又不用记住哪里该加\protect。3.2 Beamer 中 verbatim 与帧标题的冲突处理Beamer 用户经常踩另一个坑在\frametitle里直接写\verb。\frametitle{\verb|LaTeX| 排版技巧}逐字命令verbatim的特性是“原样读取字符不做任何宏展开”。而标题内容是要被提前搬动的这两者天然冲突。LaTeX 底层对\verb的舞台位置极其敏感一旦它出现在需要提前处理的参数里就可能触发 “pop an empty literal stack”。正确做法是如果只是想让某个词变个字体别用\verb改用\texttt\frametitle{\texttt{LaTeX} 排版技巧}如果确实需要展示多行代码把 frame 标记为 fragile\begin{frame}[fragile] \frametitle{代码示例} \begin{verbatim} \usepackage{hyperref} \end{verbatim} \end{frame}fragile选项会改变 Beamer 对帧内容的处理方式让逐字命令能正常工作。但注意\frametitle本身仍然不能放\verb正文部分才能。3.3 宏包冲突与加载顺序调整还有一种常见情况报错不定点出现一会儿在\section上一会儿在\caption里但凡是标题类内容都报错。这种“全局性症状”往往是宏包加载顺序导致的尤其是hyperref和其它宏包的冲突。hyperref的说明书里明确建议“尽量最后一个加载”。原因很简单它要改写很多内部命令如果之后还有宏包修改了同一条命令两边的状态就对不上了宏包内部栈也就容易出问题。我常用的导言区顺序是这个风格\usepackage{fontspec} % 字体相关先加载 \usepackage{amsmath} % 公式宏包 \usepackage{graphicx} % 插图 \usepackage{tikz} % 绘图宏包 \usepackage{cite} % 参考文献相关 \usepackage[colorlinkstrue]{hyperref} % hyperref 放最后如果顺序已经调了还报错可以把hyperref的draft选项打开试一下\usepackage[draft]{hyperref}draft模式会跳过 PDF 书签的生成逻辑很多时候书签处理在draft下就不会触发内部栈问题。如果在draft下不报错那基本可以断定问题出在书签字符串处理阶段再回头去查标题里的脆弱命令和特殊字符。3.4 特殊字符、Unicode 与编码类问题现在很多人喜欢从网页复制标题文本一粘就带进来各种特殊符号。比如说–en-dash、’右单引号、…省略号甚至 emoji。这些字符在传统 LaTeX 编译链pdflatex inputenc下如果不做转换就可能被宏包在处理标题内容时“咬住”从而破坏内部文字栈。我的建议是标题类内容里尽量不出现 emoji这不仅是技术问题更是排版品味问题。普通文本特殊符号尽量用 LaTeX 命令替换比如--代替 en-dash\ldots代替省略号。如果必须使用大量 Unicode 字符不如直接用xelatex或lualatex编译配合fontspec宏包能直接从源文件读取 UTF-8 字符少一层转换少一堆麻烦。在 VSCode 的 LaTeX Workshop 里改编译器很简单settings.json里加一个 recipe 就行{ name: xelatex, tools: [xelatex] }然后用 XeLaTeX 重新编译一次很多和输入编码相关的怪报错都会消失。4. 日常编译环境维护与防坑建议4.1 辅助文件清理的正确姿势auxiliary files很多 LaTeX 的“鬼打墙”式报错其实跟源代码没关系而是陈旧的辅助文件在作祟。.aux、.toc、.lof、.out这些文件是上次编译留下的中间产物里面记录着交叉引用、目录结构、书签结构等信息。如果你的文档结构改动较大旧数据和新结构对不上就可能让宏包在读取辅助文件时出错。所以遇到奇怪报错时最有效的“重置”操作就是清理辅助文件后重新编译。第一次编译生成辅助文件第二次编译读取辅助文件生成目录和引用两次都通过才算正常。在 VSCode 的 LaTeX Workshop 里可以直接执行 “Clean up auxiliary files” 命令命令行用户则手动删除rm -f *.aux *.toc *.lof *.lot *.out *.bbl *.blg *.nav *.snm注意.bbl和.blg是 BibTeX 的产物删掉之后需要重新跑 BibTeX再跑两遍 LaTeX整个过程要完整别删一半留一半。4.2 VSCode LaTeX Workshop 的实用配置片段既然很多读者是从 VSCode 入门的我顺便分享一段我实测下来比较省心的settings.json配置它对减少这类怪报错很有帮助{ latex-workshop.latex.clean.fileTypes: [ *.aux, *.bbl, *.blg, *.idx, *.ind, *.lof, *.lot, *.out, *.toc, *.acn, *.acr, *.alg, *.glg, *.glo, *.gls, *.fls, *.log, *.fdb_latexmk, *.nav, *.snm ], latex-workshop.latex.recipe.default: latexmk (xelatex), latex-workshop.view.pdf.viewer: tab }第一段配置指定了哪些文件算辅助文件第二段把默认编译器切成 latexmk xelatex第三段让 PDF 直接以标签页形式打开。配合 “Clean up auxiliary files” 的使用习惯能减少至少一半的“莫名其妙报错”。4.3 写宏时的通用健壮性习惯自定义命令时有些习惯能帮你躲开大量内部栈类报错。我自己写了几年宏包和模板最深刻的感受是别贪图“少打字”而滥用\edef。\edef会把参数的所有可展开内容都强行展开如果参数里有\cite、\ref、\url这些带副作用的命令展开时机一旦不对内部栈就会乱套。我踩过好多次这种坑最后基本遵循两条习惯第一需要“保存参数以后再用”时优先用\newcommand配合普通参数让 LaTeX 自己做延迟处理而不是手动\edef全部展开。第二如果确实需要展开一部分内容用\protectededef或\unexpanded来保留不该展开的部分\usepackage{etoolbox} \newrobustcmd{\safecite}[1]{\protect\cite{#1}}etoolbox的\newrobustcmd能把命令声明为“健壮命令”这样的命令在移动参数里使用时就安全得多。我建议每个经常写自定义命令的人都在导言区加载etoolbox它是处理这类问题的利器。5. 常见问题速查表5.1 典型触发场景与解法对照表触发场景报错特征解决方案标题里用了\verb或\lstinline报错位置直接指向标题行同时可能提示 “verbatim” 相关字样改用\textttBeamer 帧正文用[fragile]标题里用了\cite或\ref报错多出现在二次编译或引用更新时加\protect或改用\texorpdfstring区分输出hyperref与其它宏包顺序冲突多处标题类命令轮流报错无固定位置把hyperref调到导言区最后一个加载陈旧辅助文件导致的状态错乱只在第二次编译时报错清理后可能消失删除.aux等辅助文件后完整重跑两遍编译标题里粘贴了 emoji 或特殊 Unicode 字符报错同时伴随inputenc或编码相关提示替换为 LaTeX 命令或改用 XeLaTeX 编译自定义宏中使用了\edef展开含脆弱命令的参数报错在自定义宏调用处改用\newrobustcmd或\unexpanded保护5.2 两条实测心得再补两条常规文档里不会写的经验。第一如果你用 latexmk 作为编译驱动遇到这个报错时先看一眼编译日志里是“第几轮”出错的。如果是在第二轮或第三轮出错大概率是辅助文件或交叉引用环节的问题如果第一轮就挂那多半是宏包加载或语法本身的问题。这个特征能帮你快速分流排查方向。第二遇到难缠的报错我习惯把.log文件里从报错位置往上 30 行完整贴进一个临时文本文件然后逐行对照源代码找线索。很多报错的真正起因藏在前面两三行警告里真正的报错反而是“迟到”的并发症。上次我排查一个 “literal stack” 报错结果发现源头在\usepackage[utf8]{inputenc}和某个字体宏包的兼容性警告上后面所有标题报错都是它引起的连锁反应。所以如果你试了所有修法都没解决回头去看看那些被忽略的黄色警告它们往往是真正的病根。拿我自己来说遇到过最烧脑的一次根因居然是一个写在文档最末尾的\appendix触发了前面的宏包状态异常前前后后花了大半天。这也是为什么我一直强调把问题压到最小示例里比在完整文档里猜来猜去要快十倍。还是那句话LaTeX 的报错看着吓人但绝大多数都能靠“缩小范围 分清编译阶段 检查宏包顺序”这三板斧解决。希望这篇排查实录能帮你少走点弯路。