Рейтинг@Mail.ru

Наши друзья и партнеры

UnixForum





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

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


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

2.1. Определения данных

Переменные в языках C и C++ могут быть определены различными способами. Обычно имеется три вида определений:

Общие переменные типа common. Общие переменные типа common чаще всего используются в языке FORTRAN, а в языках С и С++ их привыкли использовать в качестве способа обходить ошибки программистов. Поскольку программисты всегда старались удалять из определений переменных ключевое слово extern, его тем же самым образом можно было убирать из определения функции, у компилятора часто в разных файлах было несколько определений одной и той же переменной. Чтобы помочь бедным и невежественным программистам, в компиляторе C/C++ обычно для определений, не использующих инициализацию, следующим образом генерируются переменные типа common

int foo;

Для переменных типа common может быть более одного определения, и все они в выходном файле объединяются в одно определение. Переменные типа common всегда инициализируются нулевыми значениями. Это означает, что их значения не нужно хранить в файле ELF. Поэтому размер файла сегмента выбирается меньшим, чем размер памяти, рассмотренный в разделе 1.4.

Неинициализированные переменные. Если программист в командной строке компилятора использует параметр -fno-common, то в сгенерированном коде в случае, если в определении переменной нет инициализации, будут вместо переменных типа common использоваться неинициализированные переменные. Либо каждую переменную можно отдельности пометить следующим образом:

int foo   attribute ((nocommon));

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

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

Инициализированные переменные. Переменная определяется и инициализируется значением, которое задает программист. В языке C:

int foo = 42;

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

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

Есть одна вещь, за которую ответственен программист. В качестве примера взгляните на следующий код:

bool is_empty = true;
char s[10];

const char *get_s (void) {
   return is_empty ? NULL : s;
}

Функция get_s использует логическую переменную is_empty для того, чтобы решить, что делать. Если переменная имеет начальное значение, то переменная s не используется. Инициализационное значение is_empty хранится в файле, т. к. оно не равно нулю. Но семантика переменной is_empty выбирается произвольным образом. Но это не требуется. Вместо этого код можно переписать следующим образом:

bool not_empty = false;
char s[10];

const char *get_s (void) {
   return not_empty ? s : NULL;
}

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

С помощью простых преобразований, похожих на эти, часто можно избежать создания инициализированных переменных и вместо этого использовать переменные типа common или неинициализированные переменные. Это позволит сохранить дисковое пространство и, в конечном итоге, улучшит время запуска. Преобразования не ограничиваются логическими значениями. Иногда, то же самое удается сделать для переменных, у которых может более двух значений, особенно это касается значений перечисляемого типа. Когда определяются переменные перечисляемых типов, то первым в определении enum всегда должно идти значение, которое при инициализации используется чаще всего. То есть

enum { val1, val2, val3 };

должно быть переписано в виде

enum { val3, val1, val2 };

в случае, если val3 является значением, используемым при инициализации чаще. Если подводить итог, то всегда предпочтительнее добавить переменные, которые не инициализированы или инициализированы нулем вместо того, чтобы использовать для инициализации значение, отличное от нуля.


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

Поделиться: