Выравнивание элементов данных C++ и упаковка массивов

StackOverflow https://stackoverflow.com/questions/1676385

Вопрос

Во время проверки кода я наткнулся на код, который определяет простую структуру следующим образом:

class foo {
   unsigned char a;
   unsigned char b;
   unsigned char c;
}

В другом месте определен массив этих объектов:

foo listOfFoos[SOME_NUM];

Позже структуры копируются в буфер в необработанном виде:

memcpy(pBuff,listOfFoos,3*SOME_NUM);

Этот код основан на предположениях о том, что:а.) Размер foo равен 3, заполнение не применяется, и б.) Массив этих объектов упаковывается без заполнения между ними.

Я пробовал это с GNU на двух платформах (RedHat 64b, Solaris 9), и на обеих это работало.

Верны ли приведенные выше предположения?Если нет, то при каких условиях (например.изменение ОС/компилятора) могут ли они потерпеть неудачу?

Это было полезно?

Решение

Массив объектов должен быть непрерывным, поэтому между объектами никогда не должно быть заполнения, хотя дополнение можно добавить в конец объекта (что дает почти тот же эффект).

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

Другие советы

Определенно было бы безопаснее сделать:

sizeof(foo) * SOME_NUM

Если вы копируете свой массив таким образом, вам следует использовать

memcpy(pBuff,listOfFoos,sizeof(listOfFoos));

Это всегда будет работать, если вы выделили pBuff одного и того же размера.Таким образом, вы вообще не делаете никаких предположений относительно заполнения и выравнивания.

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

Я думаю, причина того, что это работает, заключается в том, что все поля в структуре являются символами, которые выравнивают одно.Если есть хотя бы одно поле, которое не выравнивает 1, выравнивание структуры/класса не будет равно 1 (выравнивание будет зависеть от порядка полей и выравнивания).

Давайте посмотрим пример:

#include <stdio.h>
#include <stddef.h>

typedef struct {
    unsigned char a;
    unsigned char b;
    unsigned char c;
} Foo;
typedef struct {
    беззнаковый короткий i;
    unsigned char  a;
    unsigned char  b;
    unsigned char  c;
} Bar;
typedef struct { Foo F[5]; } F_B;
typedef struct { Bar B[5]; } B_F;


#define ALIGNMENT_OF(t) offsetof( struct { char x; t test; }, test )

int main(void) {
    printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo));
    printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar));
    printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B));
    printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F));
}

При выполнении результат:

Foo:: Size: 3; Alignment: 1
Bar:: Size: 6; Alignment: 2
F_B:: Size: 15; Alignment: 1
B_F:: Size: 30; Alignment: 2

Вы можете видеть, что Bar и F_B имеют выравнивание 2, поэтому его поле i будет правильно выровнено.Вы также можете видеть, что размер панели равен 6 а не 5.Аналогично, размер B_F (5 баров) равен 30, а не 25.

Итак, если у вас жесткий код вместо sizeof(...), здесь у вас возникнут проблемы.

Надеюсь это поможет.

Все сводится к выравниванию памяти.Типичные 32-битные машины читают или записывают 4 байта памяти за попытку.Эта структура защищена от проблем, поскольку она легко помещается под эти 4 байта без каких-либо запутанных проблем с заполнением.

Теперь, если бы структура была такой:

class foo {
   unsigned char a;
   unsigned char b;
   unsigned char c;
   unsigned int i;
   unsigned int j;
}

Логика ваших коллег, вероятно, привела бы к

memcpy(pBuff,listOfFoos,11*SOME_NUM);

(3 символа = 3 байта, 2 целых = 2*4 байта, поэтому 3 + 8)

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

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

static_assert(sizeof(foo) <= 3);

// Macro for "static-assert" (only usefull on compile-time constant expressions)
#define static_assert(exp)           static_assert_II(exp, __LINE__)
// Macro used by static_assert macro (don't use directly)
#define static_assert_II(exp, line)  static_assert_III(exp, line)
// Macro used by static_assert macro (don't use directly)
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)}

Я бы перестраховался и заменил магическое число 3 на sizeof(foo) Я считаю.

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

И пытаться отследить такую ​​ошибку — настоящая боль!

Как уже говорили другие, использование sizeof(foo) является более безопасным выбором.Некоторые компиляторы (особенно эзотерические в мире встраиваемых систем) добавляют к классам 4-байтовый заголовок.Другие могут делать причудливые трюки с выравниванием памяти, в зависимости от настроек вашего компилятора.

Для массовой платформы, вероятно, все в порядке, но это не гарантия.

Проблема с sizeof() все еще может возникнуть при передаче данных между двумя компьютерами.В одном из них код может скомпилироваться с заполнением, а в другом — без, и в этом случае sizeof() даст разные результаты.Если данные массива передаются с одного компьютера на другой, они будут неправильно интерпретированы, поскольку элементы массива не будут найдены там, где ожидалось.Одно из решений — по возможности использовать #pragma package(1), но для массивов этого может быть недостаточно.Лучше всего предвидеть проблему и использовать заполнение, кратное 8 байтам на элемент массива.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top