Запись и чтение структур в/из файл(а)
11.10.2002
|
IT |
ПА>Ламерский вопрос
Почему ламерский? Нормальный вопрос, который обычно встречается большинству программеров.
ПА>Есть у меня некая структура (struct).
ПА>Требуется создать файл, куда писать данные в виде этих структур. А также читать. А также перемещаться по файлу.
ПА>Как это сделать? (Плиз с ма-аленьким примером или в какую сторону копать).
Всё зависит от многих вещей, как всегда
Для начала давай объявим твою структуру:
Теперь, допусти, нам нужно положить её в файл средствами C/C++.
Семейсво функций FILE рассматривать не будет в связи с её архаичносью, и начнём сразу с варианта, который не входит в стандарт, но присутствует во многих компиляторах.
Эта программа открывает файл (либо создаёт его при необходимости) и добавляет в него новую структуру, затем читает первый экземпляр.
Всё казалось бы нормально, но если ты посмотришь размер созддаваемого файла, то он всегда будет кратен 24 байтам (вариант Visual C++), хотя размер структуры равен 4+5+8=17 байт. Это происходит потому, что компиляторы по умолчанию выравнивают размер структур в целях оптимизации. Следовательно, наша первая задача отменить это поведение по умолчанию. Стандартных средств сделать это нет, но как правило компиляторы содержат специальную опцию коммандной строки и/или прагму, позволяющую это делать.
Ещё одной неверной деталью в нашем примере является использование типа переменной int. Для разных версий операционных систем размер инта может быть разным и лучше явно указать размер используемого типа — short или long.
Изменим описание структуры:
Теперь запись в файл даст вполне ожидаемый результат.
Здесь можно отметить ещё одну деталь. В качестве строки я использовал массив char[5]. Использование классов типа CString std::string не приведёт ни к чему хорошему. Фактически ты сохранишь не саму строку, а содержимое класса, который её реализует. Допустим, класс CMyString реализован следующим образом:
Объявление такой структуры как
будет фактически соответствовать следующему варианту:
Т.е. в месте, где ты ожидаешь строку будет указатель на буфер в памяти, который (в смысле не буфер, а указатель на него) ты благополучно и сохранишь в файле.
Теперь рассмотрим вариант с потоками. Вообще-то, лучше конечно использовать новую версию <fstream>, но у меня она до сих не вызывает никакого доверия. По-этому, воспользуемся старым вариантом:
Ниже вариант использования Windows API вместо фуекций CRTL:
И т.д.
Еще можно привести вариант исользования класса CFile из MFC, но, в принципе, он будет не очень сильно отличаться.
Может я немного увлёкся, но главное чтобы было понятно
ЗЫ. Далше тебя ждут другие вопросы:
Шутю я, шутю.
Почему ламерский? Нормальный вопрос, который обычно встречается большинству программеров.
ПА>Есть у меня некая структура (struct).
ПА>Требуется создать файл, куда писать данные в виде этих структур. А также читать. А также перемещаться по файлу.
ПА>Как это сделать? (Плиз с ма-аленьким примером или в какую сторону копать).
Всё зависит от многих вещей, как всегда
Для начала давай объявим твою структуру:
struct mystruct {
int i;
char buf[20];
double d;
};
Теперь, допусти, нам нужно положить её в файл средствами C/C++.
Семейсво функций FILE рассматривать не будет в связи с её архаичносью, и начнём сразу с варианта, который не входит в стандарт, но присутствует во многих компиляторах.
#include <sys/stat.h>
#include <memory.h>
#include <fcntl.h>
#include <stdio.h>
#include <io.h>
struct mystruct {
int i;
char buf[5];
double d;
};
int main(int argc, char* argv[])
{
// открываем файл
int fh = _open("file.dat",_O_RDWR | _O_BINARY);
if (fh == -1)
{
// или при необходимости создаём новый
fh = _creat("file.dat",_S_IREAD | _S_IWRITE);
if (fh == -1)
// не шмагла :xz:
return 1;
}
// готовим структуру для записи
mystruct ms;
memset(&ms,0,sizeof ms);
ms.i = 1;
ms.d = 2;
// позиционируемся в конец файла
_lseek(fh,0,SEEK_END);
// добавляем новую структуру
_write(fh,&ms,sizeof ms);
// позиционируемся в начало
_lseek(fh,0,SEEK_END);
// читаем первую записанную структуру
_read(fh,&ms,sizeof ms);
return 0;
}
Эта программа открывает файл (либо создаёт его при необходимости) и добавляет в него новую структуру, затем читает первый экземпляр.
Всё казалось бы нормально, но если ты посмотришь размер созддаваемого файла, то он всегда будет кратен 24 байтам (вариант Visual C++), хотя размер структуры равен 4+5+8=17 байт. Это происходит потому, что компиляторы по умолчанию выравнивают размер структур в целях оптимизации. Следовательно, наша первая задача отменить это поведение по умолчанию. Стандартных средств сделать это нет, но как правило компиляторы содержат специальную опцию коммандной строки и/или прагму, позволяющую это делать.
Ещё одной неверной деталью в нашем примере является использование типа переменной int. Для разных версий операционных систем размер инта может быть разным и лучше явно указать размер используемого типа — short или long.
Изменим описание структуры:
#pragma pack(push,1)
struct mystruct {
long i;
char buf[5];
double d;
};
#pragma pack(pop)
Теперь запись в файл даст вполне ожидаемый результат.
Здесь можно отметить ещё одну деталь. В качестве строки я использовал массив char[5]. Использование классов типа CString std::string не приведёт ни к чему хорошему. Фактически ты сохранишь не саму строку, а содержимое класса, который её реализует. Допустим, класс CMyString реализован следующим образом:
class CMyString {
public:
int len;
char *str;
// ....
};
Объявление такой структуры как
struct mystruct {
long i;
CMyString str;
double d;
};
будет фактически соответствовать следующему варианту:
struct mystruct {
long i;
int str_len;
char *str_str;
double d;
};
Т.е. в месте, где ты ожидаешь строку будет указатель на буфер в памяти, который (в смысле не буфер, а указатель на него) ты благополучно и сохранишь в файле.
Теперь рассмотрим вариант с потоками. Вообще-то, лучше конечно использовать новую версию <fstream>, но у меня она до сих не вызывает никакого доверия. По-этому, воспользуемся старым вариантом:
#include <memory.h>
#include <fstream.h>
#pragma pack(push,1)
struct mystruct {
long i;
char buf[5];
double d;
};
#pragma pack(pop)
int main(int argc, char* argv[])
{
// создаём или открываем файл
fstream f("file.dat",ios::binary|ios::in|ios::out);
// готовим структуру для записи
mystruct ms;
memset(&ms,0,sizeof ms);
ms.i = 1;
ms.d = 2;
// позиционируемся в конец файла
f.seekp(0,ios::end);
// добавляем новую структуру
f.write((unsigned char*)&ms,sizeof ms);
// позиционируемся в начало
f.seekp(0,ios::beg);
// читаем первую записанную структуру
f.read((unsigned char*)&ms,sizeof ms);
return 0;
}
Ниже вариант использования Windows API вместо фуекций CRTL:
#include <windows.h>
#pragma pack(push,1)
struct mystruct {
long i;
char buf[5];
double d;
};
#pragma pack(pop)
int main(int argc, char* argv[])
{
// создаём или открываем файл
HANDLE fh = ::CreateFile(
TEXT("file.dat"),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
// готовим структуру для записи
mystruct ms;
memset(&ms,0,sizeof ms);
ms.i = 1;
ms.d = 2;
// позиционируемся в конец файла
::SetFilePointer(fh,0,0,FILE_END);
// добавляем новую структуру
DWORD dw=0;
::WriteFile(fh,&ms,sizeof ms,&dw,NULL);
// позиционируемся в начало
::SetFilePointer(fh,0,0,FILE_BEGIN);
// читаем первую записанную структуру
::ReadFile(fh,&ms,sizeof ms,&dw,NULL);
return 0;
}
И т.д.
Еще можно привести вариант исользования класса CFile из MFC, но, в принципе, он будет не очень сильно отличаться.
Может я немного увлёкся, но главное чтобы было понятно
ЗЫ. Далше тебя ждут другие вопросы:
- Как узнать количество записанных структу в файле?
Правильный ответ — не вычислять это по размеру файла, а добавить в начало заголовок (специальную структуру), содержащую необходимую служебную информацию: фактический размер файла, версию формата, число записей, смещение к первому блоку и т.п.
Как добавлять записи переменной длины?
Можно к каждой записи добавить свой заголовок, описывающий её структуру.
Как удалять ненужные записи из файла?
Можно просто помечать их как удалённые, а в последствии организовать упаковку файла. Можно организовать список удалённых страниц и использовать их в дальнейшем вместо добавления новых в конец.
Как обеспечить совместный доступ к файлу из нескольких программ.
Блокировки, отдельный сервер доступа и ещё куча всяких вариантов.
Как сделать динамическую структуру записей в файле...
У-у-у...
Шутю я, шутю.
11.10.2002 7 комментариев |
IT>После всего этого возникает вполне законный вопрос — а может лучше сразу взять стандартную базу данных?
IT>Шутю я, шутю.
Если бы речь шла об обычных виндах — я бы так и сделал
Однако все происходит в WinCE. Да еще на обкоцанном варианте Casio. Поэтому с ADOCE возникают некоторые проблемы...
IT>>После всего этого возникает вполне законный вопрос — а может лучше сразу взять стандартную базу данных?
ПА>Однако все происходит в WinCE. Да еще на обкоцанном варианте Casio. Поэтому с ADOCE возникают некоторые проблемы...
Тогда у тебя возникает еще ряд проблем:
— CE система юникодная, осторожнее со строками при записи и чтении
— не помню какой Endian на Casio (это кажется был MIPS), осторожнее с переносом файлов с win32
— нельзя игнорировать alignment, точнее он должен быть на 4 байта. MIPS не позволяет адресовать 32-битное число по невыровненному адресу
это важно, если ты собираешься читать массивами структур
I would recommend using WINAPI CreateFile, ReadFile, ... functions тьфу блин, совсем зарапортавался...
Используй WINAPI CreateFile, ReadFile, ... — в winCE другого может и не быть, лучше всего завернуть это дело в портабельную оболочку.
Всегда указывай полный путь до файла, в WinCE нет понятия "текущая директория", если нужно — возьми у текущего модуля.
TCHAR buf[_MAX_PATH+30];
GetModuleFileName(hInstance, buf, _MAX_PATH);
Если объёмы данных небольшие — используй XML. Была где-то библиотека портированная для CE, поищи.
IT>Ещё одной неверной деталью в нашем примере является использование типа переменной int. Для разных версий операционных систем размер инта может быть разным и лучше явно указать размер используемого типа — short или long.
Интересно, неужели для разных версий операционных систем размеры short или long, в отличие от int, одинаковы?
Насколько я помню, есть sizeof(short int) <= sizeof(int) <= sizeof(long int) в стандарте и без всяких гарантий о точных значениях.
Так что и использование short или long не даст переносимости.
IT>>Ещё одной неверной деталью в нашем примере является использование типа переменной int. Для разных версий операционных систем размер инта может быть разным и лучше явно указать размер используемого типа — short или long.
Здесь IT был не вполне точен.
Vi2>Интересно, неужели для разных версий операционных систем размеры short или long, в отличие от int, одинаковы?
Не только для разных операционных систем, но и для разных компиляторов на одной операционной системе размеры short, long и int могут различаться.
Vi2>Насколько я помню, есть sizeof(short int) <= sizeof(int) <= sizeof(long int) в стандарте и без всяких гарантий о точных значениях.
Кроме указанного соотношения есть еще требования к минимальному диапазону представляемых значений:
Vi2>Интересно, неужели для разных версий операционных систем размеры short или long, в отличие от int, одинаковы?
Ну если считать Windows 3.1 и Windows 95 разными версиями то да К тому же MS выпустила там чего-то 64 разрядное кажется, там уже и long поплывёт.
Vi2>Насколько я помню, есть sizeof(short int) <= sizeof(int) <= sizeof(long int) в стандарте и без всяких гарантий о точных значениях.
Vi2>Так что и использование short или long не даст переносимости.
Ладно придираться, я только обозначил проблему.
IT>Всё зависит от многих вещей, как всегда
Это точно! Несмотря на то, что топику уже несколько лет, я хотел бы вставить свои 5 копеек в обсуждаемую тему, тем более, что эта тема находится в статьях сайта, а значит не имеет срока давности.
Наиболее правильным и переносимым (но медленным) будет вообще не сохранять в файл структуру целиком. Более того, не желательно даже int записывать в файл напрямую. Одна из причин уже была озвучена в топике: несовпадающие размеры int в разных компиляторах даже на одной платформе. Другая причина кроется в переносимости. Не факт, что ваша программа (или даже её отдельный модуль) никогда не переедут под UNIX. В отличие от Windows, UNIX существует на большом зоопарке платформ. Некоторые из них имеют big-endian представления чисел (первым идёт старший байт). Если на такой платформе сохранить int в файл как есть, а затем прочитать этот файл на Intel-платформе, то мы получим совершенно другое число. Например (пусть размер int будет 32 бита), если на big-endian сохранили 0x12345678, то в little-endian прочитаем 0x78563412.
Для большинства эти детали не существенны, так как не так уж и много программистов (надо признаться, и я в том числе) пишут сразу для нескольких платформ. Но, по крайней мере, задуматься о потенциальных проблемах переносимости надо.
Как же сохранять данные в файл, чтобы и файл, и программа его читающая были переносимы? Ответ уже был в этом топике: сохранять всё в xml или другом текстовом файле. Однако, если объём данных велик, и от двоичных данных никуда не деться, то сохранять все данные следует побайтно. Пример сохранения 32-битного int (предполагаем, что система, где char имеет размер, не равный 8 бит, нам не попадётся):
Если гложут вопросы быстродействия, особенно в свете того, что big-endian систем не много и ради этой редкости мы замедляем считывание файлов, то необходимо ввести условную компиляцию: на little-endian системе читать int целиком, на big-endian – побайтно. Опять же, нельзя забывать про отличия размера int на разных платформах.
Хочу отметить, что я далеко не гуру в этом вопросе, и все мои замечания основаны лишь на изучении и портировании Open source ПО на компьютер RM200 (RISC-процессор R4000 (big-endian), ОС SINIX).
[]
> Как же сохранять данные в файл, чтобы и файл, и программа его читающая были переносимы? Ответ уже был в этом топике: сохранять всё в xml или другом текстовом файле. Однако, если объём данных велик, и от двоичных данных никуда не деться, то сохранять все данные следует побайтно. Пример сохранения 32-битного int (предполагаем, что система, где char имеет размер, не равный 8 бит, нам не попадётся):
http://rsdn.ru/Forum/?mid=1104166
http://rsdn.ru/Forum/?mid=1110419
--
Maxim Yegorushkin