Мне нужно записывать в файл бинарные данные заданного формата, к примеру
вот такую структуру:
struct Frame
{
int param1;
double param2;
char param3;
};
Формат структуры задает формат файла.
Для непосредственно записи будет использоваться библиотека потоков iostream.
Понятно, что для записи данной структуры в файл нужно либо выводить поочередно все поля, либо выравнить структуру по границе одного байта, чтобы в файл попали байты только структуры.
Вопрос в следующем:
Как сделать запись (и чтение) в файл платформенно независимым?
Ведь если программу откомпилировать для 64-разрядного процессора, размер структуры в байтах будет другим, по сравнению с 32-разрядным процессором.
Нужно чтобы формат файла был один для разных платворм.
S>Формат структуры задает формат файла. S>Для непосредственно записи будет использоваться библиотека потоков iostream. S>Понятно, что для записи данной структуры в файл нужно либо выводить поочередно все поля, либо выравнить структуру по границе одного байта, чтобы в файл попали байты только структуры.
S>Вопрос в следующем:
S> S>Как сделать запись (и чтение) в файл платформенно независимым? S> S>Ведь если программу откомпилировать для 64-разрядного процессора, размер структуры в байтах будет другим, по сравнению с 32-разрядным процессором. S>Нужно чтобы формат файла был один для разных платворм.
S>Как сделать это наиболее красиво?
Первый и очевидный вариант — отказаться от бинарного формата в пользу, скажем, того же XML.
Например, с помощью такого подхода можно сохранять настройки 32-битной версии программы, а
потом вводить их в 64-битную, не получая при этом конфликтов.
Второй вариант — применять только типы фиксированного размера.
Обычно делается это с помощью typedef/#define и директив условной компиляции, так как далеко не
все типы стандартного C++ подходят на эту роль.
Здравствуйте, okman, Вы писали:
O>Первый и очевидный вариант — отказаться от бинарного формата в пользу, скажем, того же XML. O>Например, с помощью такого подхода можно сохранять настройки 32-битной версии программы, а O>потом вводить их в 64-битную, не получая при этом конфликтов.
O>Второй вариант — применять только типы фиксированного размера. O>Обычно делается это с помощью typedef/#define и директив условной компиляции, так как далеко не O>все типы стандартного C++ подходят на эту роль.
Формат xml сразу отпадает. Нужно записывать в файл измененные значения. Причем измерения происходят с частотой 200 сэмплов в секунду на канал. Каналов может быть несколько. Сложно будет потом парсить xml.
Я склоняюсь ко второму варианту.
Мне только непонятно, тип double будет иметь разный размер на разных платформах или он всегда 64-разрядный?
S>Формат структуры задает формат файла. S>Для непосредственно записи будет использоваться библиотека потоков iostream. S>Понятно, что для записи данной структуры в файл нужно либо выводить поочередно все поля, либо выравнить структуру по границе одного байта, чтобы в файл попали байты только структуры.
S>Вопрос в следующем:
S> S>Как сделать запись (и чтение) в файл платформенно независимым? S> S>Ведь если программу откомпилировать для 64-разрядного процессора, размер структуры в байтах будет другим, по сравнению с 32-разрядным процессором. S>Нужно чтобы формат файла был один для разных платворм.
S>Как сделать это наиболее красиво?
Если тебе нужно, чтобы формат файла не зависел от 32-64, то надо это просто учитывать.
Стандартные типы данных почти все от 32-64 не зависят.
Вещественные типы вообще определяются форматом сопроцессора (IEEE стандартом).
int в VC++ всегда 4-байтный (не знаю, как в GCC), short — 2, char — 1.
Вот указатели — да, но их обычно в файл не записывают.
Если VC++, то можно использовать типы __int32, __int16 и __int64, тогда размер задается явно.
Ну а если все же некий тип имеет разный размер в 32 и 64, то, понятно, его просто так использовать нельзя. Например, если long где-то 32, а где-то 64, то надо выводить или весь long (если он 32), или его младшую часть (если 64).
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Вещественные типы вообще определяются форматом сопроцессора (IEEE стандартом).
Что значит определяются форматом сопроцессора? double на 16, 32, 64 разрядных процессорах будет 64 битным?
PD>Если VC++, то можно использовать типы __int32, __int16 и __int64, тогда размер задается явно.
__int32, __int16 и __int64 — это расширения Майкрософт или стандартные С++ типы?
Здравствуйте, silart, Вы писали:
S>...
S>Я склоняюсь ко второму варианту. S>Мне только непонятно, тип double будет иметь разный размер на разных платформах или он всегда 64-разрядный?
Я рекомендую сделать файл data_types.h, в котором определить нужные Вам типы, в зависимости от
используемых компилятора и архитектуры. double может быть и 64 бита, и 80, даже видел когда
размер char был равен двум байтам. Для выбора переносимых типов нужно полагаться только на
расширения компиляторов (типа как __int8 вместо char на Visual C++), поскольку стандартные
типы C++ условиям фиксированного размера на практике часто не удовлетворяют.
И обязательно в такой файл вставить статические проверки на правильность выбранных типов.
<skip/> S> S>Как сделать запись (и чтение) в файл платформенно независимым? S> S>Ведь если программу откомпилировать для 64-разрядного процессора, размер структуры в байтах будет другим, по сравнению с 32-разрядным процессором. S>Нужно чтобы формат файла был один для разных платворм.
S>Как сделать это наиболее красиво?
1. Типы полностью должны совпадать.
Например вместо int стоит взять например int32_t.
Вместо char взять int8_t.
Определен в stdint.h (C99) или cstdint (C++0x), либо boost/cstdint.
double не определен в stdint, для него нужно указывать конкретный тип для конкретного компилятора.
Скажем
(Вместо typedef можно воспользоваться static_assert (C++0x) или Boost.StaticAssert )
Таким образом если размер будет неправильным или данные не в том месте, то код не собереться.
4.
Big-endian, Little-endian
Тут сложнее, если мы пишет little endian int и читаем big endian будут проблемы.
В этом случае нужно сериализовать данные, т.е. писать все в одном формате.
Например писать все всегда в little endian.
Скажите еще пожалуйста, как преобразовывать типы данных с различной длинной?
Как скажем преобразовать int к int16_t, если int на данной платформе 32-разрядный?
Все понятно, что произойдет потеря данных, но если следить, чтобы значение вписывалось в 16 разрядов 32-х разрядного int.
Как сделать безопасное преобразование типов в стиле С++?
S>2. Почему в cstdint не описан double? Может быть где-нибудь есть описание вещественных типов заданной разрядности? Также как int16_t например?
Я так полагаю, что эти типы могут очень сильно отличаться на разных платформах.
Скорее всего double будет выглядеть одинаково на всех компилятоах x86 .
Удостоверьтесь просто.
Здравствуйте, silart, Вы писали:
S>Скажите еще пожалуйста, как преобразовывать типы данных с различной длинной? S>Как скажем преобразовать int к int16_t, если int на данной платформе 32-разрядный? S>Все понятно, что произойдет потеря данных, но если следить, чтобы значение вписывалось в 16 разрядов 32-х разрядного int. S>Как сделать безопасное преобразование типов в стиле С++?
Здравствуйте, Pavel Dvorkin, Вы писали: PD>Здравствуйте, silart, Вы писали: PD>>>Вещественные типы вообще определяются форматом сопроцессора (IEEE стандартом). S>>Что значит определяются форматом сопроцессора? double на 16, 32, 64 разрядных процессорах будет 64 битным? PD>Да. IEEE-754.
Лишь уточню, стандарт IEEE-754 определяет, что размер double есть 8 байт. Но стандарт самого C++ не требует реализации вещественной арифметики именно в соответствии с IEEE-754. Так что на каком-нибудь DSP есть шансы встретится с другой формой представления double. Хотя, как мне кажется, на таких экзотических архитектурах размер double будет не самой большой вашей проблемой на пути к кроссплатформенности. Но, если есть большое желание, то для надёжности можно просто проверить значение numeric_limits<double>::is_iec559 — именно оно отвечает за использование IEEE-754 для данной конкретной платформы.
Нет. Более того, играть с выравниванием — это значит нарываться на неприятности на тех платформах, где чтение/запись по невыровненному адресу приводит к аварийному завершению программы или даже вылету всей операционной системы.
Для реализации переносимых бинарных форматов следует (1) использовать типы данных фиксированной разрядности (cstdint); (2) читать в буфер вида char buf[N] и затем копировать оттуда все данные через memcpy; (3) учитывать порядок байт, представление отрицательных чисел и чисел с плавающей точкой, а также битность байта.
Здравствуйте, Pavel Dvorkin, Вы писали: PD>Если тебе нужно, чтобы формат файла не зависел от 32-64, то надо это просто учитывать. PD>Стандартные типы данных почти все от 32-64 не зависят. PD>Вещественные типы вообще определяются форматом сопроцессора (IEEE стандартом). PD>int в VC++ всегда 4-байтный (не знаю, как в GCC), short — 2, char — 1. PD>Вот указатели — да, но их обычно в файл не записывают. PD>Если VC++, то можно использовать типы __int32, __int16 и __int64, тогда размер задается явно. PD>Ну а если все же некий тип имеет разный размер в 32 и 64, то, понятно, его просто так использовать нельзя. Например, если long где-то 32, а где-то 64, то надо выводить или весь long (если он 32), или его младшую часть (если 64).
Здравствуйте, gegMOPO4, Вы писали:
MOP>Здравствуйте, Pavel Dvorkin, Вы писали: PD>>Если тебе нужно, чтобы формат файла не зависел от 32-64, то надо это просто учитывать. PD>>Стандартные типы данных почти все от 32-64 не зависят. PD>>Вещественные типы вообще определяются форматом сопроцессора (IEEE стандартом). PD>>int в VC++ всегда 4-байтный (не знаю, как в GCC), short — 2, char — 1. PD>>Вот указатели — да, но их обычно в файл не записывают. PD>>Если VC++, то можно использовать типы __int32, __int16 и __int64, тогда размер задается явно. PD>>Ну а если все же некий тип имеет разный размер в 32 и 64, то, понятно, его просто так использовать нельзя. Например, если long где-то 32, а где-то 64, то надо выводить или весь long (если он 32), или его младшую часть (если 64).
MOP>Какие вредные советы!
А можно поконкретнее ? Вот, к примеру, в некоторой системе-32 long 32-битный, а в той же системе-64 64-битный. Использовать не-long почему-то нельзя (если такого требования нет, то см. мое сообщение выше). Предложи решение.
Здравствуйте, Pavel Dvorkin, Вы писали: PD>Здравствуйте, gegMOPO4, Вы писали: MOP>>Здравствуйте, Pavel Dvorkin, Вы писали: PD>>>Если тебе нужно, чтобы формат файла не зависел от 32-64, то надо это просто учитывать. PD>>>Стандартные типы данных почти все от 32-64 не зависят. PD>>>Вещественные типы вообще определяются форматом сопроцессора (IEEE стандартом). PD>>>int в VC++ всегда 4-байтный (не знаю, как в GCC), short — 2, char — 1. PD>>>Вот указатели — да, но их обычно в файл не записывают. PD>>>Если VC++, то можно использовать типы __int32, __int16 и __int64, тогда размер задается явно. PD>>>Ну а если все же некий тип имеет разный размер в 32 и 64, то, понятно, его просто так использовать нельзя. Например, если long где-то 32, а где-то 64, то надо выводить или весь long (если он 32), или его младшую часть (если 64). MOP>>Какие вредные советы! PD>А можно поконкретнее ? Вот, к примеру, в некоторой системе-32 long 32-битный, а в той же системе-64 64-битный. Использовать не-long почему-то нельзя (если такого требования нет, то см. мое сообщение выше). Предложи решение.
0. Прежде всего — следует рассмотреть вариант текстового формата. Не обязательно XML, JSON и т.п., часто достаточно DSV (delimiter separated values) — строка на запись, в строке значения разделены разделителем (обычно пробел или табуляция). Уйма проблем бинарных форматов сразу исчезает.
1. Размер стандартных типов в C/С++ не специфицирован (есть только некоторые граничные условия). Просто потому, что на разных платформах выгоднее иметь типы разного размера. Кстати, ознакомьтесь с limits.h, float.h и stdint.h.
2. The value representation of floating-point types is implementation-defined.
3. Переносимость с VC++ на VC++ — это анекдот (сравнимый с кроссплатформенностью .NET), к платформенной независимости отношения не имеет.
4. __int32, __int16 и __int64 — как раз платформозависимые типы, особенность одной конкретной реализации. Для переносимости есть stdint.h.
5. А о порядке байт и позабыли. Не говоря уж о представлении знаковых чисел и плавучке.
Итого — готовый рецепт как готовить непереносимые и небезопасные программы, с нерасширяемым форматом.
Здравствуйте, gegMOPO4, Вы писали:
MOP>0. Прежде всего — следует рассмотреть вариант текстового формата. Не обязательно XML, JSON и т.п., часто достаточно DSV (delimiter separated values) — строка на запись, в строке значения разделены разделителем (обычно пробел или табуляция). Уйма проблем бинарных форматов сразу исчезает.
Отменяется в соответствии с темой. Автор спрашивает про бинарные данные, и подтверждает это в одном из ответов.
MOP>1. Размер стандартных типов в C/С++ не специфицирован (есть только некоторые граничные условия). Просто потому, что на разных платформах выгоднее иметь типы разного размера. Кстати, ознакомьтесь с limits.h, float.h и stdint.h.
Давно знаком.
MOP>2. The value representation of floating-point types is implementation-defined.
IEEE 754. Я, конечно, не поручусь за всю Одессу, но в большинстве случаев это так. Если же на каком-то процессоре это не так, то решить задачу вывода в независимом формате если и возможно, то путем ручного перекодирования в один из форматов IEEE 754. Мало ли кто что придумает...
MOP>3. Переносимость с VC++ на VC++ — это анекдот (сравнимый с кроссплатформенностью .NET), к платформенной независимости отношения не имеет.
Переносимость 32<->64 — это то, что интересует автора, а если говорить о платформонезависимости вообще, то задача едва ли имеет решение хотя бы в силу размеров данных.
MOP>4. __int32, __int16 и __int64 — как раз платформозависимые типы, особенность одной конкретной реализации. Для переносимости есть stdint.h.
Но они не зависимы в VC++ от 32<->64, а о большем речи я и не веду
MOP>5. А о порядке байт и позабыли. Не говоря уж о представлении знаковых чисел и плавучке.
Если мы займемся вопросом порядка байт, то говорить о независимости бинарного файла вообще нельзя. Пожалуйста, сделай мне независимый файл с одним-единственным 4-байтным int, так чтобы в нем было одновременно little-endian и big-endian
MOP>Итого — готовый рецепт как готовить непереносимые и небезопасные программы, с нерасширяемым форматом.
Итого — либо к делу не относящиеся замечания, либо неверно.