Страницы

Поиск по вопросам

понедельник, 24 февраля 2020 г.

Как корректно записать/считать в/из файла структуру с полями типа string? [дубликат]

#cpp #строки #структуры


        
             
                
                    
                        
                            На этот вопрос уже дан ответ здесь:
                            
                        
                    
                
                        
                            Ошибка сохранения сложной структуры в файле
                                
                                    (1 ответ)
                                
                        
                                Закрыт 10 месяцев назад.
            
                    
Имеется структура

struct User {
    string login;
    string password;
};


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

#include 
#include 
#include 
#include 
#include 

using namespace std;

struct User {
    string login;
    string password;
};

void main ()
{
    SetConsoleCP (1251); // установка универсальной кодировки
    SetConsoleOutputCP (1251);

    string path;
    int realsize=0;
    User u;
    vector  U;
    bool exit = false;

    do
    {
        system("cls");
        cout<<"Укажите, на каком диске находится файл с регистрационными данными:\n";
        getline(cin, path);
        path += ":\\users.txt";
        ifstream fin(path, ios_base::binary | ios_base::in);

        if (fin.is_open())
        {
            cout<<"Отлично, ваш файл найден!\n";
            fin.read((char*)&u, sizeof(User));
            U.push_back(u);
            fin.close();
            cout << "Введите логин:\n";
            getline(cin, u.login);
            cout << "Введите пароль:\n";
            getline(cin, u.password);
            if (!U.at(0).login.compare(u.login) && !U.at(0).password.compare(u.password))
            {
                cout << "Вы авторизованы!\n";
            }
            else
            {
                cout << "Вы не авторизованы!\n";
            }
            exit = true;
        }
        else
        {
            cout << "Файл не найден и будет создан";
            ofstream fout (path, ios_base::binary | ios_base::out);
            if (fout.is_open())
            {
                cout << "Введите логин:\n";
                getline(cin, u.login);
                cout << "Введите пароль:\n";
                getline(cin, u.password);
                fout.write((char*)&u, sizeof(User));
                fout.close();
                cout << "Файл создан и данные внесены!\n";
            }
            else
            {
                cout << "Ошибка при создании файла! Работа приложения будет завершена.\n";
                exit = true;
            }
        }
    } while(!exit);

    system("pause");

}


Вся беда в том, что при выполнении данного кода появляется ошибка:


  Необработанное исключение по адресу 0x0FDECCC8 (msvcp110.dll) в test.exe: 0xC0000005:
нарушение прав доступа при чтении по адресу 0x0067ADE4.


Кадры стека вызовов:



Путем экспериментов установлено, что замена использования вектора на простой динамический
массив никак не влияет на ошибку (т.е. она по-прежнему появляется), а вот замена использования
string на char * приводит к устранению проявления данной ошибки. Погуглив, я пришел
в выводу, что возникает какая-то ошибка в деструкторе string (могу ошибаться). Может
кто-нибудь прояснить ситуацию и дать рекомендации по корректному использованию типа
string в данного рода задачах?  
    


Ответы

Ответ 1



Самый простой способ (как уже упомянул в комментарии @pavel) - использовать текстовый режим работы с файлом и операторы форматированного ввода/вывода (operator<<, operator>>) для чтения/записи std::string из/в потока. Чтение: ifstream fin(path); if (fin) { fin >> u.login >> u.password; } Запись: ofstream fout(path); if (fout) { // Разделители нужны для последующего считывания fout << u.login << " " << u.password << "\n"; } При этом данный подход накладывает некоторые ограничения на строки: как минимум они не должны содержать в себе символы пробельной группы, т.к. такой символ будет расценен как разделитель. Функции istream::read, ostream::write в этом случае не используются вовсе. Причина, по которой они не работают как надо указана в ответе @gbg.

Ответ 2



Запись в файл с помощью fout.write((char*)&u, sizeof(User)); действительно запишет побайтово содержимое структуры User, но т.к. она содержит не POD типы (std::string), то пользы от этого мало, т.к. сами данные ваших строк скорей всего в куче находятся. В вашем случае можно записать логин и пароль в файл (при условии, что в них не используется перевод строки), по одному на строчку, и также читать. Пример: int main() { struct User { std::string login; std::string password; }; { User user{ "login", "password" }; // Пишем логин и пароль в файл в две строчки std::ofstream f("users.txt"); f << user.login << std::endl << user.password; } { User user; // Читаем из файла std::ifstream f("users.txt"); std::getline(f, user.login); std::getline(f, user.password); std::cout << user.login << std::endl << user.password << std::endl; } }

Ответ 3



Так работать не будет. string не является POD - типом, то есть сам объект типа string не содержит данные строки, они размещаются в другом месте (куда их затолкает аллокатор). Правило простое - все, что не является POD нельзя просто так взять и скопировать побайтно. Решение - использовать для хранения данных типы, являющиеся POD struct mu_ugly_pod { char user_name[100500]; char user_last_name[100500]; }; Такая штуковина должна писаться и читаться без проблем. Увы, все удобства, связанные со string в данном случае пропадут.

Комментариев нет:

Отправить комментарий