如何理解GNU源代码?

17

如果我说的有点愚蠢,我真的很抱歉。我刚刚读完了K&R并做了一些练习。今年夏天,为了进一步理解C语言,我计划重新实现一个Linux实用程序,所以我下载了GNU tar和sed的源代码,它们看起来都很有趣。但是,我不知道从哪里开始,主要实现在哪里,所有奇怪的宏定义都是从哪里来的等。

我有很多时间,所以时间不是问题。我应该先熟悉GNU工具链(例如make,binutils等),以便理解这些程序吗?还是我应该从一些更小的东西(如果有的话)开始?

我有一点Java、C++和Python的经验,如果有关系的话。

谢谢!

9个回答

17
GNU程序庞大而复杂。GNU Hello World的大小表明即使是最简单的GNU项目也需要大量的代码和配置。
对于初学者来说,autotools很难理解,但您不需要理解它们就可以阅读代码。即使您修改了代码,大多数时候只需运行make编译您的更改即可。
要阅读代码,您需要一个好的编辑器(VIM、Emacs)或IDE(Eclipse)以及一些工具来浏览源代码。tar项目包含一个src目录,这是一个很好的起点。程序总是从main函数开始,所以请这样做。
grep main *.c

或者使用您的IDE搜索此函数。它在tar.c中。现在,跳过所有的初始化内容,直到

/* Main command execution.  */

在那里,你会看到一个子命令的开关。如果你传递了-x,它会执行这个操作,如果你传递了-c,它会执行那个操作,等等。这是这些命令的分支结构。如果你想知道这些宏是什么,运行

grep EXTRACT_SUBCOMMAND *.h

在这里,你可以看到它们被列在common.h文件中。

在EXTRACT_SUBCOMMAND下面,你会看到一些有趣的东西:

read_and (extract_archive);

read_and()的定义(再次使用grep获得):

read_and (void (*do_something) (void))

单个参数是类似回调的函数指针,因此read_and应该会读取一些内容,然后调用函数extract_archive。再次使用grep查看,您将看到以下内容:
  if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
    {
      if (fun && (*fun) (current_stat_info.file_name, typeflag)
      && backup_option)
    undo_last_backup ();
    }
  else
    skip_member ();

请注意,真正的工作发生在调用fun时。 fun再次是一个函数指针,在prepare_to_extract中设置。 fun可能指向extract_file,它执行实际的写入操作。
我希望我已经为您详细介绍了这个问题,并向您展示了如何浏览源代码。如果您有相关问题,请随时与我联系。

这是关于编程的内容,请将其翻译成中文。请仅返回翻译后的文本:+1 详细示例。顺便说一句,这也是如何使简单事物变得复杂的例子。由于某种原因,一些人认为这很有趣 :-/ - PauliL

8
tarsed这样的程序存在两个问题(当然,这只是我的观点!)。首先,它们都非常古老。这意味着多年来已经有多人对其进行维护,编码风格和性格各异。对于GNU实用程序来说,通常情况下还好,因为它们通常会强制执行合理一致的编码风格,但这仍然是一个问题。另一个问题是它们的可移植性非常高。通常,“可移植性”被视为一件好事,但是当它被推向极端时,这意味着您的代码库中充满了小技巧和技巧,以解决特定硬件和系统中的奇怪错误和边缘情况。对于像tarsed这样广泛移植的程序来说,这意味着需要考虑很多边缘情况和奇怪的硬件/编译器/操作系统。
如果您想学习C语言,那么我认为最好的方法不是尝试学习其他人编写的代码。相反,尝试自己编写代码。如果您真的想从现有代码库开始,请选择一个正在积极维护的代码库,您可以在其中看到其他人进行的更改,并随时关注邮件列表中的讨论等。
对于像tarsed这样成熟的程序,您只能看到会议的结果,但无法实时查看软件设计决策和更改是如何进行的。这只能通过积极维护的软件来实现。
当然,这只是我的观点,您可以视情况而定。 :)

我认为学习C语言最好的方法是通过编程。然而,一旦你掌握了语法和语言的细微差别,阅读优秀的代码总是有帮助的,这将让你了解如何实际应用语言的语法/数据结构的新方法。 - itisravi
@itisravi:我仍然相信通过观察开发过程学习比事后学习更好。例如,如果你看到一段代码,你会想“为什么他们这样做,为什么不用另一种方式?”如果你可以在邮件列表上发布消息并询问,那么你将学到比只是接受已经编写的任何内容要多得多。 - Dean Harding

