Запись в файл бинарных данных
От: silart  
Дата: 14.06.11 14:15
Оценка:
Добрый день!

Мне нужно записывать в файл бинарные данные заданного формата, к примеру
вот такую структуру:

struct Frame
{
int param1;
double param2;
char param3;
};


Формат структуры задает формат файла.
Для непосредственно записи будет использоваться библиотека потоков iostream.
Понятно, что для записи данной структуры в файл нужно либо выводить поочередно все поля, либо выравнить структуру по границе одного байта, чтобы в файл попали байты только структуры.

Вопрос в следующем:


Как сделать запись (и чтение) в файл платформенно независимым?

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

Как сделать это наиболее красиво?
Re: Запись в файл бинарных данных
От: okman Беларусь https://searchinform.ru/
Дата: 14.06.11 14:33
Оценка: -1
Здравствуйте, silart, Вы писали:

S>Добрый день!


S>Мне нужно записывать в файл бинарные данные заданного формата, к примеру

S>вот такую структуру:

S>
S>struct Frame
S>{
S>int param1;
S>double param2;
S>char param3;
S>};
S>


S>Формат структуры задает формат файла.

S>Для непосредственно записи будет использоваться библиотека потоков iostream.
S>Понятно, что для записи данной структуры в файл нужно либо выводить поочередно все поля, либо выравнить структуру по границе одного байта, чтобы в файл попали байты только структуры.

S>Вопрос в следующем:


S>

S>Как сделать запись (и чтение) в файл платформенно независимым?
S>

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

S>Как сделать это наиболее красиво?


Первый и очевидный вариант — отказаться от бинарного формата в пользу, скажем, того же XML.
Например, с помощью такого подхода можно сохранять настройки 32-битной версии программы, а
потом вводить их в 64-битную, не получая при этом конфликтов.

Второй вариант — применять только типы фиксированного размера.
Обычно делается это с помощью typedef/#define и директив условной компиляции, так как далеко не
все типы стандартного C++ подходят на эту роль.
Re[2]: Запись в файл бинарных данных
От: silart  
Дата: 14.06.11 14:41
Оценка:
Здравствуйте, okman, Вы писали:

O>Первый и очевидный вариант — отказаться от бинарного формата в пользу, скажем, того же XML.

O>Например, с помощью такого подхода можно сохранять настройки 32-битной версии программы, а
O>потом вводить их в 64-битную, не получая при этом конфликтов.

O>Второй вариант — применять только типы фиксированного размера.

O>Обычно делается это с помощью typedef/#define и директив условной компиляции, так как далеко не
O>все типы стандартного C++ подходят на эту роль.

Формат xml сразу отпадает. Нужно записывать в файл измененные значения. Причем измерения происходят с частотой 200 сэмплов в секунду на канал. Каналов может быть несколько. Сложно будет потом парсить xml.

Я склоняюсь ко второму варианту.
Мне только непонятно, тип double будет иметь разный размер на разных платформах или он всегда 64-разрядный?
Re: Запись в файл бинарных данных
От: Pavel Dvorkin Россия  
Дата: 14.06.11 14:46
Оценка: -1
Здравствуйте, silart, Вы писали:

S>Добрый день!


S>Мне нужно записывать в файл бинарные данные заданного формата, к примеру

S>вот такую структуру:

S>
S>struct Frame
S>{
S>int param1;
S>double param2;
S>char param3;
S>};
S>


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).
With best regards
Pavel Dvorkin
Re[2]: Запись в файл бинарных данных
От: silart  
Дата: 14.06.11 14:53
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Вещественные типы вообще определяются форматом сопроцессора (IEEE стандартом).


Что значит определяются форматом сопроцессора? double на 16, 32, 64 разрядных процессорах будет 64 битным?

PD>Если VC++, то можно использовать типы __int32, __int16 и __int64, тогда размер задается явно.


__int32, __int16 и __int64 — это расширения Майкрософт или стандартные С++ типы?
Re[3]: Запись в файл бинарных данных
От: okman Беларусь https://searchinform.ru/
Дата: 14.06.11 14:55
Оценка:
Здравствуйте, silart, Вы писали:

S>...


S>Я склоняюсь ко второму варианту.

S>Мне только непонятно, тип double будет иметь разный размер на разных платформах или он всегда 64-разрядный?

Я рекомендую сделать файл data_types.h, в котором определить нужные Вам типы, в зависимости от
используемых компилятора и архитектуры. double может быть и 64 бита, и 80, даже видел когда
размер char был равен двум байтам. Для выбора переносимых типов нужно полагаться только на
расширения компиляторов (типа как __int8 вместо char на Visual C++), поскольку стандартные
типы C++ условиям фиксированного размера на практике часто не удовлетворяют.
И обязательно в такой файл вставить статические проверки на правильность выбранных типов.
Re[3]: Запись в файл бинарных данных
От: Pavel Dvorkin Россия  
Дата: 14.06.11 15:01
Оценка:
Здравствуйте, silart, Вы писали:

S>__int32, __int16 и __int64 — это расширения Майкрософт или стандартные С++ типы?


Microsoft Specific

Если это напрягает, то можно так


#define MODE32
#ifdef MODE32
#define int8  тип-имеющий-размер-8-в-32-модели
#define int16 тип-имеющий-размер-16-в-32-модели
#define int32 тип-имеющий-размер-32-в-32-модели
#define int64 тип-имеющий-размер-64-в-32-модели
#else
#define int8  тип-имеющий-размер-8-в-64-модели
#define int16 тип-имеющий-размер-16-в-64-модели
#define int32 тип-имеющий-размер-32-в-64-модели
#define int64 тип-имеющий-размер-64-в-64-модели
#endif


Тут не придерешься.
With best regards
Pavel Dvorkin
Re: Запись в файл бинарных данных
От: Аноним  
Дата: 14.06.11 15:06
Оценка:
используйте типы, определенные в стандарте с99: uint32_t, int32_t, uint8_t и тд (заголовочный файл cstdint).
Re[3]: Запись в файл бинарных данных
От: Pavel Dvorkin Россия  
Дата: 14.06.11 15:06
Оценка:
Здравствуйте, silart, Вы писали:

PD>>Вещественные типы вообще определяются форматом сопроцессора (IEEE стандартом).


S>Что значит определяются форматом сопроцессора? double на 16, 32, 64 разрядных процессорах будет 64 битным?


Да. IEEE-754.
With best regards
Pavel Dvorkin
Re: Запись в файл бинарных данных
От: _nn_ www.nemerleweb.com
Дата: 14.06.11 15:14
Оценка: 18 (3)
Здравствуйте, silart, Вы писали:

<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, для него нужно указывать конкретный тип для конкретного компилятора.
Скажем
#if defined(_MSC_VER)
typedef double double_t;
#elif defined(__GCC__)
typedef double double_t;
#elif defined(SOME OTHER COMPILER)
typedef special double_t;
#else
#error Unknown compiler
#endif


2.
Выравнивание должно быть одинаковым.

Большинство компиляторов поддерживают #pragma pack.
#pragma pack(push)
#pragma pack(1)
...
#pragma pack(pop)


Либо же , если не поддерживают, то можно задать в опциях компилятора.

3.
Размер структуры должен совпадать.
С помощью 2-го пункта скорее всего будет совпадать.
От греха подальше стоит вставить статическую проверку:

// вместо 13 подставить нужный размер.
typedef FrameStructureSizeCheck[ sizeof(Frame) == 13 ? 1 : -1];


Можно еще добавить проверку выравнивания данных:
typedef FrameStructureParam1PlaceCheck[ offsetof(Frame, param1) == 0 ? 1 : -1];
typedef FrameStructureParam2PlaceCheck[ offsetof(Frame, param2) == 4 ? 1 : -1];
typedef FrameStructureParam3PlaceCheck[ offsetof(Frame, param3) == 12 ? 1 : -1];


(Вместо typedef можно воспользоваться static_assert (C++0x) или Boost.StaticAssert )

Таким образом если размер будет неправильным или данные не в том месте, то код не собереться.

4.
Big-endian, Little-endian

Тут сложнее, если мы пишет little endian int и читаем big endian будут проблемы.
В этом случае нужно сериализовать данные, т.е. писать все в одном формате.
Например писать все всегда в little endian.

Ну а при чтении десериализовать конечно.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[2]: Запись в файл бинарных данных
От: silart  
Дата: 14.06.11 15:43
Оценка:
Спасибо всем за дельные советы!

Подскажите пожалуйста,

1. Выражение

#pragma pack(push)
#pragma pack(1)
...
#pragma pack(pop)

является переносимым?

2. Почему в cstdint не описан double? Может быть где-нибудь есть описание вещественных типов заданной разрядности? Также как int16_t например?
Re[3]: Запись в файл бинарных данных
От: silart  
Дата: 14.06.11 15:58
Оценка:
Скажите еще пожалуйста, как преобразовывать типы данных с различной длинной?
Как скажем преобразовать int к int16_t, если int на данной платформе 32-разрядный?
Все понятно, что произойдет потеря данных, но если следить, чтобы значение вписывалось в 16 разрядов 32-х разрядного int.
Как сделать безопасное преобразование типов в стиле С++?
Re[3]: Запись в файл бинарных данных
От: _nn_ www.nemerleweb.com
Дата: 14.06.11 16:12
Оценка:
Здравствуйте, silart, Вы писали:

S>Спасибо всем за дельные советы!


S>Подскажите пожалуйста,


S>1. Выражение


S>
S>#pragma pack(push)
S>#pragma pack(1)
S>...
S>#pragma pack(pop)
S>

S>является переносимым?

Нет конечно
#pragma не переносима.
VC, GCC ее поддерживают

Тут надо какой-нибудь злобный макрос типа:

#if VC

#define USE_PRAGMA_PACK 1
#define PACK_BEFORE_STRUCT
#define PACK_AFTER_STRUCT

#elif GCC

#define USE_PRAGMA_PACK 0
#define PACK_BEFORE_STRUCT
#define PACK_AFTER_STRUCT(n) __attribute__(pack(n))

#elif OTHER COMPILER

...

#else

#error Unsupported compiler

#endif


#if USE_PRAGMA_PACK
#pragma pack(push, 1)
#endif

PACK_BEFORE_STRUCT(1) struct Frame
{
 ...
} PACK_AFTER_STRUCT(1);

#if USE_PRAGMA_PACK
#pragma pack(pop)
#endif



S>2. Почему в cstdint не описан double? Может быть где-нибудь есть описание вещественных типов заданной разрядности? Также как int16_t например?


Я так полагаю, что эти типы могут очень сильно отличаться на разных платформах.
Скорее всего double будет выглядеть одинаково на всех компилятоах x86 .
Удостоверьтесь просто.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: Запись в файл бинарных данных
От: _nn_ www.nemerleweb.com
Дата: 14.06.11 16:24
Оценка:
Здравствуйте, silart, Вы писали:

S>Скажите еще пожалуйста, как преобразовывать типы данных с различной длинной?

S>Как скажем преобразовать int к int16_t, если int на данной платформе 32-разрядный?
S>Все понятно, что произойдет потеря данных, но если следить, чтобы значение вписывалось в 16 разрядов 32-х разрядного int.
S>Как сделать безопасное преобразование типов в стиле С++?

Например так:
int16_t i16 = 1;

int32_t i32 = i16;

int32_t writeValue = convertToLittleEndian(i32);


соответственно convertToLittleEndian будет просто возвращать значение в Little-endian системах.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: Запись в файл бинарных данных
От: watch-maker  
Дата: 14.06.11 17:54
Оценка:
Здравствуйте, 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 для данной конкретной платформы.
Re[3]: Запись в файл бинарных данных
От: Centaur Россия  
Дата: 14.06.11 18:59
Оценка:
Здравствуйте, silart, Вы писали:

S>Выражение


S>#pragma pack(push)
S>#pragma pack(1)
S>...
S>#pragma pack(pop)

S>является переносимым?

Нет. Более того, играть с выравниванием — это значит нарываться на неприятности на тех платформах, где чтение/запись по невыровненному адресу приводит к аварийному завершению программы или даже вылету всей операционной системы.

Для реализации переносимых бинарных форматов следует (1) использовать типы данных фиксированной разрядности (cstdint); (2) читать в буфер вида char buf[N] и затем копировать оттуда все данные через memcpy; (3) учитывать порядок байт, представление отрицательных чисел и чисел с плавающей точкой, а также битность байта.
Re[2]: Запись в файл бинарных данных
От: gegMOPO4  
Дата: 15.06.11 08:24
Оценка:
Здравствуйте, 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).

Какие вредные советы!
Re[3]: Запись в файл бинарных данных
От: Pavel Dvorkin Россия  
Дата: 15.06.11 08:53
Оценка:
Здравствуйте, 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 почему-то нельзя (если такого требования нет, то см. мое сообщение выше). Предложи решение.
With best regards
Pavel Dvorkin
Re[4]: Запись в файл бинарных данных
От: gegMOPO4  
Дата: 16.06.11 15:53
Оценка: :)
Здравствуйте, 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. А о порядке байт и позабыли. Не говоря уж о представлении знаковых чисел и плавучке.

Итого — готовый рецепт как готовить непереносимые и небезопасные программы, с нерасширяемым форматом.
Re[5]: Запись в файл бинарных данных
От: Pavel Dvorkin Россия  
Дата: 16.06.11 16:55
Оценка: 1 (1)
Здравствуйте, 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>Итого — готовый рецепт как готовить непереносимые и небезопасные программы, с нерасширяемым форматом.


Итого — либо к делу не относящиеся замечания, либо неверно.
With best regards
Pavel Dvorkin
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.