本该出现编号的地方冒出 ??,日志里写着 LaTeX Warning: There were undefined references. 这是每个 LaTeX 用户最先撞上的荒谬。引用变成 ?? 的原因在于,LaTeX 的编号取自上一次运行留下的 .aux 文件。所以 ?? 与那个经典疑问「为什么必须编译两次」,其实是同一枚硬币的两面。本页讲解这套两遍机制、如何分辨 Label(s) may have changed、multiply defined 与 Citation ... undefined、加入 BibTeX 或 Biber 后正确的执行顺序,以及最可怕的陷阱——一条警告都不给却印出错误编号的 \label 摆放位置。
为什么必须编译两次
因为在把文档从头到尾读完一遍之前,LaTeX 并不知道后面那些东西的编号。 你在第 3 页写「参见第 7 章」,可此刻第 7 章会落在哪一页还没定。于是 LaTeX 在第一遍运行时把找到的所有 \label 写进 .aux 文件,第二遍开头再读回来,这才填上编号。所以第一遍必然是 ??——这是设计,不是 bug。而且问题是循环的:引用从 ??(两个字符)变成 12(同样两个字符,但不同)会改变断行,断行改变会改变页码,页码改变又会改变 \pageref 的值。LaTeX 用「反复迭代直到稳定」来破解这个循环,而报告是否已稳定的正是 LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.。一直重跑到这句话消失为止,这就是正确的做法。
把 .aux 的内容看一眼,这套机制就通了。下面是含 \section{The section}\label{sec:s} 的文档跑完两遍后的 .aux 摘录,上面是纯 LaTeX,下面是加载了 hyperref 的情形。\newlabel 有五个参数,其中第一个是编号,第二个是页码。\ref 只是取第一个,\pageref 取第二个。加载 hyperref 后,第三个装标题文字,第四个装锚点名。\nameref{sec:s} 能打印出「The section」靠的正是第三项,\ref 能变成可点击的链接靠的是第四项。这同时也说明了为什么 .aux 坏了直接删掉重建总是安全的:里面的一切都是派生物,每次运行都会重新生成。
% plain LaTeX: {number}{page}{}{}{}
\newlabel{sec:s}{{1}{1}{}{}{}}
% with hyperref: {number}{page}{title}{anchor}{}
\newlabel{sec:s}{{1}{1}{The section}{section.1}{}}让人去数「要跑几遍」纯属浪费,于是出现了替你判断的工具:latexmk。它读日志,一直跑到 Rerun to get cross-references right 消失为止,必要时还会顺带插入 BibTeX 或索引处理。这个工具的来历相当有趣:TeX Live 2024 所附 latexmk 的文件开头记载,其原型是 1992 年密歇根大学的 David J. Musliner 写的一个名叫 go 的脚本。随后 Evan McLean 做了修改,1998 年起由 John Collins 接手,随发行版分发的版本是 2024 年 1 月的 4.83。「这玩意到底要编译几次」这样一句朴素的抱怨,养活了一个维护了三十多年的工具。
分辨这些警告:哪个是「还没」,哪个是「真的没有」
判断很简单:跑两遍,看同一条警告是否还在。 只在第一遍出现的警告属正常行为;第二遍仍在的才是真问题。把一份同时含有 \ref{nope}(不存在的标签)与 \ref{sec:real}(存在的标签)的文档编译一下:第一遍两者都被报为 undefined,末尾并列着 There were undefined references. 与 Label(s) may have changed. Rerun to get cross-references right.。第二遍 sec:real 消失,只剩 nope,Label(s) may have changed 也不见了。查看内核文件 latex.ltx 可知,运行结束时的汇总警告只有 There were undefined references 这一条,\ref 失败与 \cite 失败会立同一面旗。所以看到「undefined references」,元凶也可能是文献引用。
% first run
LaTeX Warning: Reference `nope' on page 1 undefined on input line 3.
LaTeX Warning: Reference `sec:real' on page 1 undefined on input line 5.
LaTeX Warning: There were undefined references.
LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.
% second run -- sec:real is resolved, nope is a real problem
LaTeX Warning: Reference `nope' on page 1 undefined on input line 3.
LaTeX Warning: There were undefined references.| 消息 | 含义 | 该做什么 |
|---|---|---|
Label(s) may have changed | 编号尚未稳定 | 再编译一次,仅此而已 |
Reference `x' ... undefined | .aux 中没有 \label{x} | 若第二遍仍出现,就怀疑拼写或标签位置 |
There were undefined references | 有 \ref 或 \cite 未解析 | 往上看那些逐条警告 |
Label `x' multiply defined | 同一标签出现两次以上 | 改成唯一。只会从第二遍起出现 |
Citation `x' ... undefined | .bbl 中没有该条目 | 先运行 bibtex 或 biber,再编译两遍 |
rerunfilecheck: File ... has changed | hyperref 的书签文件发生了变化 | 再编译一次 |
当 ?? 跑了两遍还不消失
这说明该标签确实没有写进 .aux。 检查只要一秒:打开 .aux,搜索 \newlabel{ 加上那个名字。没有,问题就出在 \label 一侧;有却仍显示 ??,那问题就是 \ref 一侧的拼写不一致。顺带一提,?? 以粗体印出并非偶然。内核的 \@setref 写的是 \nfss@text{\reset@font\bfseries ??}——故意加粗以免被忽略,所以哪怕在打印出来的草稿上也一眼可见。以下是标签进不了 .aux 的典型原因。
- 拼写不一致。
\label{fig:setup}与\ref{fig:Setup}是两个不同的标签;标签名区分大小写。 - 该文件被
\includeonly排除了。 被排除文件中的\label根本不会写出,指向它们的引用就一直是??。在排除生效期间,出现??是正常的。 .aux过期或损坏。 中断过上一次运行,或换过文档类、宏包时会发生。删掉.aux、.toc、.out再编译两遍即可。\label位于可移动参数之中。 直接写进标题或图表说明的参数里,它可能被复制进.toc而在那里表示别的东西。放在\caption的花括号之外、紧随其后,才是安全写法。- 你编译的根本是另一个文件。 多文件工程中,单独编译子文件不会更新父文件的
.aux。请始终从父文件构建。 - 文献 key 不在
.bib里。 这会显示为[?]而非??;请检查bibtex自己的输出里有没有I didn’t find a database entry。
把 \label 放在 \caption 之前,编号会悄无声息地出错
\label 一定要放在 \caption 之后。放在之前,既不报错也不警告,却会印出完全不同的编号。 这是可以实测的。取一份有三个 \section 和两个图的文档,第一个图把 \label 放在 \caption 前、第二个放在后,编译两遍,.aux 中写着:\newlabel{fig:before}{{3}{1}{}{}{}} 与 \newlabel{fig:after}{{2}{1}{}{}{}}。前者本应是「图 1」,却记下了 3,也就是紧邻的节编号。而 LaTeX 给出的警告是零条。PDF 顺利生成,正文里理直气壮地写着「图 3」。原理是这样的:\label 记录的是「当前生效的计数器」的值,而把计数器切换到 figure 并推进它,是 \caption 的职责(它调用 \refstepcounter)。在 \caption 之前,最近一次调用 \refstepcounter 的是 \section,于是被记下的就是节编号。这类 bug 往往要等审稿人写下「你说参见图 3,可图 3 是别的东西」时才被发现——这正是\label 紧跟 \caption 这条规则值得背下来的理由。
\begin{figure}[h]
\includegraphics{fig.pdf}
\caption{The right way round}
\label{fig:ok} % after \caption -- records the figure number
\end{figure}
\begin{figure}[h]
\label{fig:broken} % before \caption -- records the SECTION number
\includegraphics{fig.pdf}
\caption{Silently wrong}
\end{figure}环境内部的 \label:\item、equation、\section*
规则只有一条:把 \label 放在推进计数器的那条命令的紧后面。 在 enumerate 中就是 \item 之后,公式则要放在环境内部,节则紧跟 \section{…}。这里的错误同样是无声的。把 \label{it:bad} 放在 \begin{enumerate} 之后、第一个 \item 之前,编译两遍,.aux 记下的是 \newlabel{it:bad}{{1}{1}{}{}{}}——不是条目编号,而是紧邻节编号的 1。警告依旧为零。\section*{…} 也一样:带星的标题不推进计数器,因此其后的 \label 会拾到它之前最后一个带编号节的编号。若要引用带星标题,请配合 \phantomsection(来自 hyperref)与 \addcontentsline,或者干脆设计成不引用它。给标签名加 fig:、tab:、sec:、eq:、it: 之类前缀的惯例,不只是为了整齐:写下 \ref{fig:x} 的那一刻,你的意图就摆在纸面上——你是要指向一张图。若打印出来的编号不属于图,前缀正是让你察觉的东西。
Label ... multiply defined 只会从第二遍起出现
同一个 \label{x} 出现在两处以上,而 \ref{x} 只会指向其中之一(后被读到的那个)。 还有一条重要性质:在删掉 .aux 之后的第一遍运行中,这条警告根本不会出现。实测中,删除 .aux 后只跑一遍时,multiply defined 的条数为 0。警告是在第二遍运行开头、LaTeX 读回 .aux 并发现自己要重复定义一个已定义的标签时才产生的。所以「我编译了一次没看到警告」并不能证明标签唯一。文档末尾还会有汇总:LaTeX Warning: There were multiply-defined labels.。成因几乎总是复制粘贴:复制按章分的文件、用 \include 把同一个文件读了两遍,或者 subfiles 文档被父子双方都引入。预防的习惯是按文件定前缀,例如 intro:fig:setup——这样标签一旦被粘到别的章里,一眼就能看出不对。
Citation ... undefined 与 BibTeX/Biber 的执行顺序
如果文献一直停在 [?],那么需要跑四遍:pdflatex、bibtex、pdflatex、pdflatex。 交叉引用两遍就够,文献却要四遍,是因为中间要插进另一个程序。第一遍 pdflatex 把被 \cite 的键写进 .aux,日志里会出现 No file document.bbl. 与 Citation ... undefined。接着 bibtex 拿 .aux 中的键与 .bib 比对,生成 .bbl(已排版好的文献列表)。第三遍 pdflatex 首次读入 .bbl,给每条文献分配编号并写进 .aux——此时正文中的 \cite 仍未解析。直到第四遍才填上编号。实际运行的日志里,第三遍仍带着 Citation ... undefined 和 Label(s) may have changed. Rerun to get cross-references right.,第四遍才干净。使用 biblatex 时以 biber 代替 bibtex,但次数结构相同。引用一个不存在的键时,bibtex 会告诉你:Warning--I didn’t find a database entry for "missingkey"——对这类问题而言,读 bibtex 的输出而不是 LaTeX 的输出才是决定性的。
pdflatex document # writes the cited keys into document.aux
bibtex document # reads document.aux + refs.bib, writes document.bbl
pdflatex document # reads the .bbl, numbers the entries
pdflatex document # finally resolves every \cite in the text
# with biblatex, swap the second line for:
# biber document\ref、\eqref、\cref、\nameref、\vref 的取舍与加载顺序
裸的 \ref 只输出编号。 「图」「式」这类词和括号得由你自己敲,而凡是手敲的东西迟早会对不上。为了填这个缺口,存在一层专门的命令,下表列出。但加载顺序有严格约束:cleveref 必须在 hyperref 之后,也必须在 varioref 之后。顺序错了 cleveref.sty 会自己诊断出来,显示 ! Package cleveref Error: cleveref must be loaded after hyperref!. 或 ! Package cleveref Error: cleveref must be loaded after varioref!.(两者都已实测复现)。正确顺序是 hyperref → varioref → cleveref。实践建议是:新文档统一用 cleveref 最不容易出事。\cref{eq:e} 给出「eq. (1)」,\Cref{sec:s} 给出「Section 1」,会按目标类型自动配上正确的词,因此重排结构也不会留下过时的措辞。反过来,接手的多人合著稿件里,接受混用、并预期合著者继续敲 \ref,反而更现实。
| 命令 | 输出示例 | 来源与注意 |
|---|---|---|
\ref | 1 | 内核;只有编号。加载 hyperref 后会变成链接 |
\pageref | 1 | 内核;.aux 的第二个字段,即页码 |
\eqref | (1) | amsmath;带括号的公式编号 |
\cref | eq. (1) | cleveref;按目标类型自动配词 |
\Cref | Section 1 | cleveref;句首用的首字母大写形式 |
\autoref | section 1 | hyperref;比 \cref 简单,不能合并范围 |
\nameref | The section | nameref(由 hyperref 加载);输出标题文字 |
\vref | 1 on the previous page | varioref;同页时只给编号,目标在别处时补上页面说明 |
% load order matters: hyperref, then varioref, then cleveref
\usepackage{amsmath}
\usepackage{hyperref}
\usepackage{varioref}
\usepackage{cleveref} % must come last of the three