Прежде чем мы перейдем к плюсам и минусам, давайте рассмотрим примеры из реальной жизни.
Допустим, мы хотим реализовать хеш-таблицу, где каждая запись представляет собой динамически управляемый массив element
s:
struct hash_entry {
size_t allocated;
size_t used;
element array[];
};
struct hash_table {
size_t size;
struct hash_entry **entry;
};
#define HASH_TABLE_INITIALIZER { 0, NULL }
На самом деле используется и . Сама хеш-таблица представляет собой структуру с двумя членами. Элемент size
указывает размер хеш-таблицы, а элемент entry
является указателем на массив указателей на записи хеш-таблицы. Таким образом, каждая неиспользуемая запись является просто указателем NULL
. При добавлении элементов в запись хеш-таблицы весь struct entry
может быть перераспределен (для sizeof (struct entry) + allocates * sizeof (element)
или освобожден, если соответствующий указатель в элементе entry
в struct hash_table
обновлен соответствующим образом.
Если бы вместо этого мы использовали element *array
, нам нужно было бы использовать struct hash_entry *entry:
в struct hash_table
; или выделить struct hash_entry
отдельно от массива; или выделите как struct hash_entry
, так и массив в одном чанке, указатель array
будет указывать сразу после того же struct hash_entry
.
Стоимость этого будет составлять два дополнительных size_t
s памяти, используемых для каждого неиспользуемого слота хэш-таблицы, а также дополнительная разыменование указателя при доступе к element
s. (Или, чтобы получить адрес массива, две последовательные разыменования указателя вместо одной разыменования указателя плюс смещение.) Если это ключевая структура, широко используемая в реализации, эти затраты могут быть видны в профилировании и отрицательно влиять на производительность кэша. , Для случайного доступа, чем больше элемент array
, тем не менее разница меньше ; стоимость является наибольшей, когда array
s малы и соответствуют той же самой кешлайн (или нескольким кешлайнам), что и члены allocated
и used
.
Обычно мы не хотим делать элемент entry
в struct hash_table
элементом гибкого массива, поскольку это означает, что вы больше не можете объявлять хеш-таблицу статически, используя struct hash_table my_table = HASH_TABLE_INITIALIZER;
; вам нужно будет использовать указатель на таблицу и функцию инициализатора: struct hash_table *my_table; my_table = hash_table_init();
или подобное.
У меня есть другой пример связанных структур данных, использующих как элементы указателя, так и элементы гибкого массива. Это позволяет использовать переменные типа matrix
для представления любой двумерной матрицы с double
записями, даже когда матрица является представлением другого (скажем, транспонирования, блока, вектора строки или столбца или даже диагонали). вектор); все эти представления одинаковы (в отличие, например, от научной библиотеки GNU, где матричные представления представлены отдельным типом данных). Такой подход к матричному представлению облегчает написание надежного кода числовой линейной алгебры, а последующий код гораздо удобнее для чтения, чем при использовании GSL или BLAS + LAPACK. По-моему, это так.
Итак, давайте посмотрим на плюсы и минусы, с точки зрения того, как выбрать, какой подход использовать. (По этой причине я не буду обозначать какую-либо функцию как «за» или «против», так как определение зависит от контекста, в каждом конкретном случае использования.)
Структуры с гибкими элементами массива не могут быть инициализированы статически. Вы можете ссылаться на них только через указатели.
Вы можете объявлять и инициализировать структуры с указателями. Как показано в примере выше, использование макроса инициализатора препроцессора может означать, что вам не нужна функция инициализатора. Например, функция, принимающая параметр struct hash_table *table
, всегда может изменить размер массива указателей, используя realloc(table->entry, newsize * sizeof table->entry[0])
, даже если table->entry
равно NULL. Это уменьшает количество необходимых функций и упрощает их реализацию.
Для доступа к массиву через член-указатель может потребоваться дополнительная разыменование указателя.
Если мы сравним доступы к массивам в статически инициализированных структурах с указателем на массив, со структурой с гибким элементом массива, на который ссылается статический указатель, выполняется то же количество разыменований.
Если у нас есть функция, которая получает адрес структуры в качестве параметра, то для доступа к элементу массива через член-указатель требуется две разыменования указателя, тогда как для доступа к элементу гибкого массива требуется только одна разыменование указателя и одно смещение. Если элементы массива достаточно малы, а индекс массива достаточно мал, так что доступ к элементу массива находится в одной и той же строке кэша, доступ к элементу гибкого массива часто оказывается значительно быстрее. Для больших массивов разница в производительности, как правило, незначительна. Однако это зависит от аппаратной архитектуры.
Перераспределение массива через член-указатель скрывает сложность от тех, кто использует структуру в качестве непрозрачной переменной.
Это означает, что если у нас есть функция, которая получает указатель на структуру в качестве параметра, и эта структура имеет указатель на динамически распределенный массив, функция может перераспределить этот массив без того, чтобы вызывающая сторона видела какие-либо изменения в адресе структуры сама (только структура содержимое изменить).
Однако, если у нас есть функция, которая получает указатель на структуру с гибким членом массива, перераспределение массива означает перераспределение всей структуры. Это потенциально изменяет адрес структуры. Поскольку указатель передается по значению, модификация не видна вызывающей стороне. Таким образом, функция, которая может изменить размер элемента гибкого массива, должна получить указатель на указатель на структуру с элементом гибкого массива.
Если функция проверяет только содержимое структуры с гибким элементом массива, скажем, подсчитывает количество элементов, которые удовлетворяют некоторым критериям, тогда достаточно указателя на структуру; и указатель, и указываемые данные могут быть помечены const
. Это может помочь компилятору создавать лучший код. Кроме того, все данные, к которым осуществляется доступ, являются линейными в памяти, что помогает более сложным процессорам более эффективно управлять кэшированием. (Чтобы сделать то же самое с массивом, имеющим член-указатель, нужно было бы передать указатель на массив, а также поле размера по крайней мере в качестве параметров функции подсчета вместо указателя на структуру, содержащую эти значения .)
Неиспользуемая / пустая структура с гибким элементом массива может быть представлена указателем NULL (на такую структуру). Это может быть важно, если у вас есть массив массивов.
В структурах с гибкими элементами массива внешний массив является просто массивом указателей. Со структурами с членами-указателями внешний массив может быть либо массивом структур, либо массивом указателей на структуры.
Оба могут поддерживать разные типы подмассивов, если в качестве первого члена структуры имеют общий тег типа, и вы используете объединение этих структур. (Что «использование» означает в этом контексте, к сожалению, спорно Некоторые утверждают, что вы должны получить доступ к массиву через объединение, я утверждаю видимость такого союза достаточно, потому что все остальное сломается огромное количество существующего кода POSIX C. в основном весь код C на стороне сервера с использованием сокетов.)
Это главные из тех, о которых я могу думать прямо сейчас. Обе формы вездесущи в моем собственном коде, и у меня не было проблем ни с одной из них. (В частности, я предпочитаю использовать вспомогательную функцию без структуры, которая отравляет структуру, чтобы помочь обнаружить ошибки, возникающие при использовании после освобождения, в раннем тестировании; и мои программы не часто имеют проблемы с памятью.)
Я отредактирую приведенный выше список, если обнаружу, что пропустил важные аспекты. Поэтому, если у вас есть предложение или вы думаете, что я что-то упустил из виду, сообщите мне об этом в комментарии, чтобы я мог проверить и отредактировать его соответствующим образом.