Подтвердить что ты не робот

Выравнивание элементов данных С++ и массива

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

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

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

foo listOfFoos[SOME_NUM];

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

memcpy(pBuff,listOfFoos,3*SOME_NUM);

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

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

Правильны ли предположения? Если нет, при каких условиях (например, изменение в OS/компиляторе) они могут не работать?

4b9b3361

Ответ 1

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

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

Ответ 2

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

sizeof(foo) * SOME_NUM

Ответ 3

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

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

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

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

Ответ 4

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

Посмотрим на пример:

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

typedef struct {
    unsigned char a;
    unsigned char b;
    unsigned char c;
} Foo;
typedef struct {
    unsigned short 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, так что его поле я будет правильно выровнено. Вы также можете видеть, что размер бара 6, а не 5. Аналогично, размер B_F (5 баров) равен 30, а не 25.

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

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

Ответ 5

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

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

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

Ответ 6

Все сводится к выравниванию памяти. Типичные 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 char= 3 байта, 2 ints = 2 * 4 байта, поэтому 3 + 8)

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

Ответ 7

В ситуациях, когда такие вещи используются, и я не могу этого избежать, я пытаюсь сделать перерыв компиляции, когда презумпции больше не сохраняются. Я использую что-то вроде следующего (или 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)}

Ответ 8

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

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

Ответ 9

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