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

UnixForum





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

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


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

2.2.1. Используем static

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

Если в нашем примере вне данного файла не нужны ни last , ни next, то мы можем изменить исходный код следующим образом:

static int last;

static int next (void) {
   return ++last;
}

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

Если его компилировать точно также, как и прежде, то мы увидим, что все перемещения, созданные в нашем примере кода, исчезнут. То есть, у нас осталось шесть перемещений и три записи PLT. Код для доступа к last теперь выглядит следующим образом:

movl   last@GOTOFF(%ebx), %eax
incl    %eax
movl   %eax, last@GOTOFF(%ebx)

Код был улучшен зак счет того, что был убран шаг загрузки адреса переменной из таблицы GOT. Вместо этого, в обоих случаях доступ осуществляется в памяти по прямому адресу переменной. Во время компоновки положение переменной будет фиксироваться по смещению от регистра PIC, что символически указывается как last@GOTOFF. Добавив это значение к значению регистра PIC, мы получим адрес переменной last. Поскольку это значение известно на этапе компоновки, то данная конструкция потребует перемещения во время выполнения.

Аналогичная ситуация и для вызова next. В архитектуре IA-32, как и во многих других, известны режимы адресации для переходов и вызовов, осуществляемых относительно регистра PC. Поэтому компилятор может сгенерировать простую инструкцию перерехода

call next

а ассемблер сгенерирует вызов, относительный регистра PC. Разница между адресом инструкции, следующей за вызовом, и адресом next является константой времени компоновки и, следовательно, перемещение не нужно. Другим преимуществом является то, что в случае архитектуры IA-32, в регистр PIC не нужно перед тем, как делать переход, записывать значение. Если компилятор не знал бы, куда следует в том же самом объекте DSO переходить, то тогда надо было бы записывать значение в регистр PIC. В других вариантах архитектуры требования аналогичные.

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


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