Запись и чтение структур в/из файл(а)
От: IT Россия blogs.rsdn.ru
Дата: 11.10.02 04:19
Оценка: 100 (18) +2 -2
#Имя: FAQ.cpp.struct2file
ПА>Ламерский вопрос

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

ПА>Есть у меня некая структура (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, но, в принципе, он будет не очень сильно отличаться.

Может я немного увлёкся, но главное чтобы было понятно

ЗЫ. Далше тебя ждут другие вопросы:

  1. Как узнать количество записанных структу в файле?
    Правильный ответ — не вычислять это по размеру файла, а добавить в начало заголовок (специальную структуру), содержащую необходимую служебную информацию: фактический размер файла, версию формата, число записей, смещение к первому блоку и т.п.
  2. Как добавлять записи переменной длины?
    Можно к каждой записи добавить свой заголовок, описывающий её структуру.
  3. Как удалять ненужные записи из файла?
    Можно просто помечать их как удалённые, а в последствии организовать упаковку файла. Можно организовать список удалённых страниц и использовать их в дальнейшем вместо добавления новых в конец.
  4. Как обеспечить совместный доступ к файлу из нескольких программ.
    Блокировки, отдельный сервер доступа и ещё куча всяких вариантов.
  5. Как сделать динамическую структуру записей в файле...
    У-у-у...
После всего этого возникает вполне законный вопрос — а может лучше сразу взять стандартную базу данных?
Шутю я, шутю.
//rsdn.org/forum/images/bis.gif Если нам не помогут, то мы тоже никого не пощадим.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.