Skip to content

搞英语 → 看世界

翻译英文优质信息和名人推特

Menu
  • 首页
  • 作者列表
  • 独立博客
  • 专业媒体
  • 名人推特
  • 邮件列表
  • 关于本站
Menu

使用 make 编译 C 程序(针对非 C 程序员)

Posted on 2025-06-11

我从未做过 C 程序员,但时不时需要从源代码编译 C/C++ 程序。这对我来说有点棘手:很长一段时间里,我的做法基本上是“安装依赖项,运行make ,如果不行,要么尝试找个别人编译好的二进制文件,要么就放弃”。

当我运行 Linux 时,“希望其他人已经编译了它”非常有效,但自从过去几年我一直在使用 Mac 以来,我遇到了更多必须自己编译程序的情况。

那么,让我们来谈谈编译 C 程序可能需要做些什么!我会用几个我编译过的具体 C 程序的例子,并讨论一些可能出错的地方。以下是我们将要讨论的三个程序的编译:

  • 卡纸
  • SQLite
  • qf (一个分页器,您可以运行它来通过rg -n THING | qf搜索快速打开文件)

步骤 1:安装 C 编译器

这非常简单:在 Ubuntu 系统上,如果我还没有 C 编译器,我会使用以下命令安装一个:

 sudo apt-get install build-essential

这将安装gcc 、 g++和make 。Mac 上的情况可能比较复杂,但大致类似于“安装 xcode 命令行工具”。

第 2 步:安装程序的依赖项

与一些较新的编程语言不同,C 没有依赖项管理器。因此,如果程序有任何依赖项,您需要自己查找。值得庆幸的是,C 程序员通常会将依赖项保持在极低的水平,并且通常无论您使用哪种包管理器,这些依赖项都可以找到。

几乎总有一个部分解释如何在 README 中获取依赖项,例如在paperjam的 README 中,它说:

要编译 PaperJam,您需要 libqpdf 和 libpaper 库的标题(通常以 libqpdf-dev 和 libpaper-dev 包的形式提供)。

您可能需要a2x (可在AsciiDoc中找到) 来构建手册页。

因此,在基于 Debian 的系统上,您可以像这样安装依赖项。

 sudo apt install -y libqpdf-dev libpaper-dev

如果 README 文件给出了软件包的名称(例如libqpdf-dev ),我基本上总是会假设它们的意思是“在基于 Debian 的 Linux 发行版中”:如果你使用的是 Mac,那么brew install libqpdf-dev将无法正常工作。我还没有完全掌握在 Mac 上进行开发的技巧,所以目前还没有太多这方面的技巧。我想在这种情况下,如果你使用 Homebrew,那么brew install qpdf就更好了。

步骤 3:运行./configure (如果需要)

有些 C 程序附带Makefile ,而有些则附带一个名为./configure的脚本。例如,如果你下载了sqlite 的源代码,它里面有一个./configure脚本,而不是 Makefile。

我对这个./configure脚本的理解是:

  1. 你运行它,它会打印出很多难以理解的输出,然后它要么生成一个Makefile ,要么因为缺少一些依赖而失败
  2. ./configure脚本是名为autotools的系统的一部分,除了“运行它来生成Makefile ”之外,我不需要学习任何有关它的知识。

我认为您可以传递一些选项来让./configure脚本生成不同的Makefile ,但我从未这样做过。

步骤 4:运行make

下一步是运行make来尝试构建一个程序。关于make一些注意事项:

  • 有时您可以运行make -j8来并行化构建并使其运行得更快
  • 编译程序时,它通常会打印出无数个编译器警告。我总是直接忽略它们。我又没写这个软件!编译器警告跟我没关系。

编译器错误通常是依赖性问题

这是我在 Mac 上编译paperjam时遇到的错误:

 /opt/homebrew/Cellar/qpdf/12.0.0/include/qpdf/InputSource.hh:85:19: error: function definition does not declare parameters 85 | qpdf_offset_t last_offset{0}; | ^

多年来,我了解到最好不要过度思考这样的问题:如果它谈论的是qpdf ,那么有一个很好的变化,它只是意味着我在包含qpdf依赖项的方式上做错了什么。

现在让我们讨论一下以正确方式包含qpdf依赖项的一些方法。

世界上最简短的编译器和链接器介绍

