Struct padding в C++

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

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

5 ответов

Решение

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

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

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


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

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

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

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

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

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

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

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

Во-первых, основным фактом в 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
 }

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам