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

UnixForum





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

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


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

2.4.3. Массивы указателей данных

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

static const char *msgs[ ] = {
	[ERR1] = "message for err1",
	[ERR2] = "message for err2",
	[ERR3] = "message for err3"
};

const char *errstr (int nr) {
   return msgs[nr];
}

Проблемной частью может быть переменная msgs, переменная msgs определяется здесь как переменная, которая помещается в неразделяемую память, доступную для записи. Она инициализируется указателем на три строки, которые располагаются в памяти, доступной только для чтения (здесь все замечательно). Даже если определение будет переписано как

static const char *const msgs[ ] = {

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

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

В простом случае, таком как пример, приведенный выше, решение возможно полностью в рамках языка C при помощи переписывания определения массива, например, следующим образом:

static const char msgs[ ] [17] = {
	[ERR1] = "message for err1",
	[ERR2] = "message for err2",
	[ERR3] = "message for err3"
};

В результате этого код становится оптимальным. Массив msgs полностью располагается в памяти, доступной только для чтения, поскольку в нем нет указателей. Код на языке C переписывать не нужно. Недостатком этого решения является то, что оно не всегда применимо. Если строки имеют различную длину, то это бы означало дополнительные затраты довольно большого количества памяти, поскольку второе измерение массива должно быть равно длине самой длинной строки плюс один. Затрат становится еще больше, если значения ERR0, ERR1 и ERR2 не располагаются последовательно и/или не начинаются с нуля. В данном случае каждая отсутствующая запись будет означать 17 неиспользованных байтов.

Есть другие способы, пригодные для случая, с которым не удалось справиться в примере, приведенном выше, но нет ни одного из них, для которого не требовалось серьезного переписывания кода {Примечание 10}. Одно из возможных решений задачи состоит в следующем. Этот код не столь элегантен, как исходный код, но его все еще можно поддерживать. В идеале должно быть средство, которое по описанию строк генерирует соответствующие структуры данных. Это можно сделать с помощью нескольких строк

static const char msgstr[ ] =
	"message for err1\0"
	"message for err2\0"
	"message for err3";

static const size_t msgidx[ ] = {
	0,
	sizeof ("message for err1"),
	sizeof ("message for err1")
	+ sizeof ("message for err2")
};

const char *errstr (int nr) {
	return msgstr + msgidx[nr];
}
Примечание 10: Если бы мы писали код на ассемблере, мы могли сохранить смещения относительно точки входа в объект DSO и, когда бы использовали элементы массива, то добавляли абсолютный адрес указателя ссылки. К сожалению, в языке C это невозможно.

Содержимое обоих массивов в этом коде является константой времени компиляции. Ссылки на msgstr и msgidx в errstr также не требуют перемещений, поскольку известно, что определения локальные. Затраты на строки в этом коде представляют собой три раза по t слов в памяти, доступной только для чтения. То есть, у нас пропали все перемещения (и, следовательно, затраты времени запуска приложения) и код перемещается из памяти, доступной для записи, в память, доступную только для чтения. Поэтому код, приведенный выше, является оптимальным.

В приложении B приведен более сложный и менее подверженный ошибкам вариант. В представленном коде программисту не нужно будет делать копии строк и их синхронизировать.


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