覆盖C库函数,调用原始函数
我对这段代码的工作方式和原因感到有些困惑。 在我参与的任何项目中,我实际上没有遇到过这种情况,我甚至都没想过要自己做。
override_getline.c:
#include #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 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
定义,调用自定义函数,如果注释掉,则调用普通库函数,并且两者都按预期工作。
问题
-
这个的正确用语是什么? “覆盖”,“阴影”,还有什么?
-
这是gcc特定的,还是POSIX,还是ANSI C,甚至是未定义的?
-
如果函数是ANSI C函数或(如此处)POSIX函数,它会有什么不同吗?
-
调用覆盖函数在哪里? 至少在同一个链接中的其他
.o
文件中,我假设.a
文件也添加到链接命令中。 如何使用链接器的-l
命令行选项添加静态或动态库? -
如果可能,如何从覆盖的getline调用getline的库版本?
在库中搜索之前,链接器将首先在命令行中搜索您提供的符号。 这意味着只要它看到已经定义了getline
,它就不再寻找另一个getline
符号。 这就是连接器在所有平台上的工作方式。
这当然对你的第五点有影响,因为没有可能调用“原始” getline
,因为你的函数是从链接器的角度来看的原始函数。
对于第五点,您可能想看看例如这个旧答案 。
在程序中没有标准的方法可以使用两个相同名称的函数,但是使用类似UNIX的实现(特别是GNU libc),您可能能够避免这种情况:
#define _GNU_SOURCE #include #include 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
。
这里发生的是你依赖于链接器的行为。 链接器在查看标准库中的版本之前会找到getline
的实现,因此它会链接到您的例程。 所以实际上你通过链接顺序机制覆盖了这个function。 当然,其他链接器可能表现不同,我相信如果指定适当的命令行开关,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
,这相当丑陋。
如果您与共享库链接,您所看到的是预期的行为。 Linker会将它分配给您的函数,就像它第一次一样。 它也可以从任何其他外部库函数中正确调用,因为链接器将在扫描链接库时使您的函数可导出。
但是 – 如果你没有外部库链接到你的函数(因此它没有标记为可导出,并且没有插入符号表),然后dlopen()某些想要在运行时使用它的库 – 它不会找到所需的function。 此外,如果您首先执行(RTLD_NOW | RTLD_GLOBAL)原始库,则每个后续的dlopen()库都将使用此库代码,而不是您的库代码。 您的代码(或者您在编译阶段链接的任何库,而不是运行时)仍将坚持您的function,无论如何。