Есть ли стандартный способ или стандартная альтернатива упаковке структуры в c?

13

Когда программирование в CI показало, что упаковывать структуры, используя __attribute__((__packed__))атрибут GCCs, неоценимо, я могу легко преобразовать структурированный кусок энергозависимой памяти в массив байтов, который будет передаваться по шине, сохраняться в хранилище или применяться к блоку регистров. Упакованные структуры гарантируют, что при обработке в виде массива байтов он не будет содержать никаких дополнений, что является расточительным, возможным риском для безопасности и, возможно, несовместимым при взаимодействии с аппаратным обеспечением.

Нет ли стандарта для структур упаковки, который работает во всех компиляторах Си? Если нет, то я выдаюсь, думая, что это критическая особенность для системного программирования? Разве ранние пользователи языка C не находили необходимости в упаковочных структурах или есть какая-то альтернатива?

satur9nine
источник
Использование структур в доменах компиляции - очень плохая идея, в частности, указывать на оборудование (которое является другим доменом компиляции). Структуры пакетов - это только одна хитрость для этого, у них много плохих побочных эффектов, поэтому есть много других решений ваших проблем с меньшим количеством побочных эффектов, и они более переносимы.
old_timer

Ответы:

12

В структуре важно смещение каждого члена от адреса каждого экземпляра структуры. Вопрос не в том, насколько плотно упакованы вещи.

Массив, однако, имеет значение в том, как он «упакован». Правило в C состоит в том, что каждый элемент массива составляет ровно N байтов от предыдущего, где N - это количество байтов, используемых для хранения этого типа.

Но в структуре нет необходимости в единообразии.

Вот один из примеров странной схемы упаковки:

Freescale (производящий автомобильные микроконтроллеры) создает микропроцессор с сопроцессором Time Processing Unit (Google для eTPU или TPU). Он имеет два собственных размера данных, 8 бит и 24 бита, и имеет дело только с целыми числами.

Эта структура:

struct a
{
  U24 elementA;
  U24 elementB;
};

будет видеть, что каждый U24 хранит свой собственный 32-битный блок, но только в области самого высокого адреса.

Эта:

struct b
{
  U24 elementA;
  U24 elementB;
  U8  elementC;
};

будет иметь два U24s хранится в смежных блоках 32 - битных и U8 будет сохранен в «дыре» в передней части первого U24, elementA.

Но вы можете сказать компилятору упаковать все в свой 32-битный блок, если хотите; это дороже в оперативной памяти, но использует меньше инструкций для доступа.

«упаковка» не означает «плотно упаковывать» - это просто означает некоторую схему размещения элементов структуры по смещению.

Универсальной схемы не существует, она зависит от компилятора + архитектуры.

RichColours
источник
1
Если компилятор для TPU переставляет struct bперемещение elementCперед любым другим элементом, то это не соответствующий компилятору Си. Перестановка элементов не допускается в C
Барт ван Инген Шенау
Интересно, что U24 не является стандартным типом C en.m.wikipedia.org/wiki/C_data_types, поэтому неудивительно, что компилятор вынужден обрабатывать его несколько странным образом.
satur9nine
Он разделяет оперативную память с основным ядром ЦП, размер слова которого составляет 32 бита. Но этот процессор имеет ALU, который имеет дело только с 24 битами или 8 битами. Таким образом, у него есть схема для размещения 24-битных чисел в 32-битных словах. Нестандартный, но отличный пример упаковки и выравнивания. Согласитесь, это очень нестандартно.
RichColours
6

При программировании в CI было признано неоценимым упаковать структуры с использованием GCC __attribute__((__packed__))[...]

Поскольку вы упоминаете __attribute__((__packed__)), я предполагаю, что вы намерены устранить все отступы внутри struct(сделать так, чтобы каждый элемент имел выравнивание в 1 байт).

Нет ли стандарта для структур упаковки, который работает во всех компиляторах Си?

... И ответ нет". Заполнение и выравнивание данных относительно структуры (и смежных массивов структур в стеке или куче) существуют по важной причине. На многих компьютерах доступ к памяти без выравнивания может привести к потенциально значительному снижению производительности (хотя на некоторых более новых аппаратных средствах его станет меньше). В некоторых редких случаях неправильное выравнивание доступа к памяти приводит к ошибке шины, которая не может быть исправлена ​​(может даже привести к сбою всей операционной системы).

