Чтение файла в структуру (C ++) - PullRequest
5 голосов
/ 24 марта 2012

Я пытаюсь прочитать данные из двоичного файла и поместить его в структуру. Первые несколько байтов data.bin:

03 56 04 FF FF FF ...

И моя реализация:

#include <iostream>
#include <fstream>

int main()
{
    struct header {
        unsigned char type;
        unsigned short size;
    } fileHeader;

    std::ifstream file ("data.bin", std::ios::binary);
    file.read ((char*) &fileHeader, sizeof header);

    std::cout << "type: " << (int)fileHeader.type;
    std::cout << ", size: " << fileHeader.size << std::endl;

}

Вывод, который я ожидал, равен type: 3, size: 1110, но по какой-то причине это type: 3, size: 65284, так что в основном второй байт в файле пропускается. Что здесь происходит?

Ответы [ 4 ]

7 голосов
/ 24 марта 2012

На самом деле поведение определяется реализацией.Что в действительности происходит в вашем случае, вероятно, так это заполнение в 1 байт после type члена структуры, затем после второго элемента size.Я основал этот аргумент после просмотра вывода.

Вот ваши входные байты:

03 56 04 FF FF FF

первый байт 03 переходит к первому байту структуры, которая равна typeи вы видите 3 как вывод.Затем следующий байт 56 переходит ко второму байту, который является дополнением, следовательно, игнорируется, затем следующие два байта 04 FF переходят к следующим двум байтам структуры, которая имеет размер size (размер 2 байтов),На машине с прямым порядком байтов 04 FF интерпретируется как 0xFF04, что является ничем иным, как 66284, которое вы получаете в качестве вывода.

И вам нужна в основном компактная структура, чтобы сжать отступы.Используйте #pragma пакет.Но такая структура будет медленной по сравнению с обычной структурой.Лучшим вариантом является заполнение структуры вручную следующим образом:

char bytes[3];
std::ifstream file ("data.bin", std::ios::binary);
file.read (bytes, sizeof bytes); //read first 3 bytes

//then manually fill the header
fileHeader.type = bytes[0];
fileHeader.size = ((unsigned short) bytes[2] << 8) | bytes[1]; 

Другой способ написать последнюю строку заключается в следующем:

fileHeader.size = *reinterpret_cast<unsigned short*>(bytes+1); 

Но это зависит от реализации, так как это зависит отпорядковый номер машины.На машине с прямым порядком байтов это, скорее всего, будет работать.

Дружественный подход будет следующим (определяется реализацией):

std::ifstream file ("data.bin", std::ios::binary);
file.read (&fileHeader.type, sizeof fileHeader.type);
file.read (reinterpret_cast<char*>(&fileHeader.size), sizeof fileHeader.size);

Но, опять же, последняя строка зависит от порядкового номера машины.

1 голос
/ 24 марта 2012

Ну, это может быть struct padding.Чтобы структуры работали быстро на современных архитектурах, некоторые компиляторы помещают туда отступы, чтобы выровнять их по границам 4 или 8 байт.

Вы можете переопределить это с помощью прагмы или настройки компилятора.например.Visual studio его / Zp

Если бы это происходило, вы бы увидели значение 56 в первом символе, затем оно прочитало бы следующие n байтов в дополнение, а затемпрочитайте следующие 2 в коротком.Если 2-й байт был потерян в качестве заполнения, то следующие 2 байта считываются в короткий.И так как короткое замыкание теперь содержит данные '04 FF ', это (с прямым порядком байтов) равно 0xff04, что составляет 65284.

0 голосов
/ 24 марта 2012

Вы можете использовать директиву компилятора #pragma pack, чтобы переопределить проблему заполнения:

#pragma pack(push)
#pragma pack(1)
struct header {
    unsigned char type;
    unsigned short size;
} fileHeader;
#pragma pack(pop)
0 голосов
/ 24 марта 2012

Панель компиляторов структурируется до байтов, кратных 2 или 4, чтобы облегчить доступ к ним в машинном коде.Я не стал бы использовать #pragma pack, если бы это не было действительно необходимо, и это обычно применяется, только когда вы работаете на очень низком уровне (например, уровне прошивки). Статья в Википедии об этом.

Это происходит потому, что у микропроцессоров есть определенные операции для доступа к памяти по адресам, кратным четырем или двум, и это облегчает создание исходного кода, он использует памятьболее эффективно, а иногда код немного быстрее.Конечно, есть способы остановить это поведение, например, директива pragma pack, но они зависят от компиляции.Но переопределять значения по умолчанию для компилятора - плохая идея, у парней с компиляторами была очень веская причина заставить его так себя вести.

Для меня лучшим решением было бы решить это с помощью чистого C, которыйэто очень, очень просто, и будет следовать хорошей практике программирования, а именно: никогда не полагаться на то, что компилятор делает с вашими данными на низком уровне.

Я знаю, что простое выполнение #pragma pack (1) сексуально, просто и дает нам всем ощущение, что мы имеем дело и понимаем непосредственно, что происходит в кишечнике компьютера, и это превращает каждого настоящего программиста, но лучшим решением всегда является то, которое реализовано на языке, который вы используете.Это легче понять и, следовательно, легче поддерживать;это поведение по умолчанию, поэтому оно должно работать везде, и, в данном конкретном случае, решение C действительно простое и понятное: просто прочитайте свой атрибут struct по атрибуту, например так:

void readStruct(header &h, std::ifstream file)
{
    file.read((char*) &h.type, sizeof(char));
    file.read((char *) &h.size, sizeof(short));
}

(это будет работатьконечно, если вы определяете структуру глобально)

Еще лучше, так как вы работаете с C ++, было бы определить метод-член, который сделает это чтение для вас, а позже просто вызовите myObject.readData(file).Вы видите красоту и простоту?

Легче читать, поддерживать, компилировать, приводит к более быстрому и оптимизированному коду, по умолчанию.

Я обычно не люблю связываться сдирективы #pragma, если я не совсем уверен в том, что я делаю.Последствия могут быть удивительными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...