2015-04-12 2 views
2

Я пытаюсь написать структуру в двоичный файл. Я хочу, чтобы мой код был кросс-платформенным, поэтому я не уверен, что просто написал всю структуру с помощью fwrite. Если бы я сделал это, тогда размер структуры будет меняться в зависимости от размера примитивных типов для каждой платформы (в платформе A, int не будет иметь тот же размер, что и в платформе B. Таким образом, структура не будет тот же размер, и файл будет отличаться).Как написать структуру в файл в C?

Но я ничего не знаю об этом, так я должен написать каждый член структуры индивидуально, сериализовать-структуру(как я могу это сделать?), или просто написать структура ничуть FWRITE? Помните, что файл, написанный должны быть совместимы между платформами

Заранее спасибо

EDIT: Моя структура что-то вроде

typedef struct { 
    int health; 
    float x, y; 
    char ID[]; 
} Player; 
+1

«Совместимость между платформами» должна иметь _some_ широту. Формат может ограничивать весь текст до UTF8, с плавающей точкой для двоичных и целых чисел IEEE 754, например, для дополнения сети endian 2. – chux

+0

Сериализация может быть простой или сложной, в зависимости от ваших данных. Как выглядит ваша структура? –

+0

Я добавил код структуры. Я имею в виду, что файл, написанный в окнах, следует читать в linux. – twkmz

ответ

2

Поскольку вы заявили, что строите игровой прототип, и в основном хотите работать с процессорами x86 и x86_64, двоичная сериализация несколько проще.

Но во-первых, есть некоторые вещи, чтобы иметь в виду:

  • long значения имеют разный размер в x86 и x86_64.
  • Значения long также имеют различные требования к выравниванию.
  • double значения могут потребовать 8 или 4 байт, в зависимости от ОС и архитектуры.
  • Endianness обычно является проблемой, но более новые продукты Apple не используют PowerPC, поэтому вы можете в значительной степени гарантировать, что вы работаете на малоэтажных машинах.

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

Отбор другого размера переменной проблемы:

stdint.h файл содержит определения типов для int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t. Каждому из них гарантировано будет такое количество бит. Вы можете безопасно использовать int64_t вместо long среди платформ. Если по какой-либо причине есть платформа, у которой нет этих typedefs, вы можете определить их самостоятельно с помощью typedefs, так как вы можете получить информацию о целевой системе, используя макросы препроцессора компилятора в любом случае.

Отбор другая проблема выравнивания:

Это один волосатые. Самое простое решение состоит в том, чтобы иметь структуру serializable_player, содержащую все поля, которые содержит player struct, но сообщите компилятору о ее упаковке (атрибут для gcc), чтобы компилятор не помещал никаких отступов. Затем при записи в файл вы создаете serializable_player от player и пишите прямо.

Преобразование из/в сериализуемый тип может быть накладным. Если вы можете позволить себе потратить немного памяти, вы также можете обеспечить выравнивание каждого члена структуры. Выравнивание значений double с 8 байтами может быть хорошей идеей здесь. В gcc вы делаете это с атрибутом aligned.

Примечание: Упакованные структуры обычно имеют штраф за исполнение, и поскольку вы собираетесь использовать player в общей сложности, не не забудьте сделать это упакованным.

Решение проблем, различных чисел с плавающей точкой представления:

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

Раньше я работал в компании по разработке игр и отправлял плавающие точки по сети, вместо этого мы представляли их как целые числа. Что мы сделали, так это: вычислить минимальное разрешение, которое мы хотим r. Затем вычислите минимальное и максимальное значение, которое оно может быть (в зависимости от того, в какие игроки играли игроки), min и max. В этом представлении мы могли бы представлять (max - min)/r разные номера, и нам понадобилось log2((max - min)/r) бит для его сохранения. Поскольку приемник также знал о min, max, r, нам не нужно было включать эту информацию в сетевой пакет.Мы сказали, что min представлен как все 0s, max представлен как все 1s, а остальные значения находятся между ними. Это была игра в реальном времени, и карты были не слишком большими (многопользовательская поддержка 64 игроков), у нас не было проблем даже с 32 бит, игроки не дрожали/мерцали, производительность была хорошей.

Если вы используете аналогичный подход, вам не придется сериализовать/десериализовать плавающие точки в двоичном формате.

Наконец, если вы нацелитесь на архитектуру большого конца в будущем, потребуются только небольшие шаги (если вы не используете союз). Вам нужно только иметь функции convert_to_le_* для каждого размера. Эти функции должны быть пустыми для маленьких конечных машин, побитовая арифметика для больших конечных машин. Перед каждой сериализацией и после каждой десериализации вы должны вызывать эти функции для каждого члена, который будет отображаться по-разному в машинах большого конца. Приоритетные малоэтажные машины могут быть лучше здесь, так как у вашей основной аудитории, вероятно, будут машины x86 и x86_64.

Если вы используете объединение, ваше структурное представление также должно различаться между различными архитектурами.

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

Редактировать: Как указано в комментариях, использование JSON представляется лучше, если вы хотите изменить свою файловую структуру, добавить дополнительные поля и т. Д. Если есть шанс сделать это, и вы по-прежнему решаете использовать структуру двоичных файлов, файлы должны содержать магическое число в первых байтах, представляющих версию. Когда вы решите добавить/удалить поле, вам следует обновить версию. Когда вы читаете из файла сохранения, вы должны сначала проверить версию и соответствующим образом обрабатывать каждую версию.

Редактировать 2: Некоторые части этого ответа относятся к процессорам x86 и x86_64. Например, использование long вместо int64_t имеет смысл, так как в обеих архитектурах 8 байтов. Если нужно поддерживать более широкую область, я бы рекомендовал только int * _t typedefs. Примером здесь является ядро ​​linux, в котором s32 используется для подписанного 32-битного целого числа, если требуется принудительное исполнение размера.

+0

Что такое выравнивание данных и отступы? хотя это кажется несколько пугающим, я думаю, что получаю большую часть того, что вы сказали. Я должен позаботиться о размерах типов, точности и точности с плавающей запятой. Но, если у меня просто есть float, например, «1.2», это может вызвать проблемы при его хранении, даже если это так мало? – twkmz

+0

@ ochi12 [this] (http://www.catb.org/esr/structure-packing/) статья объясняет выравнивание и отладку данных. И да, любое значение, включая «1,2», по-разному представлено различными представлениями с плавающей запятой. – holgac

+0

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

1

Вы можете выбрать легкий путь или трудный путь.

Жесткий путь: попробуйте написать структуру в двоичном формате; и бороться с проблемами сущности и целочисленного размера. Это сложная проблема, и полные библиотеки были разработаны, пытаясь ответить на нее (protobuf-c, tpl и Apache Avro - извините, ограничен 2 ссылками - это первые 3 примера, на которые я упал).

Теперь простой способ.

Запись в текстовый файл.

(ASCII) Текстовый формат - это почти единственное, что вы можете использовать для полной переносимости: это стандартный способ написания текста; и мы, люди, более или менее имели компромисс о том, как писать числа.

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

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

Надеясь, что помогает,

Ekleog

+0

да, я не знаю о текстовых файлах. Я делаю игру, поэтому мне не нравится, когда игрок может изменить файлы с сохранением (именно по этой причине я пытаюсь написать структуры). Но если я нахожу бинарный ввод-вывод чрезвычайно трудным, тогда я могу просто написать все в текстовый файл – twkmz

+1

@ ochi12. Определенные игроки все равно будут изменять файл с помощью шестнадцатеричного редактора. – holgac

+0

Я знаю, но таким образом, по крайней мере, игрок не сможет просто открыть файл с помощью блокнота и установить свою жизнь на 8000 – twkmz

0

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

Значит, вы определяете формат файла (данных в файле). Например, «каждая строка содержит одно поле. Первая строка содержит здоровье как номер ASCII, вторая строка ...» и т. Д.

Затем вы публикуете свои спецификации для всех, кому когда-либо понадобится прочитать ваши файлы.

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

Смежные вопросы