Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Программирование и алгоритмические языки


Ulrich Drepper "Как писать разделяемые библиотеки"
Назад Оглавление Вперед

2.2.3. Определение посимвольной видимости

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

Поскольку в языке C не предусмотрены механизмы определения видимости функции или переменной, компилятор gcc еще раз воспользуется атрибутами

int last
  __attribute__ ((visibility ("hidden")));

int
__attribute__ ((visibility ("hidden")))
next (void) {
   return ++last;
}

int index (int scale) {
   return next () < scale;
}

Здесь переменная last и функция next определены как hidden (скрытые). Эти символы могут использоваться во всех объектных файлах, из которых собирается объект DSO, содержиащий это определение. То есть, хотя static ограничивает видимость символа файлом, в котором он был определен, атрибут hidden ограничивает видимость объектом DSO, в который попадает определение. В приведенном выше примере помечены определения (definitions). Из-за этого никаких проблем не возникает, но в любом случае необходимо пометить объявление (declaration). На самом деле более важно, чтобы надлежащим образом были помечены объявления (declarations), т.к. это, в основном, тот генерируемый код, содержащий ссылку, на которую влияют атрибуты.

Вместо того, чтобы добавлять атрибут видимости для каждого объявления или определения, можно для всех определений и объявлений, которые в данный момент видны компилятору, временно изменить видимость, используемую по умолчанию. Этот прием удобно использовать, в основном, в заголовочных файлах, поскольку изменения сводятся к минимуму, но этот прием также можно использовать для определений. Такая возможно была добавлена как функция компилятора в версию gcc 4.0 и реализована с использованием директив pragma {Примечание 9}.

Примечание 9: Квалификатор Pragma был добавлен в ISO C99, что позволяет использовать директивы pragma в макросах.
#pragma GCC visibility push(hidden)
int last;

int
next (void) {
   return ++last;
}
#pragma GCC visibility pop

int index (int scale) {
   return next () < scale;
}

Точно также, как и в примере с использованием атрибутов, last и next определены со скрытой видимостью hidden, тогда как как index определен с видимостью default (при условии, что эта та видимость default, которая в настоящее время используется по умолчанию). Т.к. рассматривается синтаксис pragma, можно с ожидаемым результатом воспользоваться вложенными директивами pragma.

В случае, если в командной строке используется параметр -fvisibility=hidden, отдельные символы можно помечать как экспортируемые с помощью таких же самых синтаксических конструкций, которые были приведены в настоящем разделе, но только использовать default вместо hidden. На самом деле, в атрибуте или в директиве pragma можно использовать все четыре названия вариантов видимости.

Кроме того, что компилятору сообщается выдавать код, в котором символ будет помечен флагом как скрытый (hidden), изменение видимости имеет еще одну цель: оно позволяет компилятору сделать предположение о том, что определение является локальным. Это означает, адресация переменных и функций может осуществляться так, как если бы определения были в файле локально определены как static. Поэтому могут генерироваться такие же самые последовательности кода, которые мы видели в предыдущем разделе. Следовательно использование атрибута видимости hidden почти полностью эквивалентно использованию static; разница лишь в том, что компилятор не может автоматически вставлять функции непосредственно в код (inline функции), поскольку он не сможет увидеть определение.

Теперь мы можем уточнить правила использования static: сливаем вместе исходные файлы и помечаем как static такое количество функций, пока это будет комфортно. В любом случае сливаем вместе файлы, содержащие функции, которые потенциально могут использоваться как встроенные (inline). Во всех остальных случаях помечаем функции (объявления), которые не будут экспортироваться из объекта DSO, как hidden.

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

В общем интерфейсе ELF ABI определет еще один режим видимости: protected или защищенный. В этой схеме ссылки на символы, определенные одном и том же объекте, всегда разрешаются локально. Но символы по-прежнему доступны за пределами объекта DSO. Это выглядит идеальным способом оптимизации объекта DSO за счет того, что избегается использование экспортируемых символов (смотрите раздел 2.2.7), но это не так. Обработка ссылок на защищенные символы даже более затратная, чем обычный поиск. Проблема связана с требованием стандарта ISO C. Стандарт требует, чтобы указатели функций, указывающие на одну функцию, могли при сравнении считаться равными. Это правило можно нарушить за счет быстрой и непродуманной реализации видимости вида protected. Предположим, что есть приложение, которое ссылается на защищенную функцию в объекте DSO. Также в объекте DSO есть еще одна функция, которая ссылается на функцию, о которой говорилось. Указатель в приложении указывает на запись PLT для этой функции, причем эта запись находится в таблице PLT приложения. Если поиск защищенного символа просто вернет адрес функции, находящейся внутри объекта DSO, то адреса будут различными.

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

Из этих правил есть несколько исключений. Можно создать двоичные модули ELF с нестандартными областями видимости. Простейшим примером является использование DF SYMBOLIC (или для двоичных модулей ELF старого стиля - DT SYMBOLIC, смотрите обсуждение выше). В таких случаях программист решает создать нестандартный двоичный модуль и, следовательно, принимает к сведению, что правила стандарта ISO C не применяются.


Предыдущий раздел:   Следующий раздел:
Назад Оглавление Вперед