Хобрук: Ваш путь к мастерству в программировании

Равенство указателя функции в C

Мои вопросы:

  1. Гарантируется ли равенство указателей функций стандартом C?
  2. Если ответ на (1) да. Так ли это, независимо от того, получен ли указатель в разных конечных единицах компиляции (например, в основном исполняемом файле и в общей библиотеке)?
  3. Как с этим справляется динамический загрузчик? (Я могу подумать о нескольких причинах, по которым это может быть сложно, все они связаны с кодом PIC (например, таблицы GOT в elf и любой эквивалентный COFF, который использует для этого)). Независимо от (1) и (2) загрузчик linux гарантирует это.

Вот пример. Вышеприведенные вопросы сводятся к тому, гарантирует ли C то, что печатает main.c: "Function equality: 1" или "Function equality: 0", и, в первом случае, как динамический загрузчик делает это возможным.

common.h:

extern void * getc_main;
extern void * getc_shared;
void assign_getc_shared(); 

main.c:

#include <stdio.h>
#include "common.h"

int main()
{
  getc_main = (void*) getc;
  assign_getc_shared();
  printf("Function equality: %d\n", getc_main == getc_shared);
  return 0;
}

shared.c:

#include <stdio.h>
#include "common.h"

void assign_getc_shared()
{
   getc_shared = (void*) getc;
}

В Unix это будет скомпилировано с помощью следующих команд:

cc -shared -fPIC -o libshared.so shared.c
cc -o main main.c -L. -lshared

И выполняется с помощью:

LD_LIBRARY_PATH=. ./main

  • Это довольно длинный способ спросить, гарантируется ли, что стандартные библиотечные функции включаются в исполняемый файл только один раз? 20.02.2013
  • И я думаю, что ответ на вопрос г-на Листера — нет, это не гарантировано. Например, функции могут быть встроенными, и если вы возьмете адрес встроенной функции, она будет включена в код как реальная функция, а это означает, что потенциально может быть несколько функций для одной и той же исходной функции. 20.02.2013
  • @MrLister Если бы мне было интересно знать только это, я бы спросил только об этом. Причина, по которой я задаю дополнительные вопросы, заключается в том, что мне интересно узнать подробности о том, как динамический загрузчик решает эту проблему. Судя по вашему комментарию, это не так, и это нормально. 20.02.2013

Ответы:


1

C 2011 (Проект комитета N1570) 6.5.9 6: «Два указателя сравниваются равными тогда и только тогда, когда … оба являются указателями на одну и ту же … функцию …. Итак, да, два указателя на одну и ту же функцию сравниваются равными.

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

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

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

20.02.2013
  • Это означает, что невозможно реализовать компилятор C, совместимый с C 2011, в реальном режиме 80x86. Поскольку к любой точке памяти можно получить доступ через 4096 различных (дальних/огромных) указателей. 20.02.2013
  • @tristopia: я не понимаю, как следует ваш вывод. Тот факт, что каждый адрес имеет 4096 возможных представлений, не мешает компилятору гарантировать, что различные представления одного и того же адреса будут сравниваться равными. Компилятору не требуется реализовывать a == b с помощью одной инструкции; можно свободно выполнять арифметические действия для преобразования каждого из a и b из любого формата, в котором они находятся, в полный уникальный адрес, а затем сравнивать полученные полные адреса. 20.02.2013
  • Кроме того, компилятор контролирует получение адресов, поэтому он может гарантировать, что конкретное представление адреса функции используется, а другие нет. 20.02.2013
  • Да, в самом деле. Он может реализовать то, что мы делали вручную в то время с макросами FP_SEG, FP_OFF. 20.02.2013
  • @EricPostpischil Что касается перемещений (заполнители, как они упоминаются в вашем ответе), я вижу проблему с тем, какое значение заполнять для каждой единицы компиляции при использовании PIC, по крайней мере, в ELF (поэтому я спросил о динамическом загрузчике). Это связано с тем, что каждая единица компиляции имеет свои собственные слоты PLT и таблицу GOT. Это означает, что динамический загрузчик, чтобы обеспечить равенство указателей, должен заполнять одинаковые значения в таблицах GOT всех процессов. Но какое значение ставить? И как сделать так, чтобы вызов слота PLT другой единицы компиляции не вызывал проблем? 20.02.2013
  • @EricPostpischil Можете ли вы встроить функцию, для которой вы экспортируете ее символ? Я действительно так не думаю, иначе LD_PRELOAD не работал бы в unix. 20.02.2013
  • @fons: Вы хотели сказать «всех процессов»? Стандарт C гарантирует, что указатели на одну и ту же функцию сравниваются равными только в одном выполнении одной программы. Они не обязательно равны в разных процессах или даже в разных исполнениях одной и той же программы. 20.02.2013
  • @EricPostpischil Нет, нет, конечно нет :) Я имел в виду только один процесс. 20.02.2013
  • Конечно, вы (или компилятор) можете встроить функцию, для которой вы экспортируете символ. Будет как встроенная версия, так и обычная автономная версия, а экспортируемый символ будет символом обычной версии. 20.02.2013
  • @fons: я не знаком с деталями того, как Linux управляет динамической загрузкой. Однако я ожидаю, что когда динамический загрузчик загружает модуль, он тогда знает, где была размещена каждая функция в модуле, и сохраняет адрес функции везде, где на нее ссылаются, в том числе в GOT каждого модуля, который ссылается на функция. Таким образом, каждый GOT содержит один и тот же адрес для функции. 20.02.2013
  • давайте продолжим это обсуждение в чате 20.02.2013
  • Новые материалы

    Dall-E 2: недавние исследования показывают недостатки в искусстве, созданном искусственным интеллектом
    DALL-E 2 — это всеобщее внимание в индустрии искусственного интеллекта. Люди в списке ожидания пытаются заполучить продукт. Что это означает для развития креативной индустрии? О применении ИИ в..

    «Очень простой» эволюционный подход к обучению с подкреплением
    В прошлом семестре я посетил лекцию по обучению с подкреплением (RL) в моем университете. Честно говоря, я присоединился к нему официально, но я редко ходил на лекции, потому что в целом я нахожу..

    Освоение информационного поиска: создание интеллектуальных поисковых систем (глава 1)
    Глава 1. Поиск по ключевым словам: основы информационного поиска Справочная глава: «Оценка моделей поиска информации: подробное руководство по показателям производительности » Глава 1: «Поиск..

    Фишинг — Упаковано и зашифровано
    Будучи старшим ИТ-специалистом в небольшой фирме, я могу делать много разных вещей. Одна из этих вещей: специалист по кибербезопасности. Мне нравится это делать, потому что в настоящее время я..

    ВЫ РЕГРЕСС ЭТО?
    Чтобы понять, когда использовать регрессионный анализ, мы должны сначала понять, что именно он делает. Вот простой ответ, который появляется, когда вы используете Google: Регрессионный..

    Не зря же это называют интеллектом
    Стек — C#, Oracle Опыт — 4 года Работа — Разведывательный корпус Мне пора служить Может быть, я немного приукрашиваю себя, но там, где я живу, есть обязательная военная служба на 3..

    LeetCode Проблема 41. Первый пропущенный положительный результат
    LeetCode Проблема 41. Первый пропущенный положительный результат Учитывая несортированный массив целых чисел, найдите наименьшее пропущенное положительное целое число. Пример 1: Input:..