Выравнивание структуры C++ и векторы STL

У меня есть структура данных прежней версии, это 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);
}

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

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

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

7
задан Nate 21 February 2010 в 23:37
поделиться

5 ответов

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

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

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

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

Edit: Учитывая, что вы не знаете, были ли структуры записаны в формате, ожидаемом компилятором, вам нужно не только читать structs по одному - вам действительно нужно читать элементы в структурах по одному, затем помещать каждый во временную 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;
}
4
ответ дан 7 December 2019 в 03:15
поделиться

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

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

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

2
ответ дан 7 December 2019 в 03:15
поделиться

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

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

1
ответ дан 7 December 2019 в 03:15
поделиться

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

Во-первых, большинство компиляторов имеют расширенные атрибуты или директивы препроцессора, которые позволят вам упаковать структуру в минимальное пространство. Этот вариант потенциально смещает некоторые поля в структуре, что может снизить производительность, но гарантирует, что код будет одинаково расположен на любой машине, для которой вы его создадите. Проверьте свой компилятор на предмет документации по #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
ответ дан 7 December 2019 в 03:15
поделиться

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

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

0
ответ дан 7 December 2019 в 03:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: