C语言 如何理解GNU源代码?

a64a0gku  于 2022-12-17  发布在  其他
关注(0)|答案(9)|浏览(157)

如果这听起来有点傻,我真的很抱歉。我刚刚阅读K&R,我做了一些练习。今年夏天,为了我的项目,我想重新实现一个linux实用程序,以进一步扩展我对C的理解,所以我下载了GNU tar和sed的源代码,因为它们看起来都很有趣。但是,我很难理解它从哪里开始,主要实现在哪里,所有奇怪的宏都来自哪里等等。
我有很多时间,所以这不是一个问题。我应该先熟悉GNU工具链(例如make,binutils,...)来理解程序吗?或者我应该从更小的东西开始(如果有这样的东西的话)?
如果有必要的话,我对Java,C++和Python有一点经验。
谢谢!

pb3skfrl

pb3skfrl1#

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,后者执行实际写入操作。
我希望我已经向你介绍了很多,并向你展示了我是如何浏览源代码的。如果你有相关的问题,请随时与我联系。

1tuwyuhd

1tuwyuhd2#

tarsed这样的程序有两个问题(当然,这只是我的观点!)首先,它们都很“老”了。这意味着多年来有多个人在维护它们,有着不同的编码风格和不同的个性。对于GNU实用程序来说,这通常是相当不错的,因为它们通常强制执行一个相当一致的编码风格,但这仍然是一个问题。另一个问题是它们难以置信的“便携性”。通常“便携性”被视为一件好事,但当走到极端时,这意味着您的代码库最终充满了一些小技巧和窍门来解决特定硬件和系统中的模糊错误和极端情况。这意味着有很多极端情况和模糊的硬件/编译器/操作系统需要考虑。
如果你想学习C语言,那么我会说最好的开始不是学习别人写的代码。相反,尝试自己写代码。如果你真的想从现有的代码库开始,选择一个被积极维护的代码库,在那里你可以看到其他人正在做的修改,跟随邮件列表上的讨论,等等。
对于tarsed这样成熟的程序,你可以看到讨论的“结果”,但是你无法看到软件设计决策和变更是如何实时进行的,这只有在主动维护的软件中才能做到。
当然,这只是我的看法,如果你愿意,你可以持保留态度:)

2wnc66cl

2wnc66cl3#