在我们讨论如何解决依赖问题之前:构建 C 程序分为两个步骤:

  1. 将代码编译成目标文件(使用gcc或clang )
  2. 将这些目标文件链接到最终的二进制文件中(使用ld )

在构建 C 程序时了解这一点很重要,因为有时您需要将正确的标志传递给编译器和链接器,以告诉它们在哪里找到您正在编译的程序的依赖项。

make使用环境变量来配置编译器和链接器

如果我在 Mac 上运行make来安装paperjam ,我会收到此错误:

 c++ -o paperjam paperjam.o pdf-tools.o parse.o cmds.o pdf.o -lqpdf -lpaper ld: library 'qpdf' not found

这并不是因为我的系统上没有安装qpdf (实际上它已经安装了!)。而是编译器和链接器不知道如何找到qpdf库。为了解决这个问题,我们需要:

  • 将"-I/opt/homebrew/include"传递给编译器(告诉它在哪里找到头文件)
  • 将"-L/opt/homebrew/lib -liconv"传递给链接器(告诉它在哪里找到库文件并链接到iconv )

我们可以让make使用环境变量将这些额外的参数传递给编译器和链接器!要了解其工作原理,请在paperjam的 Makefile 中看到一堆环境变量,例如下面的LDLIBS :

 paperjam: $(OBJS) $(LD) -o $@ $^ $(LDLIBS)

您放入LDLIBS环境变量的所有内容都会作为命令行参数传递给链接器( ld )。

秘密环境变量: CPPFLAGS

Makefiles有时会定义自己的环境变量并将其传递给编译器/链接器,但make还包含一些“隐式”环境变量,这些变量会自动传递给 C 编译器和链接器。 这里有一个完整的隐式环境变量列表,其中之一就是CPPFLAGS ,它会自动传递给 C 编译器。

(从技术上讲,使用CXXFLAGS对此更为正常,但这个特定的Makefile对CXXFLAGS进行了硬编码,因此设置CPPFLAGS是我发现的唯一一种无需编辑Makefile即可设置编译器标志的方法)

顺便说一句:我花了很长时间才意识到 `make` 与 C/C++ 的紧密联系——我曾经认为 `make` 只是一个通用的构建系统(当然你可以用它做任何事情!)但它有很多用于构建 C/C++ 程序的功能,而这些功能在构建任何其他类型的程序时都是没有的。

如何使用CPPFLAGS和LDLIBS修复此编译器错误

现在我们已经讨论了如何将CPPFLAGS和LDLIBS传递给编译器和链接器,下面是我用来成功构建程序的最后一个咒语!

 CPPFLAGS="-I/opt/homebrew/include" LDLIBS="-L/opt/homebrew/lib -liconv" make paperjam

这会将-I/opt/homebrew/include给编译器,并将-L/opt/homebrew/lib -liconv给链接器。

另外,我不想假装自己“神奇地”知道这些参数是正确的,因为搞清楚这些参数需要我费劲地谷歌搜索一番,而我在这篇文章中就略过了。我会说:

  • -I编译器标志告诉编译器在哪个目录中查找头文件,例如/opt/homebrew/include/qpdf/QPDF.hh
  • -L链接器标志告诉链接器在哪个目录中查找库,例如/opt/homebrew/lib/libqpdf.a
  • -l链接器标志告诉链接器要链接哪些库,例如-liconv表示“链接iconv库”,或-lm表示“链接math ”

提示:如何只构建一个特定文件: make $FILENAME

昨天我发现了一个很酷的工具,叫做qf ,你可以使用它从ripgrep的输出中快速打开文件。

qf位于一个包含各种工具的大目录中,但我只想编译qf 。所以我直接编译了qf ,如下所示:

 make qf

基本上,如果您知道(或可以猜测)要构建的文件的输出文件名,则可以通过运行make $FILENAME来告诉make仅构建该文件

提示:查看其他打包系统如何构建相同的 C 程序

如果你在构建 C 程序时遇到困难,那么其他人可能也遇到了同样的问题!每个 Linux 发行版都为其构建的每个软件包提供了构建文件,所以即使你无法直接从该发行版安装软件包,也许你也可以从该发行版那里获得一些构建软件包的技巧。意识到这一点(感谢我的朋友 Dave)对我来说是一个巨大的顿悟。

例如, paperjam的 nix 包中的这一行内容是:

 env.NIX_LDFLAGS = lib.optionalString stdenv.hostPlatform.isDarwin "-liconv";

这基本上是在说“传递链接器标志-liconv在 Mac 上构建它”,所以这是我们可以用来构建它的线索。

同一个文件还显示env.NIX_CFLAGS_COMPILE = "-DPOINTERHOLDER_TRANSITION=1"; 。我不确定这是什么意思,但是当我尝试构建paperjam包时,我确实收到了一个关于PointerHolder的错误,所以我猜这与“PointerHolder 转换”有某种联系。

步骤 5:安装二进制文件

编译完程序后,你可能想把它安装到某个地方!有些Makefile包含一个install目标,让你可以用make install将工具安装到系统上。我总是有点担心这个问题(文件该放哪儿?万一以后想卸载怎么办?),所以如果我编译的是一个非常简单的程序,我通常会手动复制二进制文件来安装它,就像这样:

 cp qf ~/bin

第 6 步:也许制作您自己的包裹!

搞清楚了这些之后,我意识到我可以用新学到的make知识为 Homebrew 贡献一个paperjam软件包!这样以后我就能直接在以后的系统上brew install paperjam 。

好消息是,即使所有不同的打包系统的细节各不相同,它们从根本上来说都使用 C 编译器和链接器。

即使你不是 C 程序员,了解一些 C 语言也是很有用的

我认为所有这些都是一个有趣的例子,说明了解 C 程序工作原理的一些基础知识(例如“它们有头文件”)很有用,即使你一生中从未打算编写一个非平凡的 C 程序。

能够自己编译 C/C++ 程序感觉很好,尽管我对所有的编译器和链接器标志仍然不是完全有信心,而且我仍然计划除了“运行./configure来生成Makefile ”之外,永远不学习任何有关自动工具如何工作的知识。

另外,我遗漏了一件重要的事情,那就是LD_LIBRARY_PATH / DYLD_LIBRARY_PATH (使用它来告诉动态链接器在运行时在哪里找到动态链接文件),因为我不记得上次遇到LD_LIBRARY_PATH问题并且找不到示例。

原文: https://jvns.ca/blog/2025/06/10/how-to-compile-a-c-program/

本站文章系自动翻译,站长会周期检查,如果有不当内容,请点此留言,非常感谢。
  • Abhinav
  • Abigail Pain
  • Adam Fortuna
  • Alberto Gallego
  • Alex Wlchan
  • Answer.AI
  • Arne Bahlo
  • Ben Carlson
  • Ben Kuhn
  • Bert Hubert
  • Bits about Money
  • Brian Krebs
  • ByteByteGo
  • Chip Huyen
  • Chips and Cheese
  • Christopher Butler
  • Colin Percival
  • Cool Infographics
  • Dan Sinker
  • David Walsh
  • Dmitry Dolzhenko
  • Dustin Curtis
  • Elad Gil
  • Ellie Huxtable
  • Ethan Marcotte
  • Exponential View
  • FAIL Blog
  • Founder Weekly
  • Geoffrey Huntley
  • Geoffrey Litt
  • Greg Mankiw
  • Henrique Dias
  • Hypercritical
  • IEEE Spectrum
  • Investment Talk
  • Jaz
  • Jeff Geerling
  • Jonas Hietala
  • Josh Comeau
  • Lenny Rachitsky
  • Liz Danzico
  • Lou Plummer
  • Luke Wroblewski
  • Matt Baer
  • Matt Stoller
  • Matthias Endler
  • Mert Bulan
  • Mostly metrics
  • News Letter
  • NextDraft
  • Non_Interactive
  • Not Boring
  • One Useful Thing
  • Phil Eaton
  • Product Market Fit
  • Readwise
  • ReedyBear
  • Robert Heaton
  • Ruben Schade
  • Sage Economics
  • Sam Altman
  • Sam Rose
  • selfh.st
  • Shtetl-Optimized
  • Simon schreibt
  • Slashdot
  • Small Good Things
  • Taylor Troesh
  • Telegram Blog
  • The Macro Compass
  • The Pomp Letter
  • thesephist
  • Thinking Deep & Wide
  • Tim Kellogg
  • Understanding AI
  • 英文媒体
  • 英文推特
  • 英文独立博客
©2025 搞英语 → 看世界 | Design: Newspaperly WordPress Theme