6

阅读我的《Expert C Programming 》一书,我遇到了关于函数插入的章节,以及如果无意中这样做会导致一些严重的难以发现的错误。

书中给出的例子如下:

my_source.c

mktemp() { ... }

main() {
  mktemp();
  getwd();
}

mktemp(){ ... }
getwd(){ ...; mktemp(); ... }

根据这本书,发生的事情main()mktemp()(一个标准的 C 库函数)被my_source.c中的实现插入。尽管main()调用我的实现mktemp()是预期行为,但getwd()(另一个 C 库函数)调用我的实现mktemp()不是。

显然,这个例子是存在于 SunOS 4.0.3 版本的lpr. 这本书继续解释解决方法是将关键字添加到my_source.cstatic的定义mktemp()中;尽管完全更改名称也应该可以解决此问题。

这一章给我留下了一些悬而未决的问题,希望你们能回答:

  1. GCC 有办法警告函数插入吗?我们当然不打算发生这种情况,如果发生这种情况,我想知道。
  2. 我们的软件组是否应该采取将关键字static放在我们不想暴露的所有功能前面的做法?
  3. 静态库引入的函数可以进行插入吗?

谢谢您的帮助。

编辑

我应该注意,我的问题不仅旨在插入标准 C 库函数,还包括其他库中包含的函数,也许是第 3 方,也许是内部创建的函数。本质上,我想捕获任何插入实例,而不管插入函数位于何处。

4

6 回答 6

2

这确实是一个链接器问题。

当您编译一堆 C 源文件时,编译器将为每个文件创建一个目标文件。每个 .o 文件都将包含该模块中的公共函数列表,以及模块中的代码调用但实际上并未在其中定义的函数列表,即该模块期望某个库提供的函数。

当您将一堆 .o 文件链接在一起以生成可执行文件时,链接器必须解决所有这些缺失的引用。这是可能发生插入的点。如果对名为“mktemp”的函数有未解析的引用,并且几个库提供了一个具有该名称的公共函数,它应该使用哪个版本?对此没有简单的答案,是的,如果选择错误,可能会发生奇怪的事情

所以是的,除非您确实需要从其他源文件中使用它,否则在 C 中将所有内容“静态”是一个好主意。事实上,在许多其他语言中,这是默认行为,如果您希望从外部访问它们,则必须将它们标记为“公开”。

于 2010-05-06T17:24:49.223 回答
1

听起来您想要的工具是检测函数中是否存在名称冲突-即,您不希望外部可访问的函数名称意外地具有相同的名称,因此“覆盖”或隐藏具有相同名称的函数图书馆里的名字。

最近有一个与此问题相关的 SO 问题:Linking Libraries with Duplicate Class Names using GCC

在您链接的所有库上使用该--whole-archive选项可能会有所帮助(但正如我在那边的答案中提到的那样,我真的不知道这有多好,也不知道说服构建将选项应用于所有库有多容易)

于 2010-05-06T18:37:43.510 回答
1

纯粹形式上,您描述的插入直接违反了 C 语言定义规则(ODR 规则,用 C++ 的说法)。任何体面的编译器都必须检测这些情况,或者提供检测它们的选项。在 C 语言中定义多个具有相同名称的函数是完全非法的,无论这些函数是在哪里定义的(标准库、其他用户库等)

我知道许多平台通过将一些标准函数定义为弱符号来提供自定义 [标准] 库行为的方法。虽然这确实是一个有用的功能,但我相信编译器仍必须为用户提供强制执行标准诊断的方法(最好基于每个函数或每个库)。

所以,如果你的库中没有弱符号,你也不应该担心插入。如果你这样做(或者你怀疑你这样做),你必须查阅你的编译器文档,看看它是否为你提供了检查弱符号解析的方法。

例如,在 GCC 中,您可以使用 禁用弱符号功能-fno-weak,但这基本上会杀死与弱符号相关的所有内容,这并不总是可取的。

于 2010-05-06T17:19:35.947 回答
0

如果不需要在它所在的 C 文件之外访问该函数,那么可以,我建议将函数static.

您可以做的一件事是使用具有可配置语法突出显示的编辑器。我个人使用SciTE,并且我已将其配置为以红色显示所有标准库函数名称。这样,很容易发现我是否在重复使用我不应该使用的名称(不过,编译器没有强制执行任何操作)。

于 2010-05-06T17:03:13.250 回答
0

nm -o编写一个在所有 .o 文件和库上运行的脚本并检查是否在程序和库中都定义了外部名称相对容易。只是 Unix 链接器不提供的众多明智的服务之一,因为它停留在 1974 年,一次只查看一个文件。(尝试以错误的顺序放置库,看看是否会收到有用的错误消息!)

于 2010-05-07T02:49:18.833 回答
0

当链接器尝试链接单独的模块时,会发生插入。 它不能出现在模块内。如果模块中有重复的符号,链接器会将其报告为错误。

对于 *nix 链接器,意外插入是一个问题,链接器很难防范它。出于此答案的目的,请考虑两个链接阶段:

  1. 链接器将翻译单元链接到模块(基本上是应用程序或库)。
  2. 链接器通过在模块中搜索来链接任何剩余的未找到符号。

考虑“专家 C 编程”和 SiegeX 的问题中描述的场景。链接器首先尝试构建应用程序模块。它认为符号 mktemp() 是一个外部符号,并尝试为该符号找到一个函数定义。链接器在应用程序模块的目标代码中找到函数的定义,并将符号标记为已找到。在这个阶段,符号 mktemp() 完全解析。它不被认为是试探性的,以便考虑到另一个模块可能定义符号的可能性。在许多方面这是有道理的,因为链接器应该首先尝试并解析它当前链接的模块中的外部符号。在其他模块中链接时,它只会搜索未找到的符号。此外,由于符号已被标记为已解决,链接器将在需要解析此符号的任何其他情况下使用应用程序 mktemp()。因此,库将使用 mktemp() 的应用程序版本。

防止该问题的一种简单方法是尝试使您的应用程序或库中的所有外部符号都是唯一的。对于仅在有限的基础上共享的模块,通过附加唯一标识符确保模块中的所有外部符号都是唯一的,这可以很容易地完成。

对于广泛共享的模块,组成唯一名称是一个问题。

于 2016-07-14T11:53:23.187 回答