Выравнивание структуры C ++ и векторы STL - PullRequest
7 голосов
/ 22 февраля 2010

У меня есть устаревшая структура данных длиной 672 байта. Эти структуры хранятся в файле последовательно, и мне нужно прочитать их в.

Хотя я могу читать их по одному, было бы неплохо сделать это:

// I know in advance how many structs to read in
vector<MyStruct> bunchOfStructs;
bunchOfStructs.resize(numberOfStructs);

ifstream ifs;
ifs.open("file.dat");
if (ifs) {
    ifs.read(&bunchOfStructs[0], sizeof(MyStruct) * numberOfStructs);
}

Это работает, но я думаю, что это работает только потому, что размер структуры данных оказывается равномерно делимым заполнением выравнивания структуры моего компилятора. Я подозреваю, что он сломается на другом компиляторе или платформе.

Альтернативой может быть использование цикла for для чтения в каждой структуре по одному.

Вопрос -> Когда я должен беспокоиться о выравнивании данных? Использует ли динамически распределенная память в векторе заполнение или STL гарантирует, что элементы являются смежными?

Ответы [ 5 ]

4 голосов
/ 22 февраля 2010

Стандарт требует, чтобы вы были в состоянии создать массив типа структуры. Когда вы делаете это, массив должен быть смежным. Это означает, что какой бы размер ни был выделен для структуры, он должен быть таким, который позволяет вам создавать их массив. Чтобы гарантировать это, компилятор может выделить дополнительное пространство внутри структуры, но не может требовать дополнительного пространства между структурами.

Пространство для данных в vector (обычно) выделяется с помощью ::operator new (через класс Allocator), а ::operator new требуется для выделения пространства, которое правильно выровнено для хранения любого типа.

Вы можете предоставить свой собственный Allocator и / или перегрузить ::operator new - но если вы это сделаете, ваша версия по-прежнему должна соответствовать тем же требованиям, поэтому она ничего не изменит в этом отношении.

Другими словами, именно то, что вы хотите, должно работать, если данные в файле были созданы практически так же, как вы пытаетесь их прочитать обратно. Если они были созданы на другом компьютере или с В другом компиляторе (или даже в одном и том же компиляторе с разными флагами) у вас немало потенциальных проблем - вы можете получить различия в порядке следования байтов, заполнении в структуре и т. д.

Редактировать: Учитывая, что вы не знаете, были ли структуры записаны в формате, ожидаемом компилятором, вам нужно не только читать структуры по одному - вам действительно нужно читать элементы в структурах по одному, затем поместить каждый во временную struct и, наконец, добавить заполненную struct в свою коллекцию.

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

struct whatever { 
    int x, y, z;
    char stuff[672-3*sizeof(int)];

    friend std::istream &operator>>(std::istream &is, whatever &w) { 
       is >> w.x >> w.y >> w.z;
       return is.read(w.stuff, sizeof(w.stuff);
    } 
};

int main(int argc, char **argv) { 
    std::vector<whatever> data;

    assert(argc>1);

    std::ifstream infile(argv[1]);

    std::copy(std::istream_iterator<whatever>(infile),
              std::istream_iterator<whatever>(),
              std::back_inserter(data));  
    return 0;
}
2 голосов
/ 22 февраля 2010

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

Во-первых, большинство компиляторов имеют расширенные атрибуты или директивы препроцессора, которые позволят вам упаковать структуру в минимальное пространство. Эта опция потенциально смещает некоторые поля в структуре, что может снизить производительность, но гарантирует, что она будет одинаковой на любой машине, для которой вы ее строите. Проверьте ваш компилятор на предмет документации о #pragma pack(). В GCC вы можете использовать __attribute__((__packed__)).

Во-вторых, вы можете добавить явное заполнение к вашей структуре. Эта опция позволяет вам сохранить свойства производительности исходной структуры, но сделает ее однозначной относительно структуры структуры. Например:

struct s {
    u_int8_t  field1;
    u_int8_t  pad0[3];
    u_int16_t field2;
    u_int8_t  pad1[2];
    u_int32_t field3;
};
2 голосов
/ 22 февраля 2010

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

Лучше не делать никаких предположений при выравнивании структуры.

Чтобы сохранить новые данные в файл, вы можете использовать что-то вроде boost serialization .

1 голос
/ 22 февраля 2010

Больше, чем выравнивание, вы должны беспокоиться о endianness . STL гарантирует, что хранилище в vector такое же, как массив, но целочисленные поля в самой структуре будут храниться в разных форматах, скажем, x86 и RISC.

Что касается выравнивания, Google для #pragma pack(1).

0 голосов
/ 22 февраля 2010

Если вы пишете ОО-код, который требует знания внутренней работы класса, вы делаете это неправильно. Вы не должны предполагать ничего о внутренней работе класса; Вы должны только предполагать, что методы и свойства работают одинаково на любой платформе / компиляторе.

Возможно, вам лучше реализовать класс, который имитирует функциональность вектора (возможно, путем подкласса вектора). Действуя, возможно, как реализация «прокси-шаблона», он может загружать только те структуры, к которым обращался вызывающий. Это позволит вам одновременно решать любые проблемы с порядком байтов. Этот способ должен заставить его работать на любой платформе или компиляторе.

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