Выравнивание членов структуры массива символов в стандарте C

Предположим, я хотел бы прочитать/записать заголовок файла tar. Учитывая стандарт C (C89, C99 или C11), есть ли у массивов символов какая-либо особая обработка в структурах в отношении заполнения? Может ли компилятор добавить дополнение к такой структуре:

struct header {
    char name[100];
    char mode[8];
    char uid[8];
    char gid[8];
    char size[12];
    char mtime[12];
    char chksum[8];
    char typeflag;
    char linkname[100];
    char tail[255];
};

Я также видел его использование в коде в Интернете. Просто прочтите, запишите эту структуру в файл одним фрагментом, предполагая, что не будет никаких дополнений. Конечно, также предполагая CHAR_BITS == 8. Я думаю, что такой код C настолько распространен, что стандарт имел бы дело с этим случаем, но я просто не могу найти его в нем, может быть, я не был бы хорошим юристом.

РЕДАКТИРОВАТЬ

Принятый ответ дал бы строгую или максимально строгую переносимую реализацию в соответствии с одним из стандартов C, которая позволяет мне обрабатывать эти поля с помощью стандартных библиотечных строковых функций. Учитывая CHAR_BITS и все такое. Я думаю, что для этого нужно прочитать массив из 512 uint8_t, а после этого, возможно, преобразовать их в символы один за другим. Есть ли способ проще?


person Gábor Buella    schedule 24.03.2014    source источник
comment
Кто хочет быть юристом, когда вы можете быть программистом. ;)   -  person kay    schedule 24.03.2014
comment
Это очень распространено, и, как правило, это плохая идея, несмотря ни на что. То, что данные все char, это формальность. Единственный гарантированный безопасный способ сделать это — почленно, как на стороне чтения, так и на стороне записи. да, это боль, и да, вы, вероятно, можете обмануть, используя инструкции, специфичные для реализации, для упаковки вашей структуры (с обеих сторон).   -  person WhozCraig    schedule 24.03.2014
comment
Стандарт не гарантирует выравнивание (компилятор может добавить заполнение). Однако вам будет трудно найти компилятор, добавляющий заполнение к показанной структуре; нет очевидной причины добавлять какие-либо отступы. Если бы нужно было заполнение, то оно было бы после двух полей нечетной длины — typeflag и tail. Если бы машина была в основном ориентирована на слова (базовый доступ был 16-битными единицами на четных границах байтов — я не знаю ни одной такой машины в настоящее время), то компилятор мог бы повысить производительность за счет добавления заполнения этих полей. OTOH, даже для таких компов компилятора наверное нет.   -  person Jonathan Leffler    schedule 24.03.2014


Ответы (3)


C11 (последний свободно доступный черновик) говорит только «В объекте структуры может быть безымянное дополнение, но не в его начале» (§6.7.2.1, абз. 15) и «Может быть безымянное дополнение в конце структуры или объединения» (§6.7.2.1, абз. 17). . Это не дает никаких дополнительных ограничений на отступы внутри структуры.

Платформа ABI может иметь более строгие требования к заполнению, но в зависимости от этого будет зависеть от платформы, так как другие платформы могут иметь другие требования к заполнению. x86-64 ABI для Unix/Linux дает char выравнивание по 1 байту и указывает :

Структуры и союзы предполагают выравнивание их наиболее строго выровненного компонента. Каждому элементу назначается наименьшее доступное смещение с соответствующим выравниванием. Размер любого объекта всегда кратен выравниванию объекта.

Массив использует то же выравнивание, что и его элементы, за исключением того, что локальная или глобальная переменная массива длиной не менее 16 байт или переменная массива переменной длины C99 всегда имеет выравнивание не менее 16 байт4

Для объектов структуры и объединения может потребоваться дополнение, чтобы соответствовать ограничениям по размеру и выравниванию. Содержимое любого заполнения не определено.


4Требование к выравниванию позволяет использовать инструкции SSE при работе с массивом. В общем случае компилятор не может вычислить размер массива переменной длины (VLA), но ожидается, что большинству VLA потребуется как минимум 16 байт, поэтому логично предписать, чтобы VLA имели выравнивание по крайней мере по 16 байтам. .

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

Если вы хотите быть переносимым, читая структуру за один вызов, вы можете посмотреть readv. Это векторная операция ввода-вывода или разброс/сборка, которая позволяет указать массив массивов и длин для чтения. Например, для этого случая вы можете написать:

struct header h;
struct iovec iov[10];
iov[0].iov_base = &h.name;
iov[0].iov_len = sizeof(h.name);
iov[1].iov_base = &h.mode;
iov[1].iov_len = sizeof(h.mode);
/* ... etc ... */
bytes_read = readv(fd, iov, 10);

Обратите внимание, что readv определяется в спецификации POSIX/Single Unix, а не в стандарте C. В стандартном C проще всего просто прочитать каждый из этих элементов по отдельности (и даже при наличии векторного ввода-вывода простое чтение и запись каждого элемента по отдельности, вероятно, будет более понятным, если только вам абсолютно не нужно использовать один вызов для всю операцию ввода-вывода).

В своем редактировании вы пишете:

Принятый ответ дал бы строгую или максимально строгую переносимую реализацию в соответствии с одним из стандартов C, которая позволяет мне обрабатывать эти поля с помощью стандартных библиотечных строковых функций. Учитывая CHAR_BITS и все такое. Я думаю, что для этого нужно прочитать массив из 512 uint8_t, а после этого, возможно, преобразовать их в символы один за другим. Есть ли способ проще?

Спецификация C не гарантирует, что uint8_t доступен: «Имя typedef uintN_t обозначает беззнаковый целочисленный тип с шириной N и без битов заполнения... Эти типы являются необязательными». (проект C11, §7.20.1.1, §2–3). Однако, если доступны 8-битные значения, то char гарантированно будет 8-битным значением, так как оно гарантированно будет не менее 8-битным и гарантированно будет наименьшим объектом, не являющимся битовым полем (§5.2.4.2). .1 ¶1):

Приведенные ниже значения должны быть заменены постоянными выражениями, подходящими для использования в #if директивах предварительной обработки. Кроме того, за исключением CHAR_BIT и MB_LEN_MAX, следующие должны быть заменены выражениями того же типа, что и выражение, являющееся объектом соответствующего типа, преобразованным в соответствии с целочисленными акциями. Их определяемые реализацией значения должны быть равны или больше по величине (абсолютное значение) показанным с тем же знаком.

— number of bits for smallest object that is not a bit-field (byte)
CHAR_BIT                              8

Итак, если у вас нет доступных 8-битных байтов, вы не сможете напрямую прочитать эти поля и получить доступ к октетам из них как к отдельным элементам массива; вам придется вручную разделить отдельные байты, используя сдвиг битов и маскирование. Тем не менее, я не знаю современных архитектур, в которых не хватает 8-битных байтов (для вычислений общего назначения, где файловый ввод-вывод вообще вызывает беспокойство; некоторые DSP могут, но они, вероятно, не будут иметь стандартного файлового ввода-вывода C. ).

Если у вас есть 8-битные байты, то char гарантированно будет 8-битным, поэтому нет особой пользы, кроме ясности для использования uint8_t против char. Если вы действительно обеспокоены, я бы просто удостоверился, что где-то в процессе сборки вы проверили, что CHAR_BIT равно 8, и назвали бы это хорошим.

person Brian Campbell    schedule 24.03.2014
comment
Спасибо, я узнал много нового из этого. Тем более, что это нельзя сделать внутри языка C переносимым способом. - person Gábor Buella; 24.03.2014
comment
Спасибо за iov идею! - person Gábor Buella; 24.03.2014
comment
Один комментарий: я бы предпочел iov[0].iov_len = sizeof (h.name); вместо iov[0].iov_len = 100;. - person kay; 24.03.2014
comment
@Kay Конечно, это может быть немного чище. - person Brian Campbell; 24.03.2014

На самом деле заполнение, изменение имени и тому подобное регулируются не стандартом C, а конкретным ABI: http://en.wikipedia.org/wiki/Application_binary_interface.

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

person kay    schedule 24.03.2014

В проекте стандарта C99 и C11 говорится в разделе 6.7.2.1 Спецификаторы структуры и объединения в параграфе 13 (параграф 15 в C11):

[...] Внутри объекта структуры может быть безымянный отступ, но не в его начале.

и в параграфе 15 (параграф 17 в C11):

В конце структуры или объединения может быть безымянный отступ.

person Shafik Yaghmour    schedule 24.03.2014