[{"data":1,"prerenderedAt":31},["ShallowReactive",2],{"i-lucide:grip":3,"i-lucide:chevron-right":8,"i-lucide:moon":10,"i-lucide:sun":12,"i-lucide:languages":14,"i-lucide:chevron-down":16,"i-lucide:shield-check":18,"i-lucide:mail":20,"blog-body-latex-rerun-cross-references-zh":22,"i-lucide:lightbulb":23,"i-lucide:graduation-cap":25,"i-lucide:pen-line":27,"i-lucide:star":29},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Ccircle cx=\"12\" cy=\"5\" r=\"1\"\u002F>\u003Ccircle cx=\"19\" cy=\"5\" r=\"1\"\u002F>\u003Ccircle cx=\"5\" cy=\"5\" r=\"1\"\u002F>\u003Ccircle cx=\"12\" cy=\"12\" r=\"1\"\u002F>\u003Ccircle cx=\"19\" cy=\"12\" r=\"1\"\u002F>\u003Ccircle cx=\"5\" cy=\"12\" r=\"1\"\u002F>\u003Ccircle cx=\"12\" cy=\"19\" r=\"1\"\u002F>\u003Ccircle cx=\"19\" cy=\"19\" r=\"1\"\u002F>\u003Ccircle cx=\"5\" cy=\"19\" r=\"1\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m9 18l6-6l-6-6\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":11},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":13},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"4\"\u002F>\u003Cpath d=\"M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":15},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m5 8l6 6m-7 0l6-6l2-3M2 5h12M7 2h1m14 20l-5-10l-5 10m2-4h6\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":17},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m6 9l6 6l6-6\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":19},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\"\u002F>\u003Cpath d=\"m9 12l2 2l4-4\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":21},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"m22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7\"\u002F>\u003Crect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"\u002F>\u003C\u002Fg>","\u003Cblockquote>\n\u003Cp>PDF 里的 \u003Ccode>??\u003C\u002Fcode> 和对不上的目录页码，几乎都不是你的语法写错了，而是\u003Cstrong>编译次数不够\u003C\u002Fstrong>。LaTeX 需要靠上一趟运行留下的辅助文件才能算出正确的编号，只跑一遍必然缺一半信息。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>\u003Cimg src=\"\u002Fblog\u002Flatex-rerun-cross-references\u002Fcover.webp\" alt=\"第一趟留下辅助文件，第二趟才能把 ?? 换成正确编号\">\u003C\u002Fp>\n\u003Ch2>为什么 \\ref 输出的是 ??\u003C\u002Fh2>\n\u003Cp>因为第一趟编译时，那个标签还不存在。LaTeX 从头到尾\u003Cstrong>单趟顺序\u003C\u002Fstrong>处理文档，当它读到第 3 页的 \u003Ccode>\\ref{fig:result}\u003C\u002Fcode> 时，第 12 页的 \u003Ccode>\\label{fig:result}\u003C\u002Fcode> 还没被读到，它无从知道那张图最终是第几张、落在第几页。于是它先输出一个 \u003Ccode>??\u003C\u002Fcode> 占位，并把这一趟收集到的所有标签信息写进一个同名的 \u003Ccode>.aux\u003C\u002Fcode> 辅助文件。\u003C\u002Fp>\n\u003Cp>第二趟编译时，\u003Ccode>.aux\u003C\u002Fcode> 已经躺在磁盘上了。LaTeX 会在开头先把它读进来，于是遇到 \u003Ccode>\\ref\u003C\u002Fcode> 就能直接查表，填上正确的编号。这套「先记账、下次再查账」的机制，是理解 LaTeX 所有多趟问题的钥匙。\u003C\u002Fp>\n\u003Cp>同一套机制还管着这些东西：\u003Ccode>\\pageref\u003C\u002Fcode> 的页码、\u003Ccode>\\cite\u003C\u002Fcode> 的文献编号、\u003Ccode>\\tableofcontents\u003C\u002Fcode> 的目录（存在 \u003Ccode>.toc\u003C\u002Fcode>）、图表目录（\u003Ccode>.lof\u003C\u002Fcode> \u002F \u003Ccode>.lot\u003C\u002Fcode>）、以及 hyperref 生成的 PDF 书签（\u003Ccode>.out\u003C\u002Fcode>）。它们的共同点是——\u003Cstrong>都要等整篇文档走完才知道结果，但又要在文档中间就印出来。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2>为什么目录页码经常差一页\u003C\u002Fh2>\n\u003Cp>因为目录本身也占版面。假设正文原本 30 页，第二趟编译插入了一页目录，后面所有内容就都往后挪了一页，而目录里印的还是第一趟算出来的旧页码，于是整体差 1。第三趟才会用新页码重排目录。\u003C\u002Fp>\n\u003Cp>极端一点的情况是\u003Cstrong>震荡\u003C\u002Fstrong>：某个章节标题正好卡在页面边界，多一行目录条目就把它挤到下一页，页码变了；下一趟目录条目又缩回去，它又回到上一页。这种文档可能编译多少遍都不收敛，需要人工微调（比如给那一处加一点弹性间距）。好在这种情况不常见。\u003C\u002Fp>\n\u003Ch2>到底要编译几遍\u003C\u002Fh2>\n\u003Cp>看文档里用了什么。下面这张表是常见组合的最少趟数：\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>文档里有什么\u003C\u002Fth>\n\u003Cth>最少趟数\u003C\u002Fth>\n\u003Cth>原因\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>只有正文，无引用无目录\u003C\u002Ftd>\n\u003Ctd>1 遍\u003C\u002Ftd>\n\u003Ctd>没有需要跨页回填的信息\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>有 \u003Ccode>\\ref\u003C\u002Fcode> \u002F \u003Ccode>\\pageref\u003C\u002Fcode> 交叉引用\u003C\u002Ftd>\n\u003Ctd>2 遍\u003C\u002Ftd>\n\u003Ctd>第一遍写 .aux，第二遍读 .aux\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>有 \u003Ccode>\\tableofcontents\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>2–3 遍\u003C\u002Ftd>\n\u003Ctd>目录本身改变页码，可能需要再收敛一次\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>有 \u003Ccode>\\cite\u003C\u002Fcode> + BibTeX\u003C\u002Ftd>\n\u003Ctd>4 步（见下节）\u003C\u002Ftd>\n\u003Ctd>中间要插一趟 BibTeX\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>有 hyperref 书签\u003C\u002Ftd>\n\u003Ctd>+1 遍\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>.out\u003C\u002Fcode> 变化会触发 rerunfilecheck 警告\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>注意这是\u003Cstrong>最少\u003C\u002Fstrong>趟数。多跑一趟不会有副作用（结果已收敛时输出完全相同），少跑一趟就会留下 \u003Ccode>??\u003C\u002Fcode>。所以传统做法是无脑跑三遍，虽然浪费但不出错。\u003C\u002Fp>\n\u003Ch2>有参考文献时为什么是四步\u003C\u002Fh2>\n\u003Cp>因为 BibTeX 是一个独立程序，得插在两趟 LaTeX 中间。完整顺序是 \u003Ccode>xelatex → bibtex → xelatex → xelatex\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>第一趟 xelatex\u003C\u002Fstrong>：把正文里 \u003Ccode>\\cite{knuth1984}\u003C\u002Fcode> 引用到的条目名记进 \u003Ccode>.aux\u003C\u002Fcode>，此时还没有任何文献列表；\u003C\u002Fli>\n\u003Cli>\u003Cstrong>bibtex\u003C\u002Fstrong>：读 \u003Ccode>.aux\u003C\u002Fcode> 拿到条目名，去 \u003Ccode>.bib\u003C\u002Fcode> 里查，按你指定的 \u003Ccode>\\bibliographystyle\u003C\u002Fcode> 排好版，输出一个 \u003Ccode>.bbl\u003C\u002Fcode> 文件；\u003C\u002Fli>\n\u003Cli>\u003Cstrong>第二趟 xelatex\u003C\u002Fstrong>：把 \u003Ccode>.bbl\u003C\u002Fcode> 的内容插进文末，参考文献列表出现了，同时把「哪条文献是第几号」写进 \u003Ccode>.aux\u003C\u002Fcode>——但正文里的 \u003Ccode>\\cite\u003C\u002Fcode> 在这一趟读到的还是\u003Cstrong>上一版\u003C\u002Fstrong> \u003Ccode>.aux\u003C\u002Fcode>，所以仍然显示 \u003Ccode>[?]\u003C\u002Fcode>；\u003C\u002Fli>\n\u003Cli>\u003Cstrong>第三趟 xelatex\u003C\u002Fstrong>：现在 \u003Ccode>.aux\u003C\u002Fcode> 里有编号了，正文的 \u003Ccode>[?]\u003C\u002Fcode> 才变成 \u003Ccode>[1]\u003C\u002Fcode>。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>很多人卡在第三步，看到文献列表已经出来了就以为完事，结果正文里全是 \u003Ccode>[?]\u003C\u002Fcode>。再跑一遍就好。\u003C\u002Fp>\n\u003Cp>用 biblatex 的话命令不同：中间那步要换成 \u003Ccode>biber\u003C\u002Fcode> 而不是 \u003Ccode>bibtex\u003C\u002Fcode>，趟数逻辑一样。\u003C\u002Fp>\n\u003Ch2>怎么从日志判断已经收敛\u003C\u002Fh2>\n\u003Cp>搜日志里的 rerun 提示。LaTeX 和相关宏包在发现「这一趟的结果和上一趟对不上」时，会主动打印明确的警告，这几条是最常见的原文：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark mac-code-block\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Cspan class=\"mac-lang-badge\">text\u003C\u002Fspan>\u003Ccode class=\"language-text\">\u003Cspan class=\"line\">\u003Cspan>LaTeX Warning: There were undefined references.\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>Package rerunfilecheck Warning: File `main.out' has changed.\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>(rerunfilecheck)                Rerun to get outlines right\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>Package hyperref Warning: Rerun to get \u002FPageLabels entry.\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>只要还看得到这些，就说明\u003Cstrong>还得再跑一趟\u003C\u002Fstrong>。反过来，收敛的标志是它们全部消失，\u003Ccode>rerunfilecheck\u003C\u002Fcode> 会转而打印一条 Info：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark mac-code-block\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Cspan class=\"mac-lang-badge\">text\u003C\u002Fspan>\u003Ccode class=\"language-text\">\u003Cspan class=\"line\">\u003Cspan>Package rerunfilecheck Info: File `main.out' has not changed.\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>(rerunfilecheck)             Checksum: 53F1C61ABCC06CF9FD3B6F1940CE0C3D;330.\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这条校验和就是 rerunfilecheck 的判断依据——它记下上一趟 \u003Ccode>.out\u003C\u002Fcode> 文件的 MD5，这一趟算完再比一次，一样就说明书签已经稳定。目录能不能停，判断方法也类似：比较这一趟写出的 \u003Ccode>.toc\u003C\u002Fcode> 和上一趟是否逐字节相同。\u003C\u002Fp>\n\u003Cp>本地写作时不必手动数趟数，\u003Ccode>latexmk -xelatex main.tex\u003C\u002Fcode> 会自动循环编译直到日志里再没有 rerun 信号（它内部也有次数上限，防止遇到震荡文档时死循环）。VS Code 的 LaTeX Workshop 默认 recipe 走的就是 latexmk。\u003C\u002Fp>\n\u003Cp>浏览器里编译同理：\u003Ca href=\"\u002Flatex\u002Fedit\u002F\">在线 LaTeX 编辑器\u003C\u002Fa> 的高级模式跑的是 WebAssembly 版 XeLaTeX，它会在每趟结束后扫描日志信号、并比对 \u003Ccode>.toc\u003C\u002Fcode> \u002F \u003Ccode>.lof\u003C\u002Fcode> \u002F \u003Ccode>.lot\u003C\u002Fcode> 有没有变化，需要几趟就跑几趟，不需要你手动点第二次编译。\u003C\u002Fp>\n\u003Ch2>什么时候该删掉 .aux 重来\u003C\u002Fh2>\n\u003Cp>改了标签名或换了文档类之后。\u003Ccode>.aux\u003C\u002Fcode> 是缓存，缓存就会过期：如果你把 \u003Ccode>\\label{fig:old}\u003C\u002Fcode> 改成了 \u003Ccode>\\label{fig:new}\u003C\u002Fcode>，旧 \u003Ccode>.aux\u003C\u002Fcode> 里那条 \u003Ccode>fig:old\u003C\u002Fcode> 还在，某些情况下会掩盖真正的错误，让你以为引用还在正常工作。\u003C\u002Fp>\n\u003Cp>同样，换文档类（比如从 \u003Ccode>article\u003C\u002Fcode> 换成期刊模板）之后，旧 \u003Ccode>.aux\u003C\u002Fcode> 里的计数器格式可能对不上新模板，编译会报一些看起来毫无道理的错。遇到这种「明明代码没问题却报怪错」的情况，先把 \u003Ccode>.aux\u003C\u002Fcode>、\u003Ccode>.toc\u003C\u002Fcode>、\u003Ccode>.out\u003C\u002Fcode>、\u003Ccode>.bbl\u003C\u002Fcode> 这些中间文件全删掉再编译一遍，是性价比最高的排查动作。\u003C\u002Fp>\n\u003Ch2>小结\u003C\u002Fh2>\n\u003Cp>\u003Ccode>??\u003C\u002Fcode> 不是错误，是 LaTeX 在说「这个编号我还不知道，等我跑完这一趟」。记住三件事就够了：交叉引用和目录至少两遍，带参考文献是四步，判断收敛看日志里还有没有 \u003Ccode>Rerun to get\u003C\u002Fcode>。本地用 latexmk 自动化，遇到怪错先清中间文件——这几乎能解决所有「编译出来不对劲」的问题。\u003C\u002Fp>\n",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":24},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 14c.2-1 .7-1.7 1.5-2.5c1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5c.7.7 1.3 1.5 1.5 2.5m0 4h6m-5 4h4\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":26},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0zM22 10v6\"\u002F>\u003Cpath d=\"M6 12.5V16a6 3 0 0 0 12 0v-3.5\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":28},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 21h8m.174-14.188a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":30},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.12 2.12 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16z\"\u002F>",1786455169766]