1 Востаннє редагувалося Betterthanyou (16.03.2017 19:04:00)

Тема: Робота з бінарним файлом

1) Я написав таку функцію для читання бінарного файлу, проблема в тому що мені не вдається записати в об'єкт obj, для якого була виділена динамічно пам'ять, вміст файлу.
Ось наприклад: якщо код виконувати так як він зараз виглядає, то в obj нічого не запишеться
Якщо написати

inFile->read( (char*)&obj, sizeof(obj) );

то функція read просто перезаписує obj, при виділені пам'яті об'єкт має адрес 0x00d98438, а після функції read 0x00d99420, ну і звичайно його не можна видалити (delete obj), точніше він видаляється, але видаляє щось не те і потім мені програма "кидає" помилку "big allocation alignment"

template<typename T>
T *readStructure(string pathToFile) try
{
    T *obj = nullptr;
    ifstream *inFile = new ifstream;
    inFile->open(pathToFile, std::ifstream::in | std::ifstream::binary);
    if (inFile->is_open())
    {
        obj = new T;
        inFile->read( (char*)obj, sizeof(obj) );
    }
    else
    {
        cout << "Error! Cannot open file";
    }
    delete inFile;
    return obj;
}
catch (exception &e)
{
    cerr << "Error! " << e.what();
    getchar();
    exit(EXIT_FAILURE);
}

Як правильно записати в об'єкт, якому була динамічно виділена пам'ять, вміст файлу ?

Файл створюється такою функцією
template<typename T>
void writeStructure(T obj, string pathToFile) try
{
    ofstream *outFile = new ofstream;
    outFile->open(pathToFile, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
    if(outFile->is_open())
    {
        outFile->write( (char*)&obj, sizeof(obj) );
    }
    else
    {
        cout << "Error! Cannot open file";
    }
    delete outFile;
}
catch (exception &e)
{
    cerr << "Error! " << e.what();
    getchar();
    exit(EXIT_FAILURE);
}

2) Якщо я захочу записати масив у бінарний файл, то мені потрібно кожен елемент записувати окремо чи ні ?
Масив, наскільки я розумію, це лише адрес першого елемента (а всі інші елементи знаходяться послідовно), тобто якщо в мене буде якийсь масив, і я напишу так

outFile->write( (char*)&arr, sizeof(arr) * кількість_елементів + 1 );

то все одно запишеться лише адрес масиву чи запишеться кожен елемент масиву (без адрес) ?

2 Востаннє редагувалося Torbins (17.03.2017 12:41:27)

Re: Робота з бінарним файлом

То усе ж таки

inFile->read( (char*)&obj, sizeof(obj) );

чи

inFile->read( (char*)obj, sizeof(obj) );

?


Betterthanyou написав:

2) Якщо я захочу записати масив у бінарний файл, то мені потрібно кожен елемент записувати окремо чи ні ?
Масив, наскільки я розумію, це лише адрес першого елемента (а всі інші елементи знаходяться послідовно), тобто якщо в мене буде якийсь масив, і я напишу так

outFile->write( (char*)&arr, sizeof(arr) * кількість_елементів + 1 );

то все одно запишеться лише адрес масиву чи запишеться кожен елемент масиву (без адрес) ?

В залежності від типів елементів та налаштувань вирівнювання компілятора, запишеться перших 50-100% елементів. Якщо менше 100%, то між елементами буде сміття.

Подякували: Betterthanyou1

3

Re: Робота з бінарним файлом

Torbins написав:

В залежності від типів елементів та налаштувань вирівнювання компілятора, запишеться перших 50-100% елементів. Якщо менше 100%, то між елементами буде сміття.

А можливо проблема в тому що я зберігаю ?
Структура

struct base
{
    string name;
    time_t currentDate;
};

Створення об'єкта

        base *obj1 = new base;
    obj1->name = "Axel";
    time(&obj1->currentDate);

таке можна зберігати ? (obj1)

4

Re: Робота з бінарним файлом

Ваша проблема  - це гарна ілюстрація чому не треба використовувати приведення типів у С-стилі.
"Зроби obj вказівником на перший елемент масива" - ви впевнені що саме це мали на увазі?
читайте файл та користуйтеся memcpy якщо ви впевнені що те що ви читаєте може бути obj.

Подякували: Betterthanyou1

5