Поскольку стандарт C ориентирован на переносимость, не имеет особого смысла иметь стандартный способ устранить все дополнения в структуре и просто разрешить произвольное выравнивание полей, поскольку это может привести к тому, что код C станет непереносимым.

Самый безопасный и самый переносимый способ вывода таких данных во внешний источник таким образом, чтобы исключить все заполнение, - это сериализовать в / из байтовых потоков, а не просто пытаться пересылать необработанное содержимое вашей памяти structs. Это также предотвратит снижение производительности вашей программы за пределами этого контекста сериализации, а также позволит вам свободно добавлять новые поля в structфайл, не выбрасывая и не сбивая все программное обеспечение. Это также даст вам возможность заняться порядком байтов и тому подобными вещами, если это когда-нибудь станет проблемой.

Существует один способ устранить все отступы, не обращаясь к директивам, специфичным для компилятора, хотя он применим только в том случае, если относительный порядок между полями не имеет значения. Учитывая что-то вроде этого:

struct Foo
{
    double x;  // assume 8-byte alignment
    char y;    // assume 1-byte alignment
               // 7 bytes of padding for first field
};

... нам нужен отступ для выравниваемого доступа к памяти относительно адреса структуры, содержащей эти поля, например:

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......

... где .указывает на отступы. Каждый xдолжен соответствовать 8-байтовой границе для производительности (и иногда даже правильного поведения).

Вы можете устранить заполнение переносимым способом, используя представление SoA (структура массива) следующим образом (предположим, нам нужно 8 Fooэкземпляров):

struct Foos
{
   double x[8];
   char y[8];
};

Мы фактически разрушили структуру. В этом случае представление памяти становится таким:

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______

... и это:

01234567
yyyyyyyy

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

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

Недостатком является то, что это код PITA. Это также потенциально менее эффективно для произвольного доступа с большим шагом между полями, где часто повторения AoS или AoSoA будут лучше. Но это один из стандартных способов устранить набивку и упаковать вещи настолько плотно, насколько это возможно, не прибегая к выравниванию всего.

ChrisF
источник
2
Я бы сказал, что наличие явного указания структуры структуры значительно повысит мобильность. Хотя некоторые макеты приведут к очень эффективному коду на одних машинах и очень неэффективному коду на других, код будет работать на всех машинах и будет эффективен по крайней мере на некоторых. В отличие от этого, в отсутствие такой возможности единственным способом заставить код работать на всех машинах, вероятно, будет либо сделать его неэффективным на всех машинах, либо использовать кучу макросов и условную компиляцию, чтобы объединить быстрый непереносимый код. программа и медленный портативный в том же источнике.
суперкат
С концептуальной точки зрения да, если бы мы могли указать все, вплоть до представления битов и байтов, требований выравнивания, порядка байтов и т. Д., И иметь функцию, которая позволяет осуществлять такой явный контроль в C, при этом дополнительно отделяя его от базовой архитектуры ... Но я просто говорил о ATM - в настоящее время наиболее переносимым решением для сериализатора является его запись таким образом, чтобы он не зависел от точных представлений битов и байтов и выравнивания типов данных. К сожалению, у нас нет средств для банкоматов, чтобы сделать иначе эффективно (в C).
5

Не все архитектуры одинаковы, просто включите 32-битную опцию на одном модуле и посмотрите, что происходит при использовании одного и того же исходного кода и одного и того же компилятора. Порядок байтов является еще одним хорошо известным ограничением. Бросьте представление с плавающей запятой, и проблемы усугубятся. Использование Packing для отправки двоичных данных непереносимо. Чтобы стандартизировать его так, чтобы он был практически применим, вам нужно будет переопределить спецификацию языка Си.

Хотя использование Pack для отправки двоичных данных является обычным явлением, это плохая идея, если вы хотите обеспечить безопасность, переносимость или долговечность данных. Как часто вы читаете двоичный двоичный объект из источника в вашу программу? Как часто вы проверяете, что все значения вменяемы, что хакер или изменение программы не «получили» данные? К тому времени, когда вы закодировали подпрограмму проверки, вы могли бы также использовать подпрограммы импорта и экспорта.

mattnz
источник
0

Очень распространенной альтернативой является «именованное заполнение»:

struct s {
  short s1;
  char  c2;
  char  reserved; // Padding
};

Это делает предполагать , что структура не будет дополнена до 8 байт.

MSalters
источник