Кастинг при использовании dlsym ()

Я использую dlsym() в C, и у меня есть вопрос, должно ли возвращаемое значение dlsym() быть явным или правильным ли оно неявно. Вот функция:

double (*(compile)(void))(double x, double y)
{
    if (system("scan-build clang -fPIC -shared -g -Wall -Werror -pedantic "
           "-std=c11 -O0 -lm foo.c -o foo.so") != 0) {
        exit(EXIT_FAILURE);
    }

    void *handle;
    handle = dlopen("./foo.so", RTLD_LAZY);
    if (!handle) {
        printf("Failed to load foo.so: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    foo f;
    f = (foo)dlsym(handle, "foo");
    if (!f) {
        printf("Failed to find symbol foo in foo.so: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    return f;
}

Функция compile() не принимает значения и возвращает указатель на функцию, которая принимает два double в качестве входных данных и возвращает двойное значение. Затем я устанавливаю системный вызов, который компилирует общий объект foo.so. Затем я открываю foo.so с помощью dlopen(). Затем dlsym() находит foo в foo.so и возвращает объект типа foo, который я определил в заголовке как:

typedef double (*foo)(double, double);

Надо ли бросать dlsym()?


person lord.garbage    schedule 20.07.2015    source источник
comment
Я должен отметить, что я нашел страницу руководства для dlsym() немного непрозрачной в этом отношении, но если кто-то может просветить меня на странице руководства, это также поможет.   -  person lord.garbage    schedule 21.07.2015
comment
Техническое исправление 2013 г. к POSIX.1-2008 (также известное как POSIX.1-2013) улучшило ситуацию, потребовав, чтобы соответствующие реализации поддерживали приведение 'void *' к указателю на функцию. Тем не менее, некоторые компиляторы (например, gcc с опцией -pedantic) могут жаловаться на приведение типов, используемое в этой программе. Когда я компилирую без приведения, в настоящее время жалуются gcc и clang.   -  person lord.garbage    schedule 21.07.2015
comment
если компилятор не пожалуется, я бы его не использовал. Если он жалуется, просто обязательно бросьте его, потому что он не видит в этом вреда.   -  person Pradheep    schedule 21.07.2015
comment
@Pradheep: приведение указателя объекта к указателю функции или наоборот - это неопределенное поведение. Итак, определенно есть вероятность причинения вреда.   -  person too honest for this site    schedule 21.07.2015
comment
Как я вижу, dlsys, который я использовал, всегда возвращал определенную функцию, поскольку вы уверены, что эта конкретная функция будет присутствовать в вашем файле so. Единственное, что может пойти не так, это, конечно, подпись функции. Это можно проверить в вашем модульном тесте, чтобы убедиться, что это правильный   -  person Pradheep    schedule 21.07.2015
comment
к тому же я не отрицал этот вопрос   -  person Pradheep    schedule 21.07.2015
comment
IIRC - старое предложение к некоему стандарту POSIX, определяющее dlfsym именно для этой цели (но это не было принято).   -  person Basile Starynkevitch    schedule 12.07.2017
comment
Кстати, я предлагаю вместо этого typedef подписи, например здесь   -  person Basile Starynkevitch    schedule 12.07.2017


Ответы (2)


Стандарт C написан так, чтобы предполагать, что указатели на разные типы объектов, и особенно указатели на функции, в отличие от типов объектов, могут иметь разные представления. Вот почему, как правило, вы не хотите смешивать указатели, иначе современный компилятор предупредит вас, и если вы хотите отключить предупреждения, вы обычно используете явное приведение.

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

Другими словами, код, использующий dlsym, по своей природе непереносим, ​​в том смысле, что он не является широко переносимым, в том смысле, что он переносится «только» на те машины, на которых все указатели безопасно взаимно конвертируемы. (Это, конечно, практически все популярные сегодня машины.)

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

(И если gcc -pedantic предупреждает вас даже о явном преобразовании void * в тип указателя функции, вы ничего не можете сделать, кроме как переключиться на другую версию gcc или, конечно, не использовать -pedantic.)


Дополнение: мой ответ звучал так, будто преобразование указателей на разные типы данных может быть проблемой, но в целом это не проблема. Тип void * четко определен как универсальный указатель данных: это то, что возвращает malloc, и он определен как легко конвертируемый в любой тип указателя на объект, то есть вам даже не нужно приведение. Так что это почти прекрасный выбор для типа возвращаемого значения dlsym, за исключением небольшой проблемы с указателями на функции. malloc никогда не сталкивался с этой проблемой (вы вряд ли когда-либо пытались бы изменить локализацию указателя на функцию), в то время как dlsym всегда имеет эту проблему (символы, к которым вы обычно пытаетесь получить доступ в динамически загружаемых объектных файлах, представляют собой код, по крайней мере, так часто, как они данные). Но указатели на функции - это то, во что void * не гарантируется преобразование, поэтому вы, скорее всего, получите предупреждения, поэтому вам нужны приведения, и вы можете получить предупреждения в -pedantic даже при приведении типов.

person Steve Summit    schedule 20.07.2015
comment
Компиляция с gcc 5.1.0 с -Wall -Werror -pedantic дает ошибку: dynamic_c.c:49:6: error: ISO C forbids conversion of object pointer to function pointer type [-Werror=pedantic] f = (foo)dlsym(handle, "foo");. clang 3.6.2 с такими же флагами нет. - person lord.garbage; 21.07.2015
comment
Использование *(void **) (&f) = dlsym(handle, "foo"); также отключит gcc. - person lord.garbage; 21.07.2015
comment
Я с Кейтом: это искажение с void ** выглядит плохой идеей. - person Steve Summit; 22.07.2015
comment
@brauner: Понятно и не обвиняю вас в этом, но это все равно плохая идея. - person Steve Summit; 22.07.2015
comment
Итак, если это так, есть ли более безопасная альтернатива dlsym? - person searchengine27; 06.12.2019
comment
@ searchchengine27 Я не знаю ни одного, нет. (Но я не считаю dlsym небезопасным.) - person Steve Summit; 06.12.2019

dlsym() возвращает значение void*. Это значение указателя может относиться либо к объекту, либо к функции.

Если он указывает на объект, то приведение не требуется, поскольку C определяет неявное преобразование из void* в любой тип указателя на объект:

int *ptr = dlsym(handle, "name");

Если он указывает на функцию (что, вероятно, гораздо более распространено), не существует неявного преобразования из void* в какой-либо тип указателя на функцию, поэтому необходимо преобразование.

В стандарте C нет гарантии, что значение void* может быть преобразовано в указатель на функцию. POSIX, который определяет dlsym(), неявно гарантирует, что значение void*, возвращаемое dlsym() может быть осмысленно преобразовано в тип указателя на функцию, если цель имеет правильный тип для соответствующей функции .

Предполагая, что мы имеем дело с функцией void без параметров:

typedef void (*func_ptr_t)(void);
func_ptr_t fptr = (func_ptr_t)dlsym(handle, "name");

Как это бывает, gcc (с -pedantic) предупреждает об этом:

warning: ISO C forbids conversion of object pointer to function pointer type

Это предупреждение не совсем правильное. ISO C на самом деле не запрещает преобразовывать указатель объекта в тип указателя функции. Стандарт перечисляет несколько ограничений, которые не могут быть нарушены оператором приведения; преобразование void* в тип указателя функции не входит в их число. Стандарт C не определяет поведение такого преобразования, но, поскольку POSIX делает это в данном конкретном случае, это не проблема.

Комментарий к ответу Стива Саммита предполагает, что это:

*(void **) (&f) = dlsym(handle, "foo");

отключит предупреждение gcc. Будет, но при этом сделаны предположения, которые не гарантируются ни C, ни POSIX. POSIX гарантирует, что результат dlsym может быть преобразован в указатель функции; это не гарантирует, что он имеет такое же представление или выравнивание. Скорее всего, это сработает, но я не рекомендую.

person Keith Thompson    schedule 21.07.2015
comment
Ха, странно, что на странице руководства *(void **) (&f) указан как приемлемый обходной путь POSIX, который соответствует стандарту ISO C и позволяет избежать предупреждений компилятора. Спасибо за дополнительную точность. - person lord.garbage; 21.07.2015
comment
@brauner: Интересно. Он цитирует Обоснование спецификации POSIX для dlsym (), но Спецификация POSIX для dlsym() содержит пустой раздел "Обоснование". Возможно, POSIX удалил этот обходной путь, и справочная страница не дошла до него. - person Keith Thompson; 21.07.2015
comment
Вероятно, его удалили из-за этого: Техническое исправление 2013 г. к POSIX.1-2008 (также известное как POSIX.1-2013) улучшило ситуацию, потребовав, чтобы соответствующие реализации поддерживали приведение 'void *' к указателю на функцию. Кроме того, POSIX спецификация dlsym() перечисляет явное приведение к функции указатель в одном из его примеров: /* find the address of the function my_function */ fptr = (int (*)(int))dlsym(handle, "my_function");. - person lord.garbage; 21.07.2015
comment
Сегодня я протестировал эту строку кода, func_ptr_t fptr = (func_ptr_t) dlsym (handle, name); и приведение указателя функции C ++ вызывает нулевой указатель. - person Frank; 12.04.2016
comment
До тех пор, вероятно, лучше всего отключить педантичные предупреждения для строк в вопросах, как это уже предлагалось здесь: stackoverflow.com/a / 36385690/1905491 - person stefanct; 28.06.2016