Переопределение функций библиотеки C, вызов оригинала

Я немного озадачен тем, как и почему этот код работает так, как он. Я на самом деле не сталкивался с этим в любом проекте, над которым я работал, и я даже не думал о том, чтобы сделать это сам.

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 пользовательская функция вызывается, и если она закомментирована, вызывается функция нормальной библиотеки, и обе работают как ожидалось.

Вопросы

  • Каков правильный термин для этого? "Переопределение", "затенение", что-то еще?

  • Является ли это gcc-специфическим, или POSIX, или ANSI C, или даже undefined во всех?

  • Не имеет значения, является ли функция ANSI C или (как здесь) функцией POSIX?

  • Где вызывается функция переопределения? Другими .o файлами в той же ссылке, по крайней мере, и я предполагаю, что .a файлы добавлены в ссылку. Как насчет статических или динамических библиотек, добавленных с помощью опции -l командной строки компоновщика?

  • Если это возможно, как мне вызвать библиотечную версию getline из overriden getline?

Ответ 1

Линкером будет сначала искать файлы, которые вы предоставляете в командной строке для символов, перед их поиском в библиотеках. Это означает, что как только он увидит, что getline был определен, он больше не будет искать другой символ getline. Вот как работают линкеры на всех платформах.

Это, конечно, имеет значение для вашего пятого пункта, поскольку нет возможности вызвать "оригинал" getline, так как ваша функция является оригиналом с точки зрения компоновщика.

В пятом пункте вы можете посмотреть, например, этот старый ответ.

Ответ 2

Нет стандартного способа иметь две функции с одним и тем же именем в вашей программе, но с некоторыми 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.

Ответ 3

Что здесь происходит, так это то, что вы полагаетесь на поведение компоновщика. Компилятор находит вашу реализацию 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, что довольно уродливо.

Ответ 4

Ожидается, что вы увидите поведение, если вы связываетесь с разделяемыми библиотеками. Linker просто назначит его вашей функции, как это было раньше. Он также будет корректно вызываться из любых других функций внешних библиотек, поскольку компоновщик сделает вашу функцию экспортируемой, когда она сканирует связывание библиотек.

Но если вы, скажем, не имеете внешних библиотек, которые ссылаются на вашу функцию (поэтому она не помечена как экспортируемая и не вставлена ​​в таблицу символов), а затем dlopen() некоторая библиотека, которая хочет ее использовать во время выполнения - он не найдет нужную функцию. Кроме того, если вы впервые создали оригинальную библиотеку dlopen (RTLD_NOW | RTLD_GLOBAL), каждая последующая библиотека dlopen() 'd будет использовать этот библиотечный код, а не ваш. Ваш код (или любые библиотеки, с которыми вы связаны во время фазы компиляции, а не время исполнения) все равно будут придерживаться вашей функции, независимо от того, что.