为什么不下载coreutils(http://ftp.gnu.org/gnu/coreutils/)的源代码,看看像yes这样的工具呢?不到100行的C代码和一个功能齐全、有用和真正基本的GNU软件。

kpbwa7wx

kpbwa7wx4#

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

nbewdwxp

nbewdwxp5#

我知道有时浏览C代码会很麻烦,尤其是如果你不熟悉它的话。我建议你使用tool,它可以帮助你浏览函数、符号、宏等。然后找到main()函数。
当然,您需要熟悉这些工具,但不需要成为Maven。

7vux5j2d

7vux5j2d6#

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

bxjv4tth

bxjv4tth7#

我建议使用ctagscscope浏览,你可以使用vim/emacs,它们在开源世界中被广泛使用。
它们应该存在于每个主流linux发行版的存储库中。

am46iovg

am46iovg8#

理解一些使用了大量宏、实用函数等的代码可能很难。为了更好地浏览随机C或C++软件的代码,我建议使用以下方法,这也是我通常使用的方法:
1.安装Qt开发工具和Qt Creator
1.下载您想要检查的源代码,并设置它们以进行编译(对于GNU的东西,通常只是./configure)。
1.在源目录的根目录下运行qmake -project,生成Qt Creator的Qt .pro文件。
1.在Qt Creator中打开.pro文件(当它询问时,不要使用阴影构建)。
1.为了安全起见,在Qt Creator项目视图中,删除默认的构建步骤。.pro文件只是用于在Qt Creator中导航。
1.可选:如果要在Qt Creator下构建和运行/调试,请设置自定义构建和运行步骤。仅导航时不需要。
1.使用Qt Creator来浏览代码。特别注意定位器(kb快捷键Ctrl+K),可以通过名称查找内容,“跟随光标下的符号”(kb快捷键F2)和“查找用法”(kb快捷键Ctrl-Shift-U)。

von4xj4u

von4xj4u9#

我不得不看一下“sed”,看看问题出在哪里;它不应该那么大。我看了看,我明白了问题所在,我感觉就像赫斯顿在海滩上第一眼看到一个破碎的雕像。我将要描述的关于“sed”的所有内容可能也适用于“tar”。但我还没有看过它。
很多GNU代码变得非常糟糕--到了无法维护的病态遗产的地步--原因我不知道。我不知道它发生的确切时间,也许是90年代末或2000年代初,但就像有人扳动了开关,突然,所有漂亮的模块化大部分是自己的包含的代码小部件变得非常混乱,各种各样的无关纠缠与应用程序本身试图做的事情几乎没有联系。
在您的情况下,“sed”:一个完整的库被(不必要地)拖进了应用程序。这种情况至少早在4.2版本(你查询之前的最后一个版本)就出现了,可能在那之前--我得检查一下。
另一件糟糕的事情是构建系统(再次)到了不可维护的地步。
所以,你真的在谈论遗产拯救。
我的建议......这是通用的 * 任何 * 代码库,已经存在了很长一段时间......是挖掘尽可能深,并回到其最早的形式第一;并分支到其他“sed”-如UNIX归档中的那些。
https://www.tuhs.org/Archive/
或在BSD存档中:
https://github.com/freebsd
https://github.com/weiss/original-bsd
(the第二个在其早期提交中更深入地研究了早期BSD。)
GNU页面上的许多“sed”--但不是全部--可以在GNU sed页面上的“Downloads”下找到,作为链接“mirrors”:
https://www.gnu.org/software/sed/
版本1.18仍然是完整的。版本1.17隐含地是完整的,因为那里有一个1.17到1.18的差异。两个版本都没有所有额外的东西堆积在它上面。它更能代表GNU软件看起来像什么,在被所有的纠缠打结之前。
它实际上非常小--总共只有8863行用于 *.c和 *.h文件。
对我来说,分析任何代码库的过程都是对原始代码的破坏,并且总是需要大量的重构和重新设计;简化来自于更好地、更自然地编写它,同时保持或增加它的功能。几乎总是,它是由只有几年经验的人编写的(我的意思是:例如,少于20年),并且因此没有获得完全成熟的母语流利性,也没有能够很好地编程的广泛背景。
对于这个,如果你做同样的事情,强烈建议你已经有了一些测试套件或者增加了一些。例如,在4.2版本的软件中就有一个,尽管它可能是在1.18和4.2之间增加的压力测试新功能。只是要注意这一点。(因此,可能需要减少测试套件以适合1.18。)您所做的每一个更改都必须通过套件中的任何测试进行验证。
你需要有流利的母语......或者通过练习和其他类似的练习来获得它的意愿和能力。如果你没有足够的年数,你就会撞上一堵软墙。你走得越深,前进可能就越困难。这表明你还没有足够的经验,也没有足够的广度。所以,这个练习会成为你学习经验的一部分,你必须努力完成。
因为第一个版本的日期是如此之早,你将不得不做一些重写,无论如何,只是为了使它符合标准。后来的版本可以作为一个指南,为这个过程。在最低限度,它应该被带到C99,因为这是实际上强制作为POSIX的一部分。换句话说,你至少应该尽可能最新的本世纪!
仅仅是让它发挥功能的挑战就已经足够了。仅仅通过这样做,你就可以学到很多东西。让它发挥功能的过程就是建立一个“基线”。一旦你做到了,你就有了自己的版本,你可以从“分析”开始。
一旦建立了基线,那么你就可以全速前进重构和再工程。测试套件帮助提供对障碍和插入错误的覆盖。你应该保留你拥有的所有版本(重新)在本地存储库中制作,以便您可以跳回到更早的版本,以防您需要跟踪突然出现的测试失败或其他bug。一些bug,您可能会发现,都植根于所有的方式回到开始(因此:发现隐藏的缺陷)。

在你有了基线之后(重新)编写到你满意的程度,然后你可以继续在后续版本中分层。在GNU的存档中,1.18直接跳到2.05。你必须在两者之间做一个“比较”来查看所有的变化在哪里,然后将它们移植到您的1. 18版本中,以获得您的2. 05版本。这将帮助您更好地理解所做更改解决的问题,以及进行了哪些更改。
在某个时候,你会碰到GNU的垃圾墙。在GNU的历史档案中,2.05版直接跳到了3.01版。一些纠葛开始在3.01版中溜走。所以,我们这里有一堵软墙。但是3.01版也有一个早期的测试套件,你应该在1.18版中使用它,而不是4.2版的测试套件。
当您遇到GrungeWall时,您将直接看到这些纠缠是什么,您必须决定是沿着前进还是将它们抛在一边。我无法告诉您哪个方向是兔子洞,除了SED在很长一段时间内一直非常好,它的大部分或全部都是POSIX标准中列出并强制要求的(即使是当前的版本),以及在版本3服务于这一目的之前有什么。
我运行了diff。在Python 2.05和Python 3.01之间,diff文件是5000行。好吧。这(大部分)很好,对于正在开发的代码来说是很自然的,但是其中一些可能来自于软Grunge Wall。在Python 3.01和Python 4.2上运行diff会产生超过60000行的diff文件。你只需要问自己:一个少于10000行的程序--遵守国际标准(POSIX)--怎么会产生60000行的差异呢?答案是:这就是我们所说的 bloat。因此,在Python 3.01和Python 4.2之间,您会发现一个代码库中非常常见的问题:膨胀的兴起。
这就能告诉你(“沿着去兜风”与“把它扔一边”)是兔子洞,我可能只是坚持使用3.01,粗略地回顾一下3.01和4.2之间的差异以及更改日志,以获得更改的概述,然后就这样吧。除了可能找到一种不同的方式来写他们认为有必要改变的东西,如果理由是有效的话。
我以前做过遗产拯救,那时“遗产”这个词还没有进入大多数人的词汇表,我很快就能认出它的标志性迹象。
我们已经在一些大型的代码库中看到了这种情况。实际上,Wayland对X11的取代是一次大规模的遗产拯救行动。也有可能正在进行的GNU的gcc对clang的取代可以被认为是这种情况的一个例子。

相关问题