Как записать структуру в двоичный файл c

Обновлено: 21.11.2024

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

C представил многие концепции, которые позже превратились в то, что мы видим сегодня в таких языках, как C++, Java, Javascript, Python и Ruby.

Чтобы быть более точным, C был первым языком, который познакомил большинство программистов с этими концепциями. Историю C можно проследить через язык B (программисты, по-видимому, думали о вещах поважнее, чем о том, как назвать свои языки), который может проследить его происхождение до BCPL и ALGOL. Но я просто говорю вам, что это звучит умно, поскольку я никогда не использовал ни один из этих старых языков. 😊

Как и в примере Javascript в предыдущем посте, C может объявить запись, содержащую имя и возраст, хотя и с некоторыми отличиями. Самое важное отличие от Javascript заключается в том, что C необходимо указать, что имя — это строка, а возраст — это число.

Это выглядит немного по-другому, и я должен объяснить, почему это так.

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

Структура данных person содержит указатель на строку и длинное целое число. (Синтаксис для обозначения указателя — символ *). Обработка строк в C на самом деле неудобна, поскольку они различаются по размеру, поэтому я не хочу пока слишком много говорить об этом. На данный момент имя не сидит внутри структуры. Вместо этого структура содержит указатель на строку имени и целое число, содержащее возраст.

В отличие от других языков, мы можем видеть точное расположение этих данных в памяти. У объекта можно запросить его адрес, а затем его можно рассматривать как серию байтов (беззнаковый символ в C), которые можно напечатать в шестнадцатеричном виде:

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

Но теперь, когда мы можем видеть байты, как они выглядят?

Давайте разделим это на части:

char* name long age
21 30 D9 04 9C 7F 00 00 20 00 00 00 00 00 00 00

Что мы здесь видим? Хотя поле name называется указателем, на самом деле это просто длинное число. Это число, указывающее расположение в памяти строки name (которая, как мы знаем, содержит «Mary» ). Поле age содержит число 32. В шестнадцатеричном формате оно представляется как 20 (2*16 + 0*1).

Неожиданным моментом является то, что после возраста стоят все эти нули. Это могло бы иметь больше смысла, если бы число было 0000000000000020, но это кажется обратным. Это связано с тем, что мы разбиваем число на отдельные байты. Идея здесь в том, что по мере продвижения вправо адреса памяти увеличиваются. Нижние адреса имеют меньшие части числа (сотни, десятки и единицы), а более высокие адреса имеют большие части (миллионы, миллиарды и т. д.). Но когда мы печатаем их от низкого к высокому, как здесь, они смотрят назад.

На самом деле это особенность процессоров Intel (на которых обычно работают Windows и Mac). Они сделали явный выбор для представления своих чисел таким образом. Другие типы ЦП решили представлять числа наоборот. Так было с процессорами PowerPC, которые Apple использовала до 2006 года, а также со многими другими процессорами, использовавшимися в других отраслях промышленности. Компьютеры, которые хранят небольшую часть чисел по более низким адресам, называются архитектурами «Little Endian», а компьютеры, которые сначала хранят большую часть чисел, называются «Big Endian». Это глупая отсылка к «Путешествиям Гулливера» Джонатона Свифта, где лилипуты воевали с народом Блуфуску из-за того, какой конец вареного яйца разбить: большой конец или маленький конец.

Мы также видим, что здесь много нулей. Эти числа занимают 8 байт (64 бита). Действительно ли необходимо иметь возможность хранить настолько большие числа, что они могут занимать все это пространство? Казалось бы, нет, учитывая, что C допускает несколько меньших размеров чисел:

  • int составляет 4 байта или 32 бита.
  • short — 2 байта или 16 бит.
  • char – 8 бит.

Вы уже поняли, почему большинство людей уже перешли на более современные языки?

Число в названии выглядит как случайная ерунда, и в некотором смысле так оно и есть. Фактически, если я снова запущу программу, я получу другое число:
21 90 E4 05 98 7F 00 00 20 00 00 00 00 00 00 00

Почему это? На самом деле это комбинация нескольких вещей. Если на вашем компьютере запущена вредоносная программа, и она может догадаться, где будут ваши данные, она может прочитать или изменить эту информацию злонамеренно. Чтобы смягчить это, в начале 2000-х годов в ячейки памяти была введена некоторая случайность (это затронуло ядро, обращающееся к большему количеству адресов, но, эй, я дотошный). Более важная причина заключается в том, что большинство операционных систем позволяют запускать несколько программ одновременно, и, как правило, они не должны взаимодействовать друг с другом. Частично это означает, что каждый из них получает свое представление о том, как выглядит память, и операционная система может решить, где эта память находится на самом деле, и находится ли она где-то на диске. Это сложная тема под названием «Управление виртуальной памятью», и здесь ей не место. Но на случай, если кто-то, читающий это, разочаруется, я вернусь к этой теме в будущем, поскольку она имеет большое значение при работе с доступом к файлам с отображением памяти.

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

Адаптируя связанный список Javascript из предыдущего поста, мы получаем следующее:

Поскольку пример предназначен для списка чисел, я ограничил значения только этим типом.

Следующий указатель указывает на другую структуру элемента. Его можно использовать для указания на любой адрес памяти, включая объекты локальных элементов или необработанные блоки памяти, которые запрашиваются по мере необходимости. Они создаются с помощью библиотечной функции malloc, где выделяется необходимое количество байтов для необходимых данных:

В приведенных выше примерах доступ к элементам структуры осуществлялся с использованием синтаксиса, подобного element.value = 5 . Однако на этот раз переменная списка содержит указатель на элемент (64-битное число), а не на сам элемент. Есть 2 разных синтаксиса для доступа к данным внутри элемента:

  • Мы можем разыменовать указатель с помощью оператора *, а затем получить обычный доступ к полю: (*list).value
  • Мы можем использовать оператор доступа к полю указателя -> : list->value

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

В Javascript мы использовали функцию-конструктор, которая брала только что выделенный объект и устанавливала в нем значения. Точно так же мы будем использовать функцию, которая создает элемент и устанавливает для нас значения, а затем возвращает его.

Теперь мы можем создать список аналогичным образом:

Обратите внимание, что мы используем NULL в качестве недопустимого адреса. Это всего лишь число 0, которое, как известно ЦП, является недопустимым адресом.

Мы можем увидеть содержимое при печати:

Почему я не использовал рекурсию для печати? Я использовал рекурсию в Javascript для создания строки, которая медленно росла по мере того, как мы продвигались по списку, а затем печатала окончательный результат. На этот раз я печатаю на ходу, а не строю строку. Это связано с тем, что построение строк в C неудобно (у меня есть код, который создает строку, но я решил, что это слишком запутанно для этого поста). Печать по мере выполнения может быть выполнена в функции, которая вызывает сама себя, но поскольку значение не будет возвращено, это не совсем то, о чем мы думаем при использовании рекурсии.

Запуск программы C дает нам список:
1, 1, 2, 3, 5, 8, 13, 21, 34

Итак, мы знаем, что у нас есть код, примерно эквивалентный списку в Javascript.

Много работы потребовалось просто для того, чтобы продублировать то, что у меня уже было в Javascript. Зачем беспокоиться?

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

< td>60 D2 64 E1 FF 7F 00 00
элемент структуры* next длинное значение
40 D3 64 E1 FF 7F 00 00 01 00 00 00 00 00 00 00
20 D3 64 E1 FF 7F 00 00 01 00 00 00 00 00 00 00
00 D3 64 E1 FF 7F 00 00 02 00 00 00 00 00 00 00
E0 D2 64 E1 FF 7F 00 00 03 00 00 00 00 00 00 00
C0 D2 64 E1 FF 7F 00 00< /td> 05 00 00 00 00 00 00 00
A0 D2 64 E1 FF 7F 00 00 08 00 00 00 00 00 00 00
80 D2 64 E1 FF 7F 00 00 0D 00 00 00 00 00 00 00
15 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 22 00 00 00 00 00 00 00

Интересным аспектом здесь является то, как работают указатели. Переходя от конца списка (первые элементы, которые были выделены), мы видим предпоследний элемент (значение 21, шестнадцатеричное число 15), указывающий на последний элемент с номером:
60 D2 64 E1 FF 7F 00 00

Как число, мы можем сжать это вместе и расположить цифры в правильном порядке:
00007FFFE164D260
Мы можем пройтись по всем этим адресам, начиная с конца списка и заканчивая первым. (т.е. в указанном порядке):

< /таблица>

Он не включает адрес головы списка, но следует тому же шаблону, что и другие.

Обратите внимание, что каждый из них ровно на 0x20 больше, чем предыдущий адрес? Это ровно 32, что на 2 длинных значения больше, чем размер структур. Почему 2 дополнительных длинных значения? Что ж, это зависит от распределителя памяти (в моем случае glibc). Честно говоря, я не знаю, почему между выделениями появляется этот пробел. Признаюсь, я заглянул туда (никому не говорите: доступ к памяти, которую вы не выделили, незаконен). Похоже, что для хранения административных данных используется 2 long. Я не знаю, какой тип, но некоторые компиляторы поддерживают идентификаторы типа во время выполнения [RTTI] таким образом, так что, возможно, дело в этом.

В предыдущем примере каждый элемент списка был выделен с помощью malloc. Но эта память может быть выделена другим способом. Что, если мы выделим блок памяти, а затем сами раздадим выделенные места?

Вместо использования malloc в начале add_element мы предоставим адрес для использования. Более разумно называть эту версию init_element :

Теперь нам просто нужно начать с выделения большого куска памяти. В этом случае я попрошу что-то размером 10 элементов, а затем использую каждый из них по очереди. Поскольку память ссылается на структуры элементов, если мы добавим к адресу памяти, то компилятор знает, что нужно добавить столько размеров структуры элементов к адресу. Это позволяет мне просто добавить число к указателю.

Это дает точно такой же результат, как и в прошлый раз. Давайте снова напечатаем необработанные данные:

следующий
00007FFFE164D260
00007FFFE164D280
00007FFFE164D2A0
00007FFFE164D2C0
00007FFFE164D2E0
00007FFFE164D300
00007FFFE164D320
00007FFFE164D340
< td>60 12 2B F2 FF 7F 00 00
элемент структуры* следующий длинное значение
D0 12 2B F2 FF 7F 00 00 01 00 00 00 00 00 00 00
C0 12 2B F2 FF 7F 00 00 01 00 00 00 00 00 00 00
B0 12 2B F2 FF 7F 00 00 02 00 00 00 00 00 00 00
A0 12 2B F2 FF 7F 00 00 03 00 00 00 00 00 00 00
90 12 2B F2 FF 7F 00 00< /td> 05 00 00 00 00 00 00 00
80 12 2B F2 FF 7F 00 00 08 00 00 00 00 00 00 00
70 12 2B F2 FF 7F 00 00 0D 00 00 00 00 00 00 00
15 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 22 00 00 00 00 00 00 00

Тот же шаблон, который мы видели при использовании malloc для каждого элемента, но посмотрите на адреса. На этот раз они отличаются ровно на 0x10 (16 байт), что соответствует размеру структуры. Это то, на что мы надеялись.

Компилятор выполняет некоторую работу здесь, предполагая, что указатель памяти ссылается на объекты элементов структуры. Это позволяет ему вычислять, где начинаются объекты, умножая любые добавленные цифры на размер структуры и так далее. Не каждый язык обязательно даст нам это, поэтому мы можем повторно реализовать его, используя необработанные указатели и выясняя, где что находится самостоятельно. Используемый здесь тип указателя — void* . Это особый тип указателя, который компилятор не будет сравнивать с другими типами указателей, поэтому он отлично работает, передавая его функции init_element, даже если эта функция ожидает указатель на элемент структуры.

Это беспорядочно, но идентично.

Когда мы управляем собственными данными таким образом, мы фактически не ограничены в использовании ячеек памяти в качестве указателей. Например, в приведенном выше коде всего 9 объектов, и все они попадают в блок памяти, которым управляет программа. Поскольку это так, то вместо полной ячейки памяти мы можем ссылаться на смещение в памяти. Еще лучше, поскольку все в этой памяти имеет одинаковый размер 16 байт, то вместо смещений [0,16,32,48,64. ] мы можем использовать [0,1,2,3,4. ] и просто умножаем их на размер объектов. Следует быть осторожным с тем, что смещение 0 допустимо (в отличие от указателей), поэтому вместо NULL для обозначения конца списка мы можем использовать специальное защитное значение -1.

Полученный код нуждается в настройке каждой функции. Итак, давайте покажем всю программу:

Меньше

И наконец, поскольку мы знаем характеристики данных, мы знаем, что у нас есть только небольшое количество элементов, и мы знаем, что 64-битное выравнивание не требуется, нам на самом деле не нужно использовать 2 длинных значения вообще. Мы можем вдвое сократить память, перейдя к значениям int. Если мы ожидаем небольшие списки, которые никогда не содержат числа больше 256, мы можем использовать даже однобайтовые значения без знака char. Таким образом, все ссылки на long могут измениться на unsigned char , а защитное значение -1 должно быть представлено как (unsigned char)-1 . Вот новая структура меньшего элемента.

Полную версию можно увидеть здесь.

Здесь показано, как C использует синтаксис struct для представления выделенной памяти в виде записей структурированных данных. Эти записи данных могут использовать указатели на другие записи, тем самым формируя более сложные структуры данных, при этом приведенный пример представляет собой связанный список.

Хотя выделенная память обычно обрабатывается системой (через malloc ), ею также можно управлять в рамках одного большого блока памяти. Поскольку контроль над этой памятью обрабатывается в программе, программа может использовать свои собственные идентификаторы для ссылки на отдельные объекты, а не указатели памяти. Это устраняет большую часть необходимости и использования указателей памяти, которые упрощает C.

Данные в этих демонстрациях больше не содержат указатели. Любые ссылки между объектами в данных не зависят от среды памяти. Это позволяет структурам данных, таким как связанные списки, быть внутренне непротиворечивыми в этих данных. Это формирует основу для надежного хранения данных на диске, в облаке и т. д.

Хотя C использовался для демонстрации того, как все это работает, теперь, когда потребность в указателях устранена, средства манипулирования указателями в C больше не нужны. Это означает, что мы можем вернуться к языкам, с которыми мы более знакомы, и продолжить оттуда. Мы начнем это в части 4.

Привет!
Я думаю о том, как записать структуру в двоичный файл, кто-нибудь может
помочь мне с этим?

запись структуры typedef

char *name
int контакт
>rec;

файл1 = fopen(. "wb");
.
fwrite(запись, sizeof(запись),1,файл1);

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

Привет!
Я думаю о том, как записать структуру в двоичный файл, кто-нибудь может
помочь мне с этим?

запись структуры typedef

char *name
int контакт
>rec;

файл1 = fopen(. "wb");
..
fwrite(rec, sizeof(rec),1,file1);

Я пробовал описанное выше, но, похоже, оно не работает должным образом. Может

Конечно, нет — по разным причинам. Наиболее важным из них является то, что
ваша `rec` имеет тип, а не переменную. Если вы использовали `sizeof` в качестве
функции для отключения вашего компилятора, вам не следовало этого делать.

Ниже приведен код, который демонстрирует то, что вам может понадобиться, но будьте осторожны,
в нем нет никакой проверки ошибок, и он делает некоторые предположения, которые могут
не выполняться для вас (например, что "testfile" - это допустимое имя файла). Он
чисто компилируется и работает на моем Linux-компьютере, как и ожидалось.

typedef struct record char *name;
внутренний контакт;
> запись ;
int main(void)

вернуть 0;
>
--
Удивительно, насколько "зрелая мудрость" похожа на чрезмерную усталость.


Я думаю о том, как записать структуру в двоичный файл,
кто-нибудь может помочь мне с этим?

typedef struct record char *name
int contact
>rec;

файл1 = fopen(. "wb");
..
fwrite(rec, sizeof(rec),1,file1);

Я пробовал описанное выше, но, похоже, оно не работает должным образом. Не мог бы
кто-нибудь помочь мне.

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

--
"Если вы хотите опубликовать продолжение через groups.google.com, не используйте
неработающую ссылку "Ответить" внизу статьи. Нажмите
>"показать параметры" в верхней части статьи, затем нажмите
"Ответить" в нижней части заголовков статьи." - Кит Томпсон
Подробнее:
Также см.

Берко считает: запись структуры typedef

char *name
int контакт
>rec;

файл1 = fopen(. "wb");
..
fwrite(rec, sizeof(rec),1,file1);

Я пробовал описанное выше, но, похоже, оно не работает должным образом. Может

Конечно, нет — по разным причинам.Наиболее важным из них является то, что
ваша `rec` имеет тип, а не переменную. Если вы использовали `sizeof` в качестве
функции для отключения вашего компилятора, вам не следовало этого делать.

Более того, _it_ не должен был этого делать.
Ниже приведен некоторый код, который демонстрирует, что вам может понадобиться, но будьте осторожны, он
не имеет никакой проверки ошибок и делает некоторые предположения, которые могут
не выполняться для вас (например, что "testfile" правильное имя файла). Он
компилируется чисто и работает на моем Linux-компьютере, как и ожидалось. typedef struct record char *name;
внутренний контакт;
> запись ; запись a_rec = ;
запись b_rec = ; fwrite(&a_rec, размер a_rec, 1, файл1); fread(&b_rec, размер b_rec, 1, файл1);

Другой, более важный нюанс заключается в том, что вы можете ожидать чтения
обратного значения действительного указателя только в том случае, если оно изначально было нулевым, или если вы
все еще выполняете одно и то же выполнение программа и не освободила
память, на которую указывает указатель.
Например, запись указателя на автоматически выделенный массив и
чтение его обратно при другом вызове той же функции может легко
поместить этот массив в другое место в памяти, что сделает указатель недействительным. .
Запись указателя при одном запуске программы и чтение его обратно при
другом еще более вероятно приведет к тому же самому.

char *name
int контакт
>rec;

файл1 = fopen(. "wb");
..
fwrite(rec, sizeof(rec),1,file1);

Я пробовал описанное выше, но, похоже, оно не работает должным образом. Может

Конечно, нет — по разным причинам. Наиболее важным из них является то, что
ваша `rec` имеет тип, а не переменную. Если вы использовали `sizeof` в качестве
функции для отключения вашего компилятора, вам не следовало этого делать.

Более того, _it_ не должен был этого делать.

Да, за первое упоминание `rec`. Увидев, что OP набрал код
не заботясь о том, чтобы он был едва ли правильным, я проигнорировал этот бит.

Ниже приведен код, который демонстрирует то, что вам может понадобиться, но будьте осторожны, он
не имеет никакой проверки ошибок и делает некоторые предположения, которые могут
не выполняться для вас (например, что "testfile " является допустимым именем файла). Он
чисто компилируется и работает на моем Linux-компьютере, как и ожидалось.

Другой, более важный нюанс заключается в том, что вы можете ожидать чтения
обратного значения действительного указателя только в том случае, если оно изначально было нулевым, или если вы
все еще выполняете одно и то же выполнение программа и не освободила
память, на которую указывает указатель.
Например, запись указателя на автоматически выделенный массив и
чтение его обратно при другом вызове той же функции может легко
поместить этот массив в другое место в памяти, что сделает указатель недействительным. .
Запись указателя при одном запуске программы и чтение его обратно при
другом еще более вероятно приведет к тому же самому.

