Чтение блоков буфера двоичного файла в разные типы

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

struct myStruct {
    std::string mystring; // is 40 bytes long
    uint myint1; // is 4 bytes long
};

typedef unsigned char byte;

byte *filedata = ReadFile(filename); // reads file into memory, closes the file
myStruct aStruct;
aStruct.mystring = filedata.????

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

Достаточно обширный (IMO) поиск не дал ничего подходящего, есть идеи? Я готов изменить тип хранилища (например, на std::vector), если вы считаете это необходимым.


person LordAro    schedule 29.01.2013    source источник
comment
Если у вас есть байт* в начале данных в памяти, почему бы вам просто не пройтись по длине, копируя данные по ходу? Пока вы увеличиваете свой указатель и знаете, как далеко идти, все хорошо и легко.   -  person StarPilot    schedule 30.01.2013
comment
но как я могу получить из этого определенную длину, а не только байт в текущей позиции указателя?   -  person LordAro    schedule 30.01.2013
comment
@LordAro звучало так, как будто вы знали, что длина строки составляет 40 байтов, за которой следует 4-байтовое целое число. Также остерегайтесь порядка байтов, делающего это таким образом.   -  person lcs    schedule 30.01.2013
comment
фактический код: строка[40], строка[4], строка[12], uint, uint, но это не имеет большого значения. Я также знаю о страшном порядке байтов. Однако это все еще не решает проблему, которая, я думаю, заключается в следующем: как я могу получить доступ к более чем 1 байту при доступе следующим образом: filedata[pointer] ??   -  person LordAro    schedule 30.01.2013
comment
Основы магии указателей. Если ваше поле данных имеет фиксированную длину, то это просто c кодированием. установите текущий указатель, равный вашей начальной точке. Скопируйте его на 40 символов (длина вашего набора mystring). Переместите текущий указатель на 40. установите myint1 = (int)*currentcounter. Увеличить текущий указатель на sizeof(int). если вы достигли конца ваших данных, остановитесь. если нет, повторяйте копирование строки и настройку int, пока не сделаете это. Примечание. Это работает только со строками фиксированной длины. Если у вас переменная длина, вы должны сериализовать в память так же, как данные были сериализованы на диск/в хранилище.   -  person StarPilot    schedule 30.01.2013
comment
Разыменование и приведение. Это секрет компьютеров. Это все биты и байты. Мы используем приведение, чтобы волшебным образом изменить байты на UINT или DATE или что-то еще, что необходимо.   -  person StarPilot    schedule 30.01.2013
comment
Просто из любопытства, почему вы не сериализуете его непосредственно в свои структуры данных в памяти? Зачем сначала загружать его в память, а затем сериализовать в пригодные для использования данные?   -  person StarPilot    schedule 30.01.2013
comment
Вам нужно уважать указатель байта и хранить любые данные, которые вы используете. struct1.string = *(bytePtr + sizeof(char)*40); struct1.int1 = *(bytePtr + (sizeof(char)*40 + sizeof(int));. Опять же, остерегайтесь порядка следования байтов, вам гораздо лучше сериализовать свои данные.   -  person lcs    schedule 30.01.2013
comment
И небольшая ошибка при преобразовании вашего currentpointer в строку, а затем выполнении функции копирования строки, чтобы создать правильную строку для вашего поля строковых данных --- используйте функцию копирования строки фиксированной длины, чтобы она не вечно искала нуль терминатор.   -  person StarPilot    schedule 30.01.2013
comment
Взгляните на boost::serialize, а также поищите в Интернете сериализацию c++.   -  person Thomas Matthews    schedule 30.01.2013


Ответы (2)


Если вы не собираетесь использовать библиотеку сериализации, я предлагаю добавить поддержку сериализации в каждый класс:

struct My_Struct
{
    std::string my_string;
    unsigned int my_int;
    void Load_From_Buffer(unsigned char const *& p_buffer)
    {
        my_string = std::string(p_buffer);
        p_buffer += my_string.length() + 1; // +1 to account for the terminating nul character.
        my_int = *((unsigned int *) p_buffer);
        p_buffer += sizeof(my_int);
    }
};

unsigned char * const buffer = ReadFile(filename);
unsigned char * p_buffer = buffer;
My_Struct my_variable;
my_variable.Load_From_Buffer(p_buffer);

Некоторые другие полезные методы интерфейса:

unsigned int Size_On_Stream(void) const; // Returns the size the object would occupy in the stream.
void Store_To_Buffer(unsigned char *& p_buffer); // Stores object to buffer, increments pointer.

С помощью шаблонов вы можете расширить функциональность сериализации:

void Load_From_Buffer(std::string& s, unsigned char *& p_buffer)
{
    s = std::string((char *)p_buffer);
    p_buffer += s.length() + 1;
}

void template<classtype T> Load_From_Buffer(T& object, unsigned char *& p_buffer)
{
  object.Load_From_Buffer(p_buffer);
}

Редактировать 1: причина не писать структуру напрямую

В C и C++ размер структуры может не равняться сумме размеров ее элементов.
Компиляторам разрешено вставлять заполнение или неиспользуемые пространство между элементами, чтобы элементы были выровнены по адресу.

Например, 32-разрядный процессор предпочитает получать данные с границами в 4 байта. Наличие одного char в структуре, за которым следует int, приведет к тому, что int будет иметь относительный адрес 1, который не кратен 4. Компилятор дополнит структуру так, чтобы int выровнялось по относительному адресу 4. .

Структуры могут содержать указатели или элементы, содержащие указатели.
Например, тип std::string может иметь размер 40, хотя строка может содержать 3 символа или 300. Она содержит указатель на фактический данные.

Endianess.
С многобайтовыми целыми числами некоторые процессоры, такие как старший байт (MSB), также известный как Big Endian, сначала (то, как люди читают числа) или наименее значимый байт, также известный как Little Endian. Для чтения в формате Little Endian требуется меньше схем, чем в формате Big Endian.

Редактировать 2: Варианты записей

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

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

Другой метод заключается в выводе количества элементов, а затем предметов. Итак, если бы у меня было 6 чисел, 0, 1, 2, 3, 4, 5, вывод был бы таким:
6 // Количество элементов
0
1
2
3
4
5

В приведенном выше методе Load_From_Buffer я бы создал временный объект для хранения количества, записал его, а затем проследил за каждым элементом из контейнера.

person Thomas Matthews    schedule 29.01.2013
comment
это выглядит хорошо, 1 вопрос: зачем нужен буфер и p_buffer? (Я не очень хорошо разбираюсь в С++: L) РЕДАКТИРОВАТЬ: игнорируйте это, это указатель на массив - person LordAro; 30.01.2013
comment
Если вы передадите buffer методам, методы будут увеличивать его, и вы потеряете начало исходного буфера. Всегда лучше играть с дополнительным указателем на буфер. - person Thomas Matthews; 30.01.2013
comment
@LordAro: Напоминание: если вам нравится ответ, нажмите на галочку. - person Thomas Matthews; 30.01.2013
comment
Готово, спасибо :) О: В C и C++ размер структуры может не равняться сумме размеров ее членов. ‹-- Действительно, я уже сталкивался с этой "проблемой" :) - person LordAro; 30.01.2013
comment
Из интереса (если вы все еще там), можно ли этот метод также применить к векторам? У меня возникли проблемы с определением размера массива... - person LordAro; 30.01.2013
comment
Я действительно озадачен, когда люди не могут найти размер созданного ими массива, в конце концов, вам нужно указать количество элементов, прежде чем его можно будет создать. Чтобы вывести массив, вам нужно будет записать количество элементов как один элемент, за которым следуют все элементы. - person Thomas Matthews; 30.01.2013
comment
Действительно могу, но это включает параметры входа/выхода - person LordAro; 31.01.2013

Вы можете перегрузить оператор вывода std::ostream и оператор ввода std::istream для своей структуры примерно так:

struct Record {
    std::string name;
    int value;
};

std::istream& operator>>(std::istream& in, Record& record) {
    char name[40] = { 0 };
    int32_t value(0);
    in.read(name, 40);
    in.read(reinterpret_cast<char*>(&value), 4);
    record.name.assign(name, 40);
    record.value = value;
    return in;
}

std::ostream& operator<<(std::ostream& out, const Record& record) {
    std::string name(record.name);
    name.resize(40, '\0');
    out.write(name.c_str(), 40);
    out.write(reinterpret_cast<const char*>(&record.value), 4);
    return out;
}

int main(int argc, char **argv) {
    const char* filename("records");
    Record r[] = {{"zero", 0 }, {"one", 1 }, {"two", 2}};
    int n(sizeof(r)/sizeof(r[0]));

    std::ofstream out(filename, std::ios::binary);
    for (int i = 0; i < n; ++i) {
        out << r[i];
    }
    out.close();

    std::ifstream in(filename, std::ios::binary);
    std::vector<Record> rIn;
    Record record;
    while (in >> record) {
        rIn.push_back(record);
    }
    for (std::vector<Record>::iterator i = rIn.begin(); i != rIn.end(); ++i){
        std::cout << "name: " << i->name << ", value: " << i->value
                  << std::endl;
    }
    return 0;
}
person Shaun Marko    schedule 29.01.2013