7
为什么不下载coreutils的源代码(http://ftp.gnu.org/gnu/coreutils/),并查看像yes这样的工具?少于100行的C代码,完全功能、有用且非常基本的GNU软件。

5

GNU Hello 可能是最小、最简单的 GNU 程序,易于理解。


3
在GNU Hello的最新版本(2.4.90)中,隐藏着一个笑话,即其作为一个tar.gz压缩文件需要下载566KB大小。这太可怕了。 - unwind
2
@unwind GNU Hello不仅仅是一个“Hello World”程序:它用许多语言打印“Hello World”,在你需要时冲咖啡并借给你钱。 - ereOn
2
GNU Hello是一个框架,展示了Autotools的基本用法,如何解析命令行选项以及如何支持国际化。大部分下载内容可能是消息文件,例如这个 - Paolo Bonzini

1

我知道有时候浏览 C 代码很混乱,特别是如果你不熟悉它的话。我建议你使用一个工具,它将帮助你浏览函数、符号、宏等。然后找到 main() 函数。

当然,你需要熟悉这些工具,但你不需要成为专家。


+1 我非常高兴 Source Navigator 有一个新版本。我在2006年使用过它,当时它看起来像是一个不活跃的项目。无论如何,这是一个非常好的工具。 - INS

1

如果你还不知道如何使用grep,那就学习一下吧,并用它来搜索main函数和其他感兴趣的内容。你可能还想使用代码浏览工具,比如ctagscscope,它们也可以与vim和emacs集成,或者如果你更喜欢的话,使用一个IDE。


0

我建议使用ctagscscope进行浏览。您可以将它们与vim/emacs一起使用。它们在开源世界中被广泛使用。

它们应该在每个主要Linux发行版的存储库中都有。


0

理解使用了许多宏、实用函数等代码可能很困难。为了更好地浏览随机 C 或 C++ 软件的代码,我建议采用以下方法,这也是我通常使用的:

  1. 安装Qt开发工具和Qt Creator。

  2. 下载您想要检查的源代码,并为编译设置它们(通常只需对GNU stuff运行./configure)。

  3. 在源目录的根目录中运行qmake -project,以生成用于Qt Creator的Qt .pro文件。

  4. 在Qt Creator中打开.pro文件(当它询问时,请不要使用阴影构建)。

  5. 为了安全起见,在Qt Creator项目视图中删除默认的构建步骤。 .pro文件仅用于在Qt Creator内导航。

  6. 可选:如果您想在Qt Creator下构建、运行/调试,则设置自定义构建和运行步骤。仅导航时不需要。

  7. 使用Qt Creator浏览代码。特别注意定位器(kb快捷键Ctrl+K)通过名称查找内容,“跟随光标下的符号”(kb快捷键F2)和“查找用法”(kb快捷键Ctrl-Shift-U)。


-1
我不得不看一下“sed”才能看出问题在哪里;它不应该那么大。我看了一下,知道了问题所在,感觉就像查尔顿·赫斯顿(Charleton Heston)在海滩上第一次看到破碎的雕像一样。我将要描述的关于“sed”的所有内容也可能适用于“tar”。但我还没有看过它。
许多GNU代码因为我不知道的原因而严重混乱 - 达到了无法维护的病态遗留状态。我不知道确切的时间,也许是在1990年代末或2000年代初,但就像有人按下了开关,突然间,所有漂亮的模块化自包含代码小部件都被大量混淆,与应用程序本身试图做的事情几乎没有任何联系。
在你的情况下,“sed”:整个库(不必要地)随应用程序一起使用。这至少是在版本4.2(您查询之前的最后一个版本)时的情况,可能在此之前 - 我需要检查一下。
另一件被搞糟的事情是构建系统(再次)达到了无法维护的程度。
所以,你真的在谈论遗留救援。

我的建议是......对于任何存在已久的代码库都适用......尽可能深入挖掘并首先回溯到其最早的形式;并且要扩展视野,查看其他“sed” - 如UNIX存档中的那些。

https://www.tuhs.org/Archive/

或者在BSD存档中:

https://github.com/freebsd

https://github.com/weiss/original-bsd

(第二个更深入地探讨了早期BSD在其早期提交中的情况。)

GNU页面上的许多“sed” - 但不是全部 - 可以在GNU sed页面上的“下载”下找到一个名为“镜像”的链接:

https://www.gnu.org/software/sed/

版本1.18仍然完好无损。版本1.17也隐含完好无损,因为那里有一个1.17到1.18的差异存在。两个版本都没有所有额外的东西堆积在上面。它更代表了GNU软件在变得混乱之前的样子。

实际上它非常小 - 所有*.c和*.h文件只有8863行。从这里开始。

对我来说,分析任何代码库的过程都会破坏原始代码,并且总是需要大量的重构和重新设计;并且简化来自于更好地编写本地化代码,同时保持或增加其功能。几乎总是由那些只有几年经验(我的意思是:不到20年)的人编写的,因此他们没有完全掌握语言的本地流利度,也没有足够的背景来编写良好的程序。

如果你也这样做,强烈建议你已经有一些测试套件或者添加了一些。例如,在4.2版本软件中就有一个测试套件,尽管它可能会对1.18和4.2之间新增的新功能进行压力测试。只要注意这一点。(因此,可能需要缩小测试套件以适应1.18。)你所做的每一个更改都必须通过你的测试套件进行验证。

你需要具备母语级别的语言流利度......否则,你需要愿意并有能力通过执行此类练习和其他类似练习来获得它。如果你没有足够的经验,你将会遇到一个软墙。你深入挖掘,前进就会变得更加困难。这表明你还不够有经验,没有足够的广度。因此,这个练习成为你学习经验的一部分,你只需要坚持下去。

由于早期版本的日期,您无论如何都需要进行一些重写,以将其提升到标准水平。稍后的版本可以用作此过程的指南。至少应该将其更新到C99,因为这基本上是POSIX的一部分。换句话说,您至少应该跟上当前世纪的步伐!
使其功能正常的挑战就足够让人练习了。通过这样做,您将学到其中的很多知识。使其运行起来的过程是建立“基线”的过程。一旦完成,您就拥有了自己的版本,并可以开始“分析”。
在建立基线之后,您可以全力以赴地进行重构和重新工程化。测试套件可帮助避免犯错和插入错误。您应该将所有已修改的版本保存在本地存储库中,以便在需要追踪测试失败或其他错误的突然出现时可以跳回到早期版本。您可能会发现,一些错误根源可以追溯到最初(因此:隐藏错误的发现)。

在你满意地重写了基线之后,你可以继续添加后续版本。在GNU的存档中,1.18直接跳到2.05。你需要在两者之间进行“差异”比较,看看所有更改的位置,然后将它们嫁接到你的1.18版本中,以获得你的2.05版本。这将帮助你更好地理解更改所解决的问题以及进行了哪些更改。

在某个时候,你会遇到GNU的Grange Wall。版本2.05直接跳到GNU历史存档中的3.01。一些纠缠开始在3.01版本中出现。因此,我们这里有一个软墙。但是,3.01也有一个早期的测试套件,你应该使用它来测试1.18,而不是4.2的测试套件。

当你遇到 Grunge Wall 时,你将直接看到纠缠的问题,并且你必须决定是否继续前进或放弃它们。我不能告诉你兔子洞的方向,除了 SED 长期以来一直非常好用,大部分甚至都是 POSIX 标准中列出和指定的(即使是当前版本),而版本 3 之前的东西也符合这个目的。

我运行了 diffs,2.05 和 3.01 之间的差异文件有 5000 行。好的。对于正在开发中的代码来说,这基本上很正常,但其中一些可能来自软 Grunge Wall。在 3.01 和 4.2 之间运行 diff,得到一个超过 60000 行的差异文件。你只需要问自己:一个遵守国际标准(POSIX)且不到 10000 行的程序如何能产生 60000 行的差异?答案是:这就是我们所谓的膨胀。因此,在 3.01 和 4.2 之间,你正在见证一个非常常见的代码库问题:膨胀的崛起。

所以,这基本上告诉你了哪个方向(“随波逐流”还是“放弃它”)是兔子洞。我可能会坚持3.01,并简要回顾一下3.01和4.2之间的差异以及更改日志,以获取对更改的概述,然后就此打住,除非为其更改提供的原因是有效的,否则可能会找到另一种编写方式。

我以前做过遗留代码修复,甚至在大多数人的词汇表中没有“遗留”这个词之前就已经这样了,并且很快就能认识到它的标志性迹象。这是人们可能会经历的过程。

我们已经看到一些大型代码库发生了这种情况。实际上,通过Wayland取代X11是一次大规模的遗留代码修复演习。正在进行的GNU's gcc被clang取代也可能被视为其中一个例子。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接