Struct padding в C ++ - PullRequest
       24

Struct padding в C ++

47 голосов
/ 22 марта 2011

Если у меня есть struct в C ++, нет ли способа безопасно прочитать / записать его в файл, совместимый с кроссплатформенностью / компилятором?

Потому что, если я правильно понимаю, каждый компилятор 'колодки по-разному в зависимости от целевой платформы.

Ответы [ 5 ]

47 голосов
/ 23 марта 2011

Нет.Это невозможно.Это из-за отсутствия стандартизации C ++ на двоичном уровне .

Дон Бокс пишет (цитата из его книги Essential COM , глава COM как лучше C ++ )

C ++ и переносимость

После того как принято решение распространять класс C ++ в виде DLL, возникает один из фундаментальных недостатков C ++ , а именно отсутствие стандартизации на двоичном уровне .Хотя рабочий документ ISO / ANSI C ++ пытается систематизировать, какие программы будут компилироваться и каковы будут семантические эффекты их запуска, он не пытается стандартизировать бинарную модель времени выполнения C ++ .Впервые эта проблема станет очевидной, когда клиент попытается соединиться с библиотекой импорта DLL-библиотеки FastString из среды разработки C ++ , отличной от , используемой для создания библиотеки FastString.

Заполнение структуры выполняется разными компиляторами по-разному.Даже если вы используете один и тот же компилятор, выравнивание упаковки для структур может отличаться в зависимости от того, какой пакет прагмы вы используете.

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

Например, посмотрите это,

struct A
{
   char c;
   char d;
   int i;
};

struct B
{
   char c;
   int i;
   char d;
};

int main() {
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
}

Скомпилируйте его с помощью gcc-4.3.4, и вы получите такой вывод:

8
12

То есть размеры разные, хотя обе структуры имеютте же члены!

Код в Ideone: http://ideone.com/HGGVl

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

19 голосов
/ 22 марта 2011

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

Я опишу, как построить структуру, используя скаляры, но вы должны иметь возможность использовать вложенные структуры, если вы примените один и тот же дизайн для каждой включенной структуры.

Во-первых, основным фактом в C и C ++ является то, что выравнивание типа не может превышать размер типа. Если бы это было так, то было бы невозможно выделить память, используя malloc(N*sizeof(the_type)).

Макет структуры, начиная с самых больших типов.

 struct
 {
   uint64_t alpha;
   uint32_t beta;
   uint32_t gamma;
   uint8_t  delta;

Затем добавьте структуру вручную, чтобы в итоге вы нашли самый большой тип:

   uint8_t  pad8[3];    // Match uint32_t
   uint32_t pad32;      // Even number of uint32_t
 }

Следующим шагом является определение того, должна ли структура храниться в формате с прямым или младшим порядком байтов. Лучший способ - это «поменять» все элементы in situ перед записью или после чтения структуры, если формат хранения не соответствует порядку байтов системы хоста.

7 голосов
/ 22 марта 2011

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

Вам необходимо определить формат файла и преобразовать вашу структуру в этот формат и из него. Библиотеки сериализации (например, boost :: serialization или буфер протокола Google) могут помочь в этом.

3 голосов
/ 22 марта 2011

Короче говоря, нет. Не существует независимого от платформы, стандартно-совместимого способа работы с отступами.

Заполнение в Стандарте называется выравниванием, и оно начинает обсуждаться в 3.9 / 5:

Типы объектов имеют выравнивание требования (3.9.1, 3.9.2). выравнивание полного типа объекта целое число, определяемое реализацией значение, представляющее количество байтов; объект размещен по адресу что соответствует требованиям выравнивания типа объекта.

Но это продолжается и отходит ко многим темным углам Стандарта. Выравнивание является «определяемым реализацией», что означает, что оно может быть различным в разных компиляторах или даже в разных моделях адресов (т.е. 32-битных / 64-битных) в одинаковом компиляторе.

Если у вас нет действительно жестких требований к производительности, вы можете рассмотреть вопрос о сохранении ваших данных на диске в другом формате, например, в виде строк символов. Многие высокопроизводительные протоколы отправляют все, используя строки, когда естественный формат может быть чем-то другим. Например, канал обмена с малой задержкой, над которым я недавно работал, отправляет даты в виде строк в следующем формате: «20110321», а время отправляется аналогично: «141055.200». Несмотря на то, что этот фид обмена отправляет 5 миллионов сообщений в секунду в течение всего дня, они по-прежнему используют строки для всего, потому что таким образом они могут избежать бесконечности и других проблем.

2 голосов
/ 22 марта 2011

Вы можете использовать что-то вроде boost::serialization.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...