Вы, конечно, правы. Мне удалось не заметить (например, "видел, но
не увидел") тот факт, что один элемент на самом деле является указателем. Также
было неясно (мне), в чем именно заключалась проблема OP: заставить его
компилироваться и запускаться или выполняться, как ожидалось (что бы это ни было,
ожидалось).

В предыдущем руководстве мы говорили о функциях файлового ввода-вывода и использовании текстовых файлов. В этом руководстве по программированию на C мы поговорим об использовании двоичных файлов.

Двоичные файлы

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

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

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

Операция чтения считывает структуру, на которую указывает индикатор позиции файла. После чтения структуры указатель перемещается на следующую структуру.

Операция записи запишет в текущую структуру, на которую указывает указатель. После операции записи указатель позиции в файле перемещается на следующую структуру.

Функция fseek переместит указатель позиции в файле на запрошенную запись.

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

Функции fread и fwrite принимают четыре параметра:

  • Адрес памяти
  • Количество байтов для чтения на блок
  • Количество блоков для чтения
  • Файловая переменная

Этот оператор fread говорит о чтении x байтов (размер записи) из файла ptr_myfile в адрес памяти &my_record. Запрашивается только один блок.Замена единицы на десять приведет к одновременному считыванию десяти блоков по x байтов.

Давайте рассмотрим пример записи:

В этом примере мы объявляем структуру rec с членами x, y и z типа integer. В основной функции мы открываем (fopen) файл для записи (w). Затем проверяем, открыт ли файл, если нет, выводится сообщение об ошибке и выходим из программы. В цикле for мы заполняем член структуры x числом. Затем записываем запись в файл. Делаем это десять раз, таким образом создавая десять записей. После записи десяти записей мы закроем файл (не забудьте об этом).

Итак, теперь мы записали в файл, давайте прочитаем из только что созданного файла. Взгляните на пример:

Изменяются только две строки в цикле for. С помощью fread мы считываем записи (одну за другой). После прочтения записи мы печатаем элемент x (этой записи).

Единственное, что нам нужно объяснить, это параметр fseek. Функция fseek должна быть объявлена ​​следующим образом:

Функция fseek устанавливает индикатор позиции файла для потока, на который указывает поток. Новая позиция, измеренная в символах от начала файла, получается путем добавления смещения к позиции, указанной параметром «откуда». В файле stdio.h объявлены три макроса с именами: SEEK_SET, SEEK_CUR и SEEK_END.

Если позиция, объявленная с помощью параметра "откуда", равна SEEK_SET, то позиция является началом файла.

SEEK_END можно использовать, если вы хотите перейти к концу файла. (Используя отрицательные числа, можно перемещаться с конца файла.)

Если "откуда" SEEK_CUR, то позиция устанавливается, x байт, от текущей позиции.

Давайте рассмотрим пример:

В этом примере мы используем fseek для поиска последней записи в файле. Эту запись мы читаем с помощью инструкции fread, а с помощью инструкции printf мы печатаем элемент x структуры my_record. Как видите, «цикл for» тоже изменился. «Цикл for» теперь будет отсчитывать до нуля. Затем этот счетчик используется в операторе fseek для установки указателя файла на нужную запись. В результате мы считываем записи в обратном порядке.

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

С оператором fseek в этом примере мы переходим в конец файла. Затем мы перематываем на первую позицию в файле. Затем прочитайте все записи и напечатайте значение члена x. Без перемотки получится фигня. (Попробуйте!)

На этом уроке все.

Эта запись была опубликована в Учебники по C. Вы можете следить за любыми ответами на эту запись через ленту RSS 2.0. И комментарии и запросы в настоящий момент закрыты. Твитнуть это! или используйте, чтобы поделиться этим сообщением с другими.

В настоящее время есть 32 ответа на «C Tutorial — Binary File I/O»

Почему бы не сообщить нам, что вы думаете, добавив свой комментарий!

это выдающийся краткий учебник по работе с файлами. очень помогает.
спасибо.

Здравствуйте, я изменил раздел записи, чтобы можно было записать только 1 байт, но это не работает,

/* Наша структура */
struct rec
int mydata;

счетчик целых чисел;
ФАЙЛ *ptr_myfile;
структура записи my_record;

в моем test.bin я прочитал "01 00 00 00" в моем шестнадцатеричном редакторе
мой вопрос в том, как я не могу написать тройку 00 00 00 после того, как я поместил свой 01 в файл?
потому что в следующий раз я получу 01 00 00 00 02 00 00 00

Заранее спасибо за вашу помощь и извините за мой английский и французский!

@betagtx260
Если я правильно понял ваш вопрос, вы хотите видеть только 1 байт в двоичном файле.
В этом случае вы используете неправильный тип переменной (в данном случае int), потому что int без знака может содержать все значения от 0 до UINT_MAX включительно. UINT_MAX должен быть не менее 65 535. Типы int должны содержать не менее 16 бит для хранения требуемого диапазона значений. Но это также может варьироваться в зависимости от компилятора и платформы, для которой вы компилируете.

В компиляторах для 8- и 16-разрядных процессоров (включая процессоры Intel x86, работающие в 16-разрядном режиме, например, в MS-DOS), тип int обычно имеет 16 разрядов и имеет точно такое же представление, что и тип short. В компиляторах для 32-разрядных и более крупных процессоров (включая процессоры Intel x86, работающие в 32-разрядном режиме, такие как Win32 или Linux) длина int обычно составляет 32 бита, и она имеет точно такое же представление, что и long. Int содержит 32 бита (таким образом, вы видите 01 00 00 00 в вашем шестнадцатеричном редакторе).

int main()
printf("%d %d \n", sizeof(int), sizeof(short int));
printf("%d %d \n", sizeof(signed int), sizeof(unsigned int));
printf("%d %d \n", sizeof(char), sizeof(unsigned char));
возврат 0;
>

Вывод будет примерно таким (на компьютере с Linux и Intel):
4 2
4 4
1 1

struct rec
char mydata;
>;

int main()
int counter;
ФАЙЛ *ptr_myfile;
структура записи my_record;

ptr_myfile=fopen("test.bin", "w");
if (!ptr_myfile)
printf("Невозможно открыть файл!");
возврат 1;
>

fclose(ptr_myfile);
возврат 0;
>

Если вы откроете файл test.bin с помощью шестнадцатеричного редактора, вы увидите только 1 байт (01 в данном случае), если вы компилируете для платформы Intel (win32 и linux.)
Или если вы хотите использовать int , примите, что в двоичном файле записано 4 байта (но, по крайней мере, теперь вы знаете, почему это так.)

Надеюсь, это ответ на ваш вопрос!

Спасибо за быстрый ответ,
и теперь я понимаю,
снова танки 😉

Привет, после пиратства и попытки понять, как писать и читать,
моя запись идеальна, чего я хочу, но я не могу прочитать свой bin-файл, мне нужна помощь, пожалуйста,

это мой тестовый файл записи работает на 100%

int main()
int i;
целый счетчик;
ФАЙЛ *ptr_myfile;
структура записи my_record;

ptr_myfile=fopen("test.bin", "wb");
if (!ptr_myfile)
printf("Невозможно открыть файл!");
возврат 1;
>

my_record.mydata= я;
fseek (ptr_myfile, counter, SEEK_SET);
fwrite(&my_record, sizeof(struct rec), 1, ptr_myfile);

он ​​создает файл .bin , и в шестнадцатеричном редакторе вы можете прочитать 01 02 03 04 05 06 07 08 и т. д.

но я не могу прочитать файл test.bin с помощью fread, буду признателен за вашу помощь снова
Заранее спасибо

struct rec
char mydata;
>;

int main()
int i;
целый счетчик;
ФАЙЛ *ptr_myfile;
структура записи my_record;

ptr_myfile=fopen("test.bin", "wb");
if (!ptr_myfile)
printf("Невозможно открыть файл!");
возврат 1;
>
я = 0;

for ( counter=0; counter < 10; counter++)
my_record.mydata= i;
fseek (ptr_myfile, counter, SEEK_SET);
fwrite(&my_record, sizeof(struct rec), 1, ptr_myfile);
я++;
>
fclose(ptr_myfile);
возврат 0;
>

struct rec
char mydata;
>;

int main()
int counter;
ФАЙЛ *ptr_myfile;
структура записи my_record;

ptr_myfile=fopen("test.bin", "r");
if (!ptr_myfile)
printf("Невозможно открыть файл!");
возврат 1;
>
for ( counter=1; counter fread(&my_record,sizeof(struct rec),1,ptr_myfile);
printf("%d\n",my_record.mydata); < br />>
fclose(ptr_myfile);
возврат 0;
>

Привет, Tanks за вашу помощь и поддержку,
мое последнее сообщение с моим тестом записи двоичных файлов идеально подходит для меня, потому что я могу записать шестнадцатеричное значение прямо в файл, от 00 до FF в любом месте файла, которое я хочу, но я не могу прочитать его с помощью fread после этого, я хочу попрактиковаться с небольшим файлом, который я создал, потому что мое настоящее приложение я хочу управлять файлом .iso,
и у меня нет успеха с fread с файлом iso.

Это правильное объявление двоичного файла?
Двоичные файлы должны иметь b в режиме
например. w+b или wb
Я прав?

@selewa :
Вы правы, если смотреть только на синтаксис. Но если вы откроете test.bin в текстовом редакторе, вы увидите всевозможные значения ascii. Это связано с тем, что мы записываем/читаем записи (структуры).
Поэтому теперь будет разница между содержимым файла, записанного только с помощью «w» или «wb». Но чтобы сохранить правильный синтаксис, мы изменили примеры исходного кода.
Вы можете использовать следующие режимы:
r — открыть для чтения,
w — открыть для записи (файл не обязательно должен существовать)
a — открыть для добавления (файл не обязательно должен существовать). существует)
r+ — открыть для чтения и записи, начать с начала
w+ — открыть для чтения и записи (перезаписать файл)
a+ — открыть для чтения и записи (дописать, если файл существует)< /p>

Чтобы открыть файл в двоичном режиме, вы должны добавить букву b в конец строки режима; например, «вб».
Для режимов чтения и записи вы можете добавить букву b либо после знака плюс — «r+b», либо перед — «rb+».

Удачи и спасибо за исправление!

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

Например, размер структуры с переменными uint_8 и uint_32 обычно составляет 8 байт вместо 5 из-за упаковки данных компилятором.

Теперь, если вы хотите прочитать 5 байтов из двоичного файла, используя:

вы будете читать неправильные байты в полях (переменная uint32 будет иметь только один байт).

Есть ли способ предотвратить это??

Я видел, как люди читают по одному байту за раз, и если вам нужно, чтобы это было одно число, просто умножьте его на соответствующую позицию…

первое число n*10000
+ второе число n*1000
+ третье число n*100
+ четвертое число n*10
+ пятое число n..

Я не профессионал, поэтому, пожалуйста, поищите лучшие способы.

В последнем примере вы делаете следующее…

fseek(ptr_myfile, sizeof(struct rec), SEEK_END);
перемотать назад(ptr_myfile);

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

Эти две строки в вашем примере бессмысленны. Или я ошибаюсь?

@radix: цель этого примера — показать, как использовать функцию rewind(). Поэтому ищется конец файла (иначе не с чего перематывать), а затем вызывается rewind() для перехода к первой позиции файла.

Вы получите мусор, если перемотка не будет вызвана, потому что цикл for попытается прочитать записи за пределами конца файла, так как не выполняется проверка, достигнут ли конец файла.

Таким образом, это снова просто показать использование функции rewind(): ни больше, ни меньше. Поиск конца файла выполняется только для того, чтобы мы могли перемотать его назад.

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

Можно ли это сделать с помощью функций fwrite и fread?

Я хочу сохранить данные массива символов (89 байт) в двоичный файл. Пожалуйста, пришлите для этого код функции fwrite. Можно ли сохранить текущее время ПК в тот же двоичный файл?

первый пример на веб-странице записывает только в переменную x? как писать и читать, используя переменные y и z?

@mohammed – очень просто использовать переменные y и z в первых примерах записи и чтения. Вам просто нужно добавить несколько дополнительных строк. В примерах теперь используется только переменная x, чтобы примеры были меньше. Чтобы использовать все три переменные (x,y,z), добавьте в примеры следующие строки:

Цикл записи:
for ( counter=1; counter my_record.x= counter;
my_record.y= counter;
my_record.z= counter;
fwrite( &my_record, sizeof(struct rec), 1, ptr_myfile);
>

Пример чтения:
for ( counter=1; counter isaiah 4 августа 2011 г.:

tnx для примера программы tnx много….больше мощности

Это был чрезвычайно полезный урок. Большое спасибо.

Я понимаю, что fseek() смещает указатель на символы. Могу ли я в любом случае сместить указатель на «n» строк? Я пытаюсь записать/перезаписать строку в определенную строку в моем текстовом файле. я пробовал

но это не смещает мой указатель на строку 3 в моем текстовом файле. Просвети меня своей мудростью, великий. 🙂

Еще раз спасибо!
Алан

[…] Учебное пособие по C — ввод-вывод двоичных файлов | Учебники по программированию CodingUnit. Оцените это: Поделитесь этим:TwitterFacebookStumbleUponPrintEmailLinkedInОцените это:LikeБудьте первым, кто […]

очень хорошо объяснил .. thnXX

Привет всем!
Большое спасибо за помощь. То, что я находил, действительно помогло мне достичь моей цели.
Еще раз спасибо… Удачного программирования…!

Спасибо, что написали это! именно то, что мне нужно было знать для моего проекта

Отличный учебник, очень хорошо объясненный. Спасибо 😀

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

Здравствуйте, я пытаюсь прочитать файл формата CEOS спутника, который содержит все типы данных, такие как целочисленные ascii, двоичные.
Я знаю структуру бинарного файла. Я читаю файл в соответствии со структурой, но не могу отображать данные. Требуется ли после чтения данных по структуре преобразовать двоичный в десятичный формат? или я могу использовать «cout» для прямого отображения двоичных данных.

пожалуйста, скажите мне, как я могу преобразовать двоичное число в систему счисления.
используя строку.
например:
двоичное число 64 будет 1000000
(двоичное)1000000 = 64 (основание)

Отличные уроки... очень полезные... Большое спасибо за это...

Я хочу знать, как изображения .bmp отображаются с помощью языка программирования C…. …….

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

Двоичные файлы имеют две особенности, которые отличают их от текстовых файлов:

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

Двоичные файлы также обычно имеют более быстрое время чтения и записи, чем текстовые файлы, потому что двоичный образ записи сохраняется непосредственно из памяти на диск (или наоборот). В текстовом файле все нужно преобразовывать в текст и обратно, а это требует времени.

C очень четко поддерживает концепцию файлов структур.Открыв файл, вы можете прочитать структуру, записать структуру или найти любую структуру в файле. Эта концепция файла поддерживает концепцию указателя файла. При открытии файла указатель указывает на запись 0 (первая запись в файле). Любая операция чтения считывает текущую структуру, на которую указывает указатель, и перемещает указатель вниз на одну структуру. Любая операция записи записывает в текущую структуру, на которую указывает указатель, и перемещает указатель вниз на одну структуру. Поиск перемещает указатель на запрошенную запись.

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

Следующая программа иллюстрирует эти концепции:

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

Здесь представлены новые функции fread, fwrite и fseek. Функция fread принимает четыре параметра:

  • Адрес памяти
  • Количество байтов для чтения на блок
  • Количество блоков для чтения
  • Файловая переменная

Таким образом, строка fread(&r,sizeof(struct rec),1,f); говорит прочитать 12 байт (размер rec) из файла f (из текущего местоположения указателя файла) в адрес памяти &r. Запрашивается один блок из 12 байт. Было бы так же просто прочитать 100 блоков с диска в массив в памяти, изменив 1 на 100.

Функция fwrite работает так же, но перемещает блок байтов из памяти в файл. Функция fseek перемещает указатель файла на байт в файле. Как правило, вы перемещаете указатель с шагом sizeof(struct rec), чтобы указатель оставался на границах записи. Вы можете использовать три варианта поиска:

  • SEEK_SET
  • SEEK_CUR
  • SEEK_END

SEEK_SET перемещает указатель на x байт вниз от начала файла (от байта 0 в файле). SEEK_CUR перемещает указатель на x байт вниз от текущей позиции указателя. SEEK_END перемещает указатель с конца файла (поэтому с этой опцией необходимо использовать отрицательное смещение).

В приведенном выше коде представлено несколько различных параметров. В частности, обратите внимание на раздел, где файл открывается в режиме r+. Это открывает файл для чтения и записи, что позволяет изменять записи. Код ищет запись, читает ее и изменяет поле; затем он выполняет поиск назад, потому что чтение сместило указатель, и записывает изменение обратно.

При работе с двоичными форматами файлов иногда приходится просматривать необработанные биты и байты с помощью шестнадцатеричного редактора, особенно при отладке модуля записи или синтаксического анализатора файлов. Преобразование шестнадцатеричных чисел в структуры и значения в формате файла очень утомительно. Вы должны помнить обо всех смещениях значений и их типах, чтобы прийти к правильному выводу. Расширенной функцией Okteta, шестнадцатеричного редактора KDE, являются определения структуры, которые описывают структуру данных формата файла, чтобы графический интерфейс мог визуализировать значения и структуры, содержащиеся в файле.

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

Сначала мы увидим инструмент структур в действии. Все, что вам нужно, это шестнадцатеричный редактор Okteta, пример файла jitdump и определение структуры для формата файла. Определение состоит из двух файлов, которые необходимо установить вручную, см. документацию. Для меня это сводится к следующим двум командам.

Теперь вам просто нужно скачать файл примера, распаковать его и открыть в Okteta.

В правом нижнем углу вы увидите панель инструмента структур. В данный момент он может быть пуст. Сначала мы должны включить определение структуры. Нажмите «Настройки» в нижней части панели, а затем «Управление структурами». Теперь установите флажок рядом с определением jitdump. После применения изменений вы должны увидеть запись jitdump на панели структур. Вы можете развернуть уровни, чтобы увидеть заголовок файла и список записей различных типов. Вот скриншот.

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

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

В этом разделе мы рассмотрим, как вы можете написать свои собственные определения структуры для интересующих вас форматов файлов. Хорошим началом является документация Okteta и вики-база KDE, которая очень помогла мне с некоторыми мельчайшими деталями. подробности. Я расскажу вам, что я сделал для определения jitdump.

Определение структуры состоит из двух файлов: metadata.desktop и main.osd или main.js, в зависимости от типа определения. Мы сосредоточимся на типе JavaScript, поскольку он позволяет нам динамически изменять расположение данных структур во время синтаксического анализа.

metadata.desktop

Имя, описание и тип определения структуры хранятся в этом файле. Файл .desktop довольно прост и похож на файл .ini. В каждой строке есть пара "ключ-значение" и заголовок раздела "Desktop Entry" в первой строке.

Большинство записей не требуют пояснений. Убедитесь, что для имени и комментария указано правильное имя и описание определения вашей структуры. X-KDE-PluginInfo-Category=structure/js задает тип JavaScript. (Другим вариантом может быть X-KDE-PluginInfo-Category=structure .)

main.js

Фактическое определение находится в этом файле. Формат jitdump состоит из структуры данных заголовка, за которой следует неопределенное количество записей до конца файла (EOF). Определение структуры строится в функции инициализации main.js.

Мы используем функцию struct() для определения структуры данных, аналогичной C/C++. Он имеет два члена: заголовок типа file_header и записи, массив элементов записи, использующий функцию массива (тип, длина) для его определения. Длина массива жестко запрограммирована на 100 элементов, так как я не знаю, как динамически увеличивать ее до конца файла. Нам все еще нужно указать оба типа file_header и record .

Далее определим структуру заголовка file_header. Вот определение C/C++.

Определение довольно простое. Мы снова используем struct() со всеми перечисленными выше членами, используя предопределенные функции uint32() и uint64() для указания целочисленных типов.

Выглядит очень похоже на код C++, не так ли? Элемент magic — это JitD или DTiJ, в зависимости от порядка следования байтов архитектуры. Чтобы он отображался как символы, я изменил тип на массив фиксированного размера char() .

Тип записи немного сложнее. На самом деле он представляет собой семейство типов, состоящее из общего заголовка записи, за которым следуют различные полезные данные, в зависимости от типа записи. Другой способ взглянуть на это: структура, содержащая объединение типов полезной нагрузки.

Это называется тегированным объединением, которое довольно часто встречается в форматах данных. Удобная функция taggedUnion() обеспечивает поддержку таких структур.

Теперь нам просто нужно определить все поля и альтернативы. Начнем с общих полей: заголовка записи.

Заголовок записи состоит из трех полей: id , total_size и timestamp . id — это тип перечисления, указывающий, к какому типу относится эта запись. Чтобы напечатать идентификатор записи перечисления, мы также определяем перечисление в JavaScript и используем предопределенную функцию enumeration(name, base_type, enum_type) .

Мы не будем определять все типы записей, а только JIT_CODE_LOAD и JIT_CODE_DEBUG_INFO, поскольку они являются наиболее важными. Далее давайте посмотрим запись загрузки.

Мы используем alter() для определения записи объединения, которая принимает в качестве первого параметра функцию выбора. Он проверяет, должна ли запись быть активной или нет. Мы проверяем с помощью this.wasAbleToRead ошибки синтаксического анализа, а затем сравниваем значение id в заголовке записи с JIT_CODE_LOAD. В этом контексте это относится к текущей строящейся структуре, которая является объединением с тегами. Мы можем получить доступ ко всему, что уже проанализировано.

Запись загрузки содержит завершающуюся нулем строку имя_функции . Мы определяем его с помощью функции string(encoding) и дополнительно устанавливаем для terminatedBy значение null. Сразу за строкой следует буфер памяти для сгенерированного кода функции. Мы используем массив байтов для его представления. Размер массива определяется в поле code_size. Мы передаем функцию в array(), которая динамически извлекает размер при построении массива. Здесь это относится к массиву, который мы создаем, что означает, что мы должны сначала добраться до родительской структуры, прежде чем получить доступ к значению code_size .

Теперь, когда мы все это знаем, определить запись информации об отладке несложно.

Массивы также могут состоять из агрегатных типов. Сначала мы определяем тип элемента и передаем его в качестве первого параметра в array(type, size) .

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

Выглядит аккуратно. И я редко могу сказать о коде JavaScript…

Мне очень нравится этот инструмент.Определения легко писать, а визуализация расположения данных в шестнадцатеричном редакторе очень помогает. JavaScript определенно не мой любимый язык программирования, но он выполняет свою работу и более гибок, чем определение XML.

Для меня это самый быстрый способ написать парсер для бинарного формата данных и бесплатно получить графический интерфейс. Я рад, что он есть в моем наборе инструментов.

Читайте также: