覆盖C库函数,调用原始函数

时间:2013-09-26 08:25:26

标签: c gcc

我对这段代码的工作方式和原因感到有些困惑。在我参与的任何项目中,我实际上没有遇到过这种情况,我甚至都没想过会自己做这件事。

override_getline.c:

#include <stdio.h>

#define OVERRIDE_GETLINE

#ifdef OVERRIDE_GETLINE
ssize_t getline(char **lineptr, size_t *n, FILE *stream)
{
    printf("getline &lineptr=%p &n=%p &stream=%p\n", lineptr, n, stream);
    return -1; // note: errno has undefined value
}
#endif

main.c中:

#include <stdio.h>

int main()
{
    char *buf = NULL;
    size_t len = 0;
    printf("Hello World! %zd\n", getline(&buf, &len, stdin));
    return 0;
}

最后,示例编译并运行命令:

gcc main.c override_getline.c && ./a.out

使用OVERRIDE_GETLINE定义,调用自定义函数,如果注释掉,则调用普通库函数,并且两者都按预期工作。

问题

  1. 这个的正确用语是什么? “覆盖”,“遮蔽”,还有其他什么?

  2. 这是gcc特定的,还是POSIX,还是ANSI C,甚至都未定义?

  3. 如果函数是ANSI C函数或(如此处)POSIX函数会有什么不同吗?

  4. 调用重写函数在哪里?至少在同一个链接中使用其他.o个文件,我也假设.a个文件也添加到链接命令中。如何使用链接器的-l命令行选项添加静态或动态库?

  5. 如果可能,如何从覆盖的getline调用getline的库版本?

4 个答案:

答案 0 :(得分:16)

在库中搜索之前,链接器将首先在命令行中搜索您提供的符号。这意味着,只要它看到已定义getline,它就不会再查找另一个getline符号。这就是连接器在所有平台上的工作方式。

这当然会对你的第五点产生影响,因为没有可能调用“原始”getline,因为你的函数 原来是从...链接器。

对于第五点,您可能需要查看例如this old answer

答案 1 :(得分:3)

在程序中没有标准的方法可以使用两个相同名称的函数,但是使用类似UNIX的实现(特别是GNU libc),你可能可以解决这个问题:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>

ssize_t getline(char **lineptr, size_t *n, FILE *stream)
{
   ssize_t (*realfunc)(char**, size_t *, FILE*) =
       (ssize_t(*)(char**, size_t *, FILE*))(dlsym (RTLD_NEXT, "getline"));
   return realfunc(lineptr, n, stream);
}

为此,您需要与-ldl相关联。

答案 2 :(得分:1)

这里发生的是你依赖于链接器的行为。链接器在看到标准库中的版本之前会找到getline的实现,因此它会链接到您的例程。因此,实际上您通过链接顺序的机制覆盖了该功能。当然,其他链接器可能表现不同,我相信如果指定适当的命令行开关,gcc链接器甚至可能会抱怨重复的符号。

为了能够调用您的自定义例程和库例程,您通常会使用宏,例如

#ifdef OVERRIDE_GETLINE
#define GETLINE(l, n, s) my_getline(l, n, s)
#else
#define GETLINE(l, n, s) getline(l, n, s)
#endif

#ifdef OVERRIDE_GETLINE
ssize_t my_getline(char **lineptr, size_t *n, FILE *stream)
{
   // ...
   return getline(lineptr, n, stream);
}
#endif

请注意,这需要您的代码将getline称为GETLINE,这相当丑陋。

答案 3 :(得分:0)

如果您使用共享库进行链接,您会看到预期的行为。链接器只会将它分配给您的函数,因为它是第一个。它也可以从任何其他外部库函数中正确调用,因为链接器会在扫描链接库时使您的函数可以导出。

但是 - 如果你没有外部库链接到你的函数(因此它没有标记为可导出,并且没有插入符号表),然后dlopen()一些想要使用它的库在运行时 - 它将找不到所需的功能。此外,如果您首先执行(RTLD_NOW | RTLD_GLOBAL)原始库,则每个后续的dlopen()库都将使用库代码,而不是您的。您的代码(或者您在编译阶段链接的任何库,而不是运行时)仍将坚持您的功能,无论如何。