未定义引用与重复标签

本该出现编号的地方冒出 ??,日志里写着 LaTeX Warning: There were undefined references. 这是每个 LaTeX 用户最先撞上的荒谬。引用变成 ?? 的原因在于,LaTeX 的编号取自上一次运行留下的 .aux 文件。所以 ?? 与那个经典疑问「为什么必须编译两次」,其实是同一枚硬币的两面。本页讲解这套两遍机制、如何分辨 Label(s) may have changedmultiply definedCitation ... 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 坏了直接删掉重建总是安全的:里面的一切都是派生物,每次运行都会重新生成。

document.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 消失,只剩 nopeLabel(s) may have changed 也不见了。查看内核文件 latex.ltx 可知,运行结束时的汇总警告只有 There were undefined references 这一条\ref 失败与 \cite 失败会立同一面旗。所以看到「undefined references」,元凶也可能是文献引用。

log
% 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 中没有该条目先运行 bibtexbiber,再编译两遍
rerunfilecheck: File ... has changedhyperref 的书签文件发生了变化再编译一次

?? 跑了两遍还不消失

这说明该标签确实没有写进 .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 这条规则值得背下来的理由。

latex
\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\itemequation\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 的执行顺序

如果文献一直停在 [?],那么需要跑四遍:pdflatexbibtexpdflatexpdflatex 交叉引用两遍就够,文献却要四遍,是因为中间要插进另一个程序。第一遍 pdflatex 把被 \cite 的键写进 .aux,日志里会出现 No file document.bbl.Citation ... undefined。接着 bibtex.aux 中的键与 .bib 比对,生成 .bbl(已排版好的文献列表)。第三遍 pdflatex 首次读入 .bbl,给每条文献分配编号并写进 .aux——此时正文中的 \cite 仍未解析。直到第四遍才填上编号。实际运行的日志里,第三遍仍带着 Citation ... undefinedLabel(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 的输出才是决定性的。

terminal
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!.(两者都已实测复现)。正确顺序是 hyperrefvariorefcleveref。实践建议是:新文档统一用 cleveref 最不容易出事\cref{eq:e} 给出「eq. (1)」,\Cref{sec:s} 给出「Section 1」,会按目标类型自动配上正确的词,因此重排结构也不会留下过时的措辞。反过来,接手的多人合著稿件里,接受混用、并预期合著者继续敲 \ref,反而更现实。

命令输出示例来源与注意
\ref1内核;只有编号。加载 hyperref 后会变成链接
\pageref1内核;.aux 的第二个字段,即页码
\eqref(1)amsmath;带括号的公式编号
\crefeq. (1)cleveref;按目标类型自动配词
\CrefSection 1cleveref;句首用的首字母大写形式
\autorefsection 1hyperref;比 \cref 简单,不能合并范围
\namerefThe sectionnameref(由 hyperref 加载);输出标题文字
\vref1 on the previous pagevarioref;同页时只给编号,目标在别处时补上页面说明
preamble
% load order matters: hyperref, then varioref, then cleveref
\usepackage{amsmath}
\usepackage{hyperref}
\usepackage{varioref}
\usepackage{cleveref}    % must come last of the three