Если у меня есть struct
в C++, нет ли способа безопасно читать/записывать ее в файл, совместимый с кросс-платформенным/компилятором?
Потому что, если я правильно понимаю, каждый кубик компилятора по-разному основан на целевой платформе.
Если у меня есть struct
в C++, нет ли способа безопасно читать/записывать ее в файл, совместимый с кросс-платформенным/компилятором?
Потому что, если я правильно понимаю, каждый кубик компилятора по-разному основан на целевой платформе.
Нет. Это невозможно. Это из-за отсутствия стандартизации C++ на двоичном уровне.
Дон Бокс пишет (цитируя его книгу Essential COM, глава COM As A Better 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
Суть в том, что в стандарте не говорится о том, как должно выполняться дополнение, и поэтому компиляторы могут принимать какие-либо решения, и вы не можете предположить, что все компиляторы принимают такое же решение.
Если у вас есть возможность самостоятельно структурировать структуру, это должно быть возможно. Основная идея заключается в том, что вы должны проектировать ее так, чтобы в нее не было необходимости вставлять байты. второй трюк заключается в том, что вы должны обрабатывать различия в endianess.
Я опишу, как построить структуру с помощью скаляров, но вы должны иметь возможность использовать вложенные структуры, если вы будете применять один и тот же дизайн для каждой включенной структуры.
Во-первых, основной факт в 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 protocolbuffers) могут помочь с этим.
Короче говоря, нет. Не существует независимого от платформы, стандартно-совместимого способа борьбы с заполнением.
Заполнение называется "выравниванием" в стандарте, и оно начинает обсуждать его в 3.9/5:
Типы объектов имеют выравнивание требования (3.9.1, 3.9.2). выравнивание полного типа объекта целое число, определенное реализацией значение, представляющее количество байтов; объект выделяется по адресу который соответствует требованиям к выравниванию его типа объекта.
Но это продолжается оттуда и отходит ко многим темным углам Стандарта. Выравнивание является "реализацией", что означает, что он может быть различным для разных компиляторов или даже через модели адресов (например, 32-разрядные/64-разрядные) под одним и тем же компилятором.
Если у вас действительно жесткие требования к производительности, вы можете рассмотреть возможность хранения ваших данных на диске в другом формате, например, char. Многие высокопроизводительные протоколы отправляют все, используя строки, когда естественный формат может быть чем-то другим. Например, канал обмена с низкой задержкой, который я недавно работал, отправляет даты в виде строк, отформатированных следующим образом: "20110321", и время отправляется аналогичным образом: "141055.200". Несмотря на то, что этот обменный канал отправляет 5 миллионов сообщений в секунду в течение всего дня, они все еще используют строки для всего, потому что таким образом они могут избежать эндиенции и других проблем.
Вы можете использовать что-то вроде boost::serialization
.