Как правильно назначить указатель, возвращаемый dlsym, переменной типа указателя функции?

Я пытаюсь использовать dlopen() и dlsym() в моем коде и скомпилируйте его с помощью gcc.

вот первый файл.

/* main.c */

#include <dlfcn.h>

int main()
{
    void *handle = dlopen("./foo.so", RTLD_NOW);

    if (handle) {
        void (*func)() = dlsym(handle, "func");
        func();
    }

    return 0;
}

вот второй файл.

/* foo.c */

#include <stdio.h>

void func()
{
    printf("hello, worldn");
}

вот как я компилирую и запускаю код.

$ gcc -std=c99 -pedantic -Wall -Wextra -shared -fPIC -o foo.so foo.c
$ gcc -std=c99 -pedantic -Wall -Wextra -ldl -o main main.c
main.c: In function ‘main’:
main.c:10:26: warning: ISO C forbids initialization between function pointer and ‘void *’ [-Wpedantic]
         void (*func)() = dlsym(handle, "func");
                          ^
$ ./main
hello, world

как я могу избавиться от предупреждения?

тип литья не поможет. Если я попытаюсь ввести cast возвращаемое значение dlsym() в указатель функции, я получаю это предупреждение вместо этого.

main.c:10:26: warning: ISO C forbids conversion of object pointer to function pointer type [-Wpedantic]
         void (*func)() = (void (*)()) dlsym(handle, "func");
                          ^

что убедит ли компилятор, что этот код в порядке?

5 ответов


Если вы хотите быть педантически корректным, не пытайтесь разрешить адрес функции. Вместо этого экспортируйте некоторую структуру из динамической библиотеки:

в библиотеке

struct export_vtable {
   void (*helloworld)(void);
};
struct export_vtable exports = { func };

на абонента

struct export_vtable {
   void (*helloworld)(void);
};

int main() {
   struct export_vtable* imports;
   void *handle = dlopen("./foo.so", RTLD_NOW);

   if (handle) {
        imports = dlsym(handle, "exports");
        if (imports) imports->helloworld();
    }

    return 0;
}

этот метод на самом деле довольно распространен, не для переносимости-POSIX гарантирует, что указатели функций могут быть преобразованы в void* и из void*, но потому, что он обеспечивает большую гибкость.


проблема здесь в том, что указатель на объект тонко отделен от указателя функции. В ISO / IEC 9899: 201x статьи §6.3.2.3 указатели указано:

  1. указатель на Void может быть преобразован в указатель на любой тип объекта. Указатель на любой тип объекта может быть преобразован в указатель на void и обратно; результат должен быть равен оригинальный указатель.

.

  1. указатель на функцию одного типа может быть преобразован в указатель на функция другого типа и обратно; результат должен сравниваться равен исходному указателю. Если преобразованный указатель используется для вызовите функцию, тип которой не совместим с указанным тип, поведение не определено.

таким образом, указатель функции отличается от указателей объектов, и, следовательно, задание void * для указателя функции всегда строго не соответствует.

в любом случае, как я сказал в комментарии, в 99.9999....9999% случаев это разрешено благодаря приложение J-вопросы переносимости, §J. 5.7 функция наведения указателя вышеупомянутого документа, в котором говорится:

  1. указатель на объект или на void может быть приведен к указателю на функция, позволяющая вызывать данные как функцию (6.5.4).
  2. указатель на функцию может быть приведен к указателю на объект или void, позволяющая проверять или изменять функцию (например, отладчиком) (6.5.4).

Теперь на практической стороне техника, которая избегает разделения кода в большем количестве файлов, заключается в использовании прагм для подавления педантичных предупреждений для небольшого фрагмента кода.
Более брутальной формой может быть:

/* main.c */

#include <dlfcn.h>

#pragma GCC diagnostic push    //Save actual diagnostics state
#pragma GCC diagnostic ignored "-pedantic"    //Disable pedantic
int main()
{
    void *handle = dlopen("./foo.so", RTLD_NOW);
    if (handle) {
        void (*func)() = dlsym(handle, "func");
        func();
    }
    return 0;
}
#pragma GCC diagnostic pop    //Restore diagnostics state

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

/* main.c */

#include <dlfcn.h>

#pragma GCC diagnostic push    //Save actual diagnostics state
#pragma GCC diagnostic ignored "-pedantic"    //Disable pedantic
void (*)() __attribute__((always_inline)) Assigndlsym(void *handle, char *func)
{
    return dlsym(handle, func);  //The non compliant assignment is done here
}
#pragma GCC diagnostic pop    //Restore diagnostics state

int main()
{
    void *handle = dlopen("./foo.so", RTLD_NOW);
    if (handle) {
        void (*func)() = Assigndlsym(handle, "func"); //Now the assignment is compliant
        func();
    }
    return 0;
}

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

Итак, сделайте функцию, которая обертывает dlsym function и возвращает указатель на функцию. Поместите его в отдельный файл и скомпилируйте этот файл без -pedantic.


можно использовать union, например:

union {
    void *ptr;
    void (*init_google_logging) (char* argv0);
} orig_func;

orig_func.ptr = dlsym (RTLD_NEXT, "_ZN6google17InitGoogleLoggingEPKc");

orig_func.init_google_logging (argv0);

компилятор только "пытается помочь", поэтому вам нужно использовать два типа:

#include <inttypes.h>

void (*func)() = (void (*)())(intptr_t)dlsym(handle, "func");