Re: Робота з бінарним файлом

Теж саме відноситься до запису. Щоб серіалізувати структуру вам треба її спочатку записати у масив. Я б зробив би (наприклад) шаблон або шаблонну функцію яка б відповідала за запис. Якщо ви пишете на С++ не намагайтеся використовувати суржик С. Пишіть на С++.

Подякували: Betterthanyou1

6 Востаннє редагувалося Torbins (18.03.2017 16:54:38)

Re: Робота з бінарним файлом

Betterthanyou
А який розмір вашої struct base? Ви не вказуєте розмір для name, отже він буде залежати від рядка всередині. Такі динамічні структури зазвичай зберігаються в купі, а у вашій struct - лише посилання на потрібне місце купи. Якщо ви тупо записуватимете в файл значення struct, то в файл потраплять лише адреси посилань на купу. Які після перезапуску програми будуть уже невірними.

Подякували: Arete, Betterthanyou2

7

Re: Робота з бінарним файлом

Betterthanyou
Ваша структура не є POD (Plain Old Data). Або використовуйте POD, або ж глибоке копіювання (deep copy).

Подякували: Betterthanyou1

8

Re: Робота з бінарним файлом

Довго я пробував доробити функції для запису і читання будь-яких структур в двійковому файлі, але все одно не виходить. Тому я написав у структурі base(про яку я писав у попередньому повідомлені) метод "ручного запису". (дивіться нижче writeBase, readBase)

Що до POD структур я доробив функції, і тепер вони з ними працюють.

Записати структуру даних (мається на увазі не struct, а будь-яку структуру (int, class, union, etc...))

template<typename T>
void writeStructure(T obj, string pathToFile) try
{
    if (!std::is_pod<T>::value)
    {
        throw exception("Cannot write non-POD data");
    }
    ofstream *outFile = new ofstream;
    outFile->open(pathToFile, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
    if(outFile->is_open())
    {
        outFile->write( reinterpret_cast<char*>(&obj), sizeof(obj) );
    }
    else
    {
        throw exception("Cannot open file");
    }
    outFile->close();
    delete outFile;
}
catch (exception &e)
{
    cerr << "Error! " << e.what();
    getchar();
    exit(EXIT_FAILURE);
}

Прочитати структуру даних

template<typename T>
T *readStructure(string pathToFile) try
{
    if (!std::is_pod<T>::value)
    {
        throw exception("Cannot read non-POD data");
    }
    T *obj = nullptr;
    ifstream *inFile = new ifstream;
    inFile->open(pathToFile, std::ifstream::in | std::ifstream::binary);
    if (inFile->is_open())
    {
        obj = new T;
        inFile->read( reinterpret_cast<char*>(obj), sizeof(*obj) );
    }
    else
    {
        throw exception("Cannot open file");
    }
inFile->close();
    delete inFile;
    return obj;
}
catch (exception &e)
{
    cerr << "Error! " << e.what();
    getchar();
    exit(EXIT_FAILURE);
}

Наприклад:
Структура

struct MyStruct
{
    int a;
    char b;
    double c;
    long long d;
};

Запис і читання

/*Просто перевірка*/
cout << std::is_pod<MyStruct>::value << '\n';//true
    cout << sizeof(MyStruct) << '\n';//20
    cout << sizeof(MyStruct::a) + sizeof(MyStruct::c) + sizeof(MyStruct::d) << '\n';//24
    MyStruct *obj3 = new MyStruct;
    obj3->a = 7;
    obj3->b = 'q';
    obj3->c = 6.9547;
    obj3->d = 123456789;
    cout << "Show object\n";
    writeStructure(*obj3, "file");//НЕ "obj3" а "*obj3" - якщо передати посилання то й запишеться посилання
    MyStruct *obj4(readStructure<MyStruct>("file"));
    cout << obj4->a << endl;//7
    cout << obj4->b << endl;//'q'
    cout << obj4->c << endl;//6.9547
    cout << obj4->d << endl;//123456789
    delete obj4;

Я довго шукав інформацію про купу, але безрезультатно, це ж не воно (make_heap) ? Тому я не знаю як отримати до неї доступ.
Щодо глибокого копіювання, щоб їм скористатися потрібно знати назви всіх полів структури ? Бо я не знаю як інакше їм можна скористатися, я його застосував в структурі base написавши методи writeBase, readBase
Отут

outFile->write(reinterpret_cast<char*>(&name[0]), sizeName);//записую n комірок пам'яті (де n - sizeName)
struct base
{
    string name;
    time_t currentDate;
    void writeBase() 
    {
        ofstream *outFile = new ofstream;
        outFile->open("file", std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
        if (outFile->is_open())
        {
            size_t sizeName = name.size();
            outFile->write(reinterpret_cast<char*>(&sizeName), sizeof(sizeName));
            outFile->write(reinterpret_cast<char*>(&name[0]), sizeName);
            outFile->write(reinterpret_cast<char*>(&currentDate), sizeof(currentDate));
        }
        outFile->close();
        delete outFile;
    }
    void readBase()
    {
        ifstream *inFile = new ifstream;
        inFile->open("file", std::ifstream::in | std::ifstream::binary);
        if (inFile->is_open())
        {
            size_t sizeName;
            inFile->read(reinterpret_cast<char*>(&sizeName), sizeof(sizeName));
            name.resize(sizeName);
            inFile->read(reinterpret_cast<char*>(&name[0]), sizeName);
            inFile->read(reinterpret_cast<char*>(&currentDate), sizeof(currentDate));
        }
        inFile->close();
        delete inFile;
    }
};

Наприклад:

base *obj1 = new base;
    obj1->name = "Axel";
    time(&obj1->currentDate);

    obj1->writeBase();
    base *obj = new base();
    obj->readBase();
    cout << obj->name << endl;
    cout << ctime(&obj->currentDate) << endl;
    delete obj;

Але як мені застосувати глибоке копіювання в шаблонних функція writeStructure/readStructure ?

9

Re: Робота з бінарним файлом

varkon написав:

Якщо ви пишете на С++ не намагайтеся використовувати суржик С. Пишіть на С++.

На C++ (наскільки я знаю) немає, наприклад, бібліотеки для роботи з часом ctime, бібліотеки з розмірами типів climits, бібліотеки для роботи з масивами символів cstring і т.д.

10

Re: Робота з бінарним файлом

Betterthanyou написав:
varkon написав:

Якщо ви пишете на С++ не намагайтеся використовувати суржик С. Пишіть на С++.

На C++ (наскільки я знаю) немає, наприклад, бібліотеки для роботи з часом ctime, бібліотеки з розмірами типів climits, бібліотеки для роботи з масивами символів cstring і т.д.

Ви неправильно знаєте. Якраз ці бібліотеки є на C++ (в C вони звуться відповідно time, limits і string). Але використовувати їх в C++ вважається.. ну, не те, що небажаним, але принаймні, якщо ви хочете їх використовувати, то маєте це робити свідомо, із розумінням, які є альтернативи, в чому їхні переваги і недоліки. Рідні відповідні бібліотеки C++ звуться chrono та - насподівано - limits і string.

Подякували: Betterthanyou, varkon2

11

Re: Робота з бінарним файлом

Betterthanyou написав:

Я довго шукав інформацію про купу, але безрезультатно, це ж не воно (make_heap) ? Тому я не знаю як отримати до неї доступ.

Він вам і не потрібен, бо купа не містить необхідної вам інформації.

Betterthanyou написав:

Щодо глибокого копіювання, щоб їм скористатися потрібно знати назви всіх полів структури ? Бо я не знаю як інакше їм можна скористатися, я його застосував в структурі base написавши методи writeBase, readBase

Отримати інформацію про класи можна з RTTI, ця процедура називається рефлексією. Кількість інформації в структурах RTTI залежить в першу чергу від налаштувань компілятора, але є, мабуть, й інші чинники.
Але навіть із застосуванням рефлексії не кожну структуру можна відновити після збереження в файл. Наприклад уявіть собі, що структура зберігає хендл відкритого файлу. Його значення без проблем запишеться в файл, бо це звичайне число. Але після завантаження з файлу воно не запрацює, якщо за цей час програму встигли перезапустити, чи просто закрили файл. Тому усі механізми рефлексії зі стандартних бібліотек різних мов передбачають можливість ручного втручання в процес запису та читання. Наприклад у Джаві є інтерфейс IPersistent з методами flushContents і initializeContents, які має реалізувати об'єкт, якщо замість хендла файлу треба записати шлях до нього.

Подякували: Betterthanyou1