С++ для начинающих. Запись структуры в бинарный файл и чтение структуры из бинарного файла

В этой статье будет описан способ записи в бинарный файл самой обычной, самой простой структуры. В своё время я потратил много времени на поиски вразумительных материалов, но тогда это всё было без толку, и первые мои начинания оказывались сильно ошибочными.
  • Чтобы структуру удобно записать враз, нужно знать, сколько памяти занимает объект структуры.

При записи структуры в файл мы копируем биты из памяти в файл. Очень важно взять правильные размеры записываемой сущности. Посмотрим простейший пример:

(char*)&w1 —>> Приводим адрес структуры к типу char*. Так устроен механизм записи в бинарный файл, что нужно приводить тип "адрес" к типу char*.
sizeof(teacher) —>> Записываем согласно размеру структуры. Наша структура в данный момент занимает минимум char + float + int байт, максимум — добавку к этому несколько байт, рождённых автоматическими выравниваниями. Посмотрите сами, совпадает ли у вас размер структуры с суммой размеров заявленных внутри структуры типов.

Если вы посмотрели, то размеры могли или совпасть, или не совпасть. Если не совпали, то это следствие автоматических выравниваний структуры. Если совпали, попробуйте поменять численные типы (short, double). При записи в файл лучше отключить выравнивание. Делается это с помощью директивы препроцессора pragma pack

В данном случае единица нужна, чтобы информация структуры прокладывалась бит к биту. Т. е. происходила абсолютная копия информации из памяти, один к одному. Лучше при необходимости каждую структуру, которой нужно отключить выравнивание, оборачивать прагмой. Но возьмите себе на заметку, что не стоит прагмой злоупотреблять, выравнивания нужны не для балды, а по определённым причинам. Они же не сами по себе появились, это так сделали разрабочтики языка, чтобы типы выравнивались, значит, это нужно.
Размер структуры можно узнавать как минимум двумя способами: применять sizeof к объекту структуры, как было проделано в листинге #1, применять sizeof к имени структуры. В зависимости от ситуации удобным может оказаться или первый, или второй вариант. Предположим, например, что в нашем коде не непосредственно структура, а указатель на структуру, тогда sizeof(teacher) вычисляла бы размер указательной переменной, но не размер структуры:


Поскольку вариант из листинга #1 дал слабину (размер указательной переменной нам при записи в файл явно не нужен сейчас), мы используем в таком случае sizeof к названию структуры:


//Запись структуры в бинарный файл, если на структуру указывает указатель clang Листинг #2

Кроме того, что мы немного иначе подсмотрели размер, при приведении типа у нас не задействовался знак & В данном случае не имеет смысла приводить адрес к адресу, это даже вредит (можете попробовать вставить убранный символ), поэтому мы для приведения задействуем непосредственно название указательной переменной, таким образом мы приводим адрес указательной переменной к типу char*. Такая вот разница есть при записи в бинарный файл обычных типов, с опорой на указательную переменную. Просто понять нужно, что указатель — это адрес, если адрес, то получится взятие адреса какого-то адреса и этот адрес адреса будет приводится, но то другая сущность. Т. е. если имеется адрес (указательная переменная), то записываем, опираясь на название указательной переменной, если не имеется адреса, то записываем, подбирая адрес у переменной/объекта.
В случае наличия указателя внутри структуры, всё осложняется. Вы можете посмотреть пример. Внутри структуры теперь используется указательная переменная. Из-за неё не получается быстренько взять и схлопнуть структуру в бинарный файл, а потом быстро взять и достать из файла. Вот такое вот вредительство.

В нашем случае указательная переменная относительно простая: она представляет собой эквивалент массива. Возможны ситуации, где указатели разбросаны по памяти и собраны в какую-то сущность. Например, это может быть массив указателей (int **arr). В таком случае всё ещё сложнее. Кроме поэлементной обработки внутренних структуре типов может быть придётся обходить те структуры, которые представляют собой указательные переменные. В идеале, конечно, избежать указателей.
  • (char*)&X —> Для новичков неудобная, непонятная запись. Читать ее нужно справа налево. Адрес структуры X приводится к типу указатель на char. Это необходимо, чтобы сообщить методу write (read) адрес структуры в нужном ему виде. Внимательно прочитайте справа-налево, единственное: &X = адрес структуры X, и имейте ввиду, что такой вид обратного чтения при работе с указателями бывает часто удобен.
  • Постарайтесь не запутаться и не взять адрес у адреса, если вам нужен просто адрес
  • Постарайтесь избежать указателей, если это возможно
  • Если всё-таки приходится использоввать указатели, то используйте поэлементную запись структуры в файл, а при записи указателя не забудьте домножить размер указателя на количество элементов, на которое он указывает. Чаще всего указатель воспринимается как эквивалент массива, поэтому чтобы захватить весь массив необходимо умножение
  • Всегда помните, что структуры имеют свойство автоматически выравнивать свои внутренние типы. Это можно отключать, но без острой на то необходимости (в нашем случае была острая необходимость) выравнивания лучше не отключать. Необходимо было отменить выравнивание, чтобы при, например, смене компилятора, программа смогла открывать ранее записанный файл. Если выравнивание нужно отключить, сделать это можно директивой pragma pack

Статья полностью переписана 27.03.2018

4 комментария на «“С++ для начинающих. Запись структуры в бинарный файл и чтение структуры из бинарного файла”»

  1. Выше сказано про другое. То о чем говорилось тогда я исправил тогда.
    На строку выделено 255 символов.
    В строку вводится строка
    Последний символ в строке — признак окончания строки
    За признаком окончания строки все что угодно. Это мусор.

    Добавить строчку

    и мусора не будет видно в файле.

  2. RomixERR:

    А теперь я объясню подробнейшем образом что такое (char*)&X .

    Действительно, (char*)&X не удобная и устаревшая запись. Следует писать reinterpret_cast&X

    • reinterpret_cast(UkVol); гораздо неудобнее и выглядит уродливо.
      То не устаревшая форма записи, то форма записи в стиле Си. Да, считается, что преобразования делать лучше не в стиле Си.
      А еще лучше использовать std::string и не использовать указатели

  3. Влад:

    Спасибо большое, работает!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/evgeniy-kornilov-2/programmirovanie-shahmat-i-drugih-logicheskih-igr/?lfrom=15589587

Последние комментарии

Яндекс.Метрика