Чи є стандартний спосіб або стандартна альтернатива упаковці структури в с?


13

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

Чи не існує стандарту для пакування структур, який працює у всіх компіляторах C? Якщо ні, то я не відчуваю думки, що це критична особливість для системного програмування? Чи ранні користувачі мови С не виявили потреби в упаковці конструкцій або існує якась альтернатива?


використання структур у доменах компіляції - дуже погана ідея, зокрема вказувати на апаратне забезпечення (що є ще одним компілюючим доменом). Пакетні структури - це лише одна хитрість для цього, у них багато поганих побічних ефектів, тому існує багато інших рішень для ваших проблем із меншими побічними ефектами, які є більш портативними.
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-бітний блок, якщо хочете; Це дорожче оперативної пам'яті, але використовує менше інструкцій для доступу.

"упаковка" не означає "щільно упакувати" - це просто означає деяку схему впорядкування елементів структури wrt зміщення.

Немає загальної схеми, це компілятор + залежить від архітектури.


1
Якщо компілятор для TPU переставляє struct bпереміщатися elementCперед будь-яким з інших елементів, то він не є відповідним компілятором C. Перестановка елементів заборонена в C
Bart van Ingen Schenau

Цікаво, але U24 - це не стандартний тип типу 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 орієнтований на портативність, мало сенсу мати стандартний спосіб усунення всіх накладок у структурі та просто дозволяти довільні поля нерівні, оскільки це може потенційно ризикувати зробити код 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

... немає більше накладних накладних витрат і без залучення нерівного доступу до пам'яті, оскільки ми більше не отримуємо доступ до цих полів як зміщення адреси структури, а натомість як зміщення базової адреси для того, що фактично є масивом.

Це також приносить бонус за швидкість для послідовного доступу в результаті зменшення кількості даних (більше нерелевантних прокладок у суміші, щоб уповільнити відповідну швидкість споживання даних машини), а також потенціал для компілятора векторизувати обробку дуже тривіально .

Мінус полягає в тому, що це ПДТА для кодування. Це також потенційно менш ефективно для випадкового доступу з більшим кроком між полями, де часто повторення AoS або AoSoA будуть робити краще. Але це один стандартний спосіб усунути прокладки і упакувати речі максимально щільно, не закручуючи вирівнювання всього.


2
Я заперечую, що наявність засобів чіткого визначення структури структури значно підвищить мобільність. Хоча деякі макети призводять до дуже ефективного коду на деяких машинах і дуже неефективного коду на інших, код працює на всіх машинах і буде ефективним принаймні на деяких. На противагу цьому, за відсутності такої функції, єдиним способом змусити роботу коду на всіх машинах, ймовірно, буде або зробити його неефективним на всіх машинах, або ж використовувати купу макросів та умовну компіляцію для комбінування швидкої непереносимої портативної машини програма та повільний портативний у тому ж джерелі.
supercat

Концептуально так, якби ми могли вказати все до бітів і байтових представлень, вимог вирівнювання, витривалості і т. Д. І мають функцію, яка дозволяє таке явне управління в C, при необхідності, відокремлюючи його далі від основної архітектури ... Але я просто говорив про Банкомат - на даний момент найбільш портативне рішення серіалізатора полягає в тому, щоб записати його таким чином, щоб це не залежало від точних уявлень бітів і байтів та вирівнювання типів даних. На жаль, нам не вистачає засобів для банкоматів, щоб зробити це інакше ефективно (на С).

5

Не всі архітектури однакові, просто увімкніть 32-бітний параметр на одному модулі і подивіться, що відбувається при використанні одного і того ж вихідного коду та одного компілятора. Порядок байтів - ще одне добре відоме обмеження. Киньте представлення з плаваючою комою, і проблеми погіршаться. Використання пакування для надсилання двійкових даних не є портативним. Щоб стандартизувати його так, щоб він був практично придатним для використання, вам потрібно буде переглядати специфікацію мови C.

Хоча звичайне використання пакета для надсилання двійкових даних є поганою ідеєю, якщо ви хочете захистити дані, переносимість або довговічність даних. Як часто ви читаєте двійкову крапку з джерела у своїй програмі. Як часто ви перевіряєте, чи всі значення є здоровими, щоб хакер чи зміна програми не "потрапили" до даних? До того моменту, як ви зашифрували контрольну процедуру, ви також можете використовувати процедури імпорту та експорту.


0

Дуже поширеною альтернативою є "ім'я" padding ":

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

Це робить припускати , що структура не буде доповнена до 8 байт.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.