Повреждение C ++ Cout - PullRequest
0 голосов
/ 11 июля 2019

Я читаю заголовок файла, используя ifstream. Изменить: меня попросили поставить полную минимальную программу, так что вот она.

#include <iostream>
#include <fstream>


using namespace std;

#pragma pack(push,2)

struct Header
{
    char label[20];
    char st[11];
    char co[7];
    char plusXExtends[9];
    char minusXExtends[9];
    char plusYExtends[9];
};

#pragma pack(pop)

int main(int argc,char* argv[])
{
    string fileName;
    fileName = "test";

    string fileInName = fileName + ".dst";

    ifstream fileIn(fileInName.c_str(), ios_base::binary|ios_base::in);
    if (!fileIn)
    {
       cout << "File Not Found" << endl;
       return 0;
    }

    Header h={};
    if (fileIn.is_open()) {
                cout << "\n" << endl;
                fileIn.read(reinterpret_cast<char *>(&h.label), sizeof(h.label));
                cout << "Label: " << h.label << endl;
                fileIn.read(reinterpret_cast<char *>(&h.st), sizeof(h.st));
                cout << "Stitches: " << h.st << endl;
                fileIn.read(reinterpret_cast<char *>(&h.co), sizeof(h.co));
                cout << "Colour Count: "  << h.co << endl;
                fileIn.read(reinterpret_cast<char *>(&h.plusXExtends),sizeof(h.plusXExtends));
                cout << "Extends: "  << h.plusXExtends << endl;
                fileIn.read(reinterpret_cast<char *>(&h.minusXExtends),sizeof(h.minusXExtends));
                cout << "Extends: "  << h.minusXExtends << endl;
                fileIn.read(reinterpret_cast<char *>(&h.plusYExtends),sizeof(h.plusYExtends));
                cout << "Extends: "  << h.plusYExtends << endl;

// This will output corrupted
                cout << endl << endl;
                cout << "Label: " << h.label << endl;
                cout << "Stitches: " << h.st << endl;
                cout << "Colour Count: "  << h.co << endl;
                cout << "Extends: "  << h.plusXExtends << endl;
                cout << "Extends: "  << h.minusXExtends << endl;
                cout << "Extends: "  << h.plusYExtends << endl;
    }


    fileIn.close();

    cout << "\n";
    //cin.get();
    return 0;
}


ifstream fileIn(fileInName.c_str(), ios_base::binary|ios_base::in);

Затем я использую структуру для хранения элементов заголовка

Фактическая структура длиннее, чем эта. Я сократил его, потому что мне не нужна вся структура для вопроса. Во всяком случае, когда я читаю структуру, я делаю cout, чтобы увидеть, что я получаю. Эта часть в порядке.

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

Вместо того, чтобы видеть метку, швы и счетчик цветов, я получаю странные символы и искаженный вывод. Иногда вы можете увидеть выходные данные h.label с некоторым искажением, но метки - это стежки, которые перезаписаны. Иногда со странными символами, но иногда с текстом из предыдущего кута. Я думаю, что либо данные в структуре будут повреждены, либо выходные данные cout будут повреждены, и я не знаю почему. Чем длиннее заголовок, тем больше проблема становится очевидной. Мне бы очень хотелось сделать все койки в конце заголовка, но если я это сделаю, я вижу большой беспорядок вместо того, что должно выводиться.

Мой вопрос: почему мой кут становится поврежденным?

Corruption

1 Ответ

2 голосов
/ 11 июля 2019

Использование массивов для хранения строк опасно, потому что если вы выделите 20 символов для хранения метки, а длина метки составит 20 символов, тогда не останется места для хранения завершающего символа NUL (0).Как только байты сохранены в массиве, нет ничего, что можно было бы сказать функциям, которые ожидают строки с нулевым символом в конце (например, cout), где конец строки равен.

Ваша метка содержит 20 символов.Этого достаточно для хранения первых 20 букв алфавита: ABCDEFGHIJKLMNOPQRST

Но это не завершенная нулем строка.Это просто массив символов.Фактически, в памяти, байт сразу после T будет первым байтом следующего поля, которое является вашим 11-символьным массивом st.Скажем, эти 11 символов: abcdefghijk.

Теперь байты в памяти выглядят так: ABCDEFGHIJKLMNOPQRSTabcdefghijk

Нет способа определить, где заканчивается label и начинается st,Когда вы передаете указатель на первый байт массива, который по соглашению должен интерпретироваться как строка с нулевым символом в конце, реализация с радостью начнет сканирование до тех пор, пока не найдет нулевой символ конца (0).Что, при последующих повторных использованиях структуры, это не может!Существует серьезный риск переполнения буфера (чтение за концом буфера) и, возможно, даже конец вашего блока виртуальной памяти, что в конечном итоге вызывает нарушение прав доступа / ошибку сегментации.

Когда ваша программа впервые запустилась,память структуры заголовка была все нули (потому что вы инициализировали с помощью {}), и поэтому после чтения поля метки с диска, байты после T были уже нулевыми, поэтому ваш первый cout работал правильно.На st[0] произошел завершающий нулевой символ.Затем вы перезаписываете это, когда читаете поле st с диска.Когда вы вернетесь к выводу label снова, терминатор исчезнет, ​​и некоторые символы st будут интерпретированы как принадлежащие строке.

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

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

Для этого вам понадобятся следующие две структуры:

#pragma pack(push,2)
struct RawHeader  // only for file IO
{
    char label[20];
    char st[11];
    char co[7];
    char plusXExtends[9];
    char minusXExtends[9];
    char plusYExtends[9];
};
#pragma pack(pop)

struct Header  // A much more practical Header struct than the raw one
{
    std::string label;
    std::string st;
    std::string co;
    std::string plusXExtends;
    std::string minusXExtends;
    std::string plusYExtends;
};

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

#include <string>
#include <string.h>

template <int n> std::string arrayToString(const char(&raw)[n]) {
    return std::string(raw, strnlen_s(raw, n));
}

В вашей функции:

Header h;
RawHeader raw;

fileIn.read((char*)&raw, sizeof(raw));
// Now marshal all the fields from the raw header over to the practical header.
h.label         = arrayToString(raw.label);
h.st            = arrayToString(raw.st);
h.st            = arrayToString(raw.st);
h.co            = arrayToString(raw.co);
h.plusXExtends  = arrayToString(raw.plusXExtends);
h.minusXExtends = arrayToString(raw.minusXExtends);
h.plusYExtends  = arrayToString(raw.plusYExtends);

Стоит отметить, что у вас также есть возможность сохранять структуру raw и не копировать ваши rawchar-массивы в std :: strings при чтении файла.Но тогда вы должны быть уверены, что когда вы хотите использовать данные, вы всегда должны вычислять и передавать длины строк функциям, которые будут работать с этими буферами как строковые данные.(Аналогично тому, что делает мой arrayToString помощник.)

...