LINUX.ORG.RU

Выравнивание вложенных структур

 


0

3

Привожу адаптированный пример кода. Сразу скажу, что код не мой. Альтернативное решение найдено. Но понять причины проблемы все же хочется.


#include <iostream>
#include <cstring>

struct SFirstNStruct
{
    unsigned int data;
};


struct SSecondNStruct
{
    unsigned short secondField;
    unsigned short unusedField;

};

struct Data
{
    SFirstNStruct firstField;
    unsigned short secondField;
};


struct Data2
{
    SFirstNStruct firstField;
    SSecondNStruct nestedStruct;
};


int main()
{
    Data2 d2;
    char * s2 = (char*) &(d2.nestedStruct.secondField);
    char * f2 = (char*) &(d2.firstField.data);
    std::cout << s2 - f2 << std::endl;
    
    Data d;
    char * s = (char*) &d.secondField;
    char* f = (char*) &d.firstField.data;
    std::cout << s - f << std::endl;
    
    std::memcpy(&d2, &d, sizeof(Data));
    
   return 0;
}

Вся суть в последнем memcpy. Когда-то это работало, потом перестало. Стал я проводить расследование и выяснил. Data2::SSecondNStruct::secondField и Data::secondField имеют разные офсеты от начала структуры. Написал вот этот тетовый пример, который выводит 4,4. А на реальной системе 8,4. Отсюуда вопрос, что могло вызвать такое поведение? Опции компилятора, прагмы? Пока ничего не в хедерах, ни в опциях компилятора криминального не нашел. gcc 5.4 вроде.

★★★★★

Последнее исправление: Dudraug (всего исправлений: 1)

Может где-то есть что-то такое.

struct __attribute__((aligned(8))) SSecondNStruct;

А на реальной системе 8,4.

Этот же пример выводит или оригинальный код?

xaizek ★★★★★
()
Ответ на: комментарий от xaizek

Может где-то есть что-то такое.

Нет, такого там нет.

Этот же пример выводит или оригинальный код?

В оригинальном. Ну я там такого не выводил, а через gdb на офсеты смотрел.

Dudraug ★★★★★
() автор топика

Если буквально отвечать на вопрос - вероятно размер short. Чтобы не было таких неожиданностей, существуют uint8_t и т.п. Ну и pragma pack.

Elyas ★★★★★
()
Ответ на: комментарий от Elyas

Размер шорт проверял(правда только в gdb) - 2.

Dudraug ★★★★★
() автор топика
Ответ на: комментарий от Elyas

Про uint16_t и так все ясно. Код писался не мной, еще во времена с++98. Проверка размера шорта была чуть ли не первая вещь проверенная мной.

Dudraug ★★★★★
() автор топика

Вся суть в последнем memcpy. Когда-то это работало

С точки зрения стандарта оно никогда не работало.

/thread

anonymous
()
Ответ на: комментарий от Dudraug

По идее достаточно обернуть сиё #pragma pack(push, 1) #pragma pack pop.

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

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)

Так у тебя размер Data2 больше чем Data.

У тебя Data из стурктуры { int, short }, а Data2 из { int, { short, short } }. Что, разве не видно, что размеры отличаются.

Там как переменные будут ложиться на 4 байта, то short будет занимать 4 байта, и при копировании памяти, будет копироваться 4 байта ( наверное ), размер структуры считается от начала и до конца, даже неучтённые байты считаются. Так вот, если посчитать, то int(4) и short(2==4) дадут 8 байт. А int , { short (2 == 4), short (2 == 4) } дадут 8 байт. Там где 2 == 4 написал, означает что хоть short и занимает 2 байта, он будет выровнен по 4 байта.

u0atgKIRznY5
()
Ответ на: комментарий от u0atgKIRznY5

Что-то я не очень правильно наверное обьяснил. Надо в hexdump'е посмотреть. Вот написал код, чтобы посмотреть расположение байтов.

#include <stdio.h>

struct f {
        unsigned int f;
        short b;
        short a;
};

int main ( )
{
        struct f f;
        f.f = 255;
        f.b = 10;
        f.a = 15;
        int length = sizeof ( struct f );

        unsigned char *ptr = (unsigned char * )&f;
        for ( int i = 0; i < length; i++ ) {
                printf ( "%02X:", *ptr );
                ptr++;
        }
        printf ("\n");

}
Вывод FF:00:00:00:0A:00:0F:00 Это содержание памяти как в Data2, а вот что в Data1 FF:00:00:00:0A:00:00:00

Итого, сравним.

FF:00:00:00:0A:00:00:00 - Data
FF:00:00:00:0A:00:0F:00 - Data2
По Data видно, что еще два байта будут заполнять структуру, чтобы размер был кратен 4.

u0atgKIRznY5
()
Ответ на: комментарий от u0atgKIRznY5

Всё таки искусственный интеллект ещё хреново умеют делать

anonymous
()

Баг воспроизводится (8,4), если в SSecondNStruct есть хотя бы один 8-ми байтный скалярный элемент:

struct SSecondNStruct
{
    unsigned short secondField;
    unsigned short unusedField;
    long a; // Это сделает баг - 8,4
};
Нужно отметить, что конец элемента firstField пересекает 8-ми байтную границу. Баг уйдет (4,4), если в SFirstNStruct добавить еще 4 байта или 8-ми байтный скалярный элемент, например,
struct SFirstNStruct
{
    unsigned int data;
    long b; // Это опять замаскирует троянского коня - 4,4
};
Причина: выравнивание структуры такое же, как у её наибольшего по размеру скалярного элемента.

Тема подробно раскрыта в главе "5. Structure alignment and padding" «The Lost Art of C Structure Packing»

fopen ★★
()
Ответ на: комментарий от Dudraug

Но зачем? Тут же по сути UB. Где-то работает где-то нет. Как поведет. Ты же не отлаживаешь конструкции вроде «i += ++i + ++i»? Прост сделай нормальное присваивание значений вместо memcpy.

Aswed ★★★★★
()
Ответ на: комментарий от Aswed

Затем, что тут такая область, что юзание ub - норма, типо оптимизации на всем. Просто это работало на 32битах, а теперь нет.

Dudraug ★★★★★
() автор топика
Ответ на: комментарий от u0atgKIRznY5

Ты несешь чушь. С размерами там все ок и на 32 битах там все работает. На деле я привел не совсем полный пример, но телепаты ниже уже пояснили. Тут дело не в размере структур, а в выравнивание.

Dudraug ★★★★★
() автор топика
Ответ на: комментарий от Dudraug

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

Aswed ★★★★★
()
Ответ на: комментарий от Aswed

Да я сам это не приветствую.

Dudraug ★★★★★
() автор топика
Ответ на: комментарий от Dudraug

Тут дело не в размере структур, а в выравнивание.

Почему чушь, я же написал.

FF:00:00:00:0A:00:00:00 - Data
FF:00:00:00:0A:00:0F:00 - Data2
И написал что должно быть кратно 4. Последние два байта в Data заполняются нулями, чтобы соответствовать 4 байтам. Ты сам попробуй сделай unsigned char * на Data и посмотри как разместятся данные в памяти. Неужели не понятно.

u0atgKIRznY5
()
Ответ на: комментарий от u0atgKIRznY5

Ты все еще несешь чуть, но ок объясню мы имеем две структуры.

{int,short}
{int, {short, short}}

И мы копируем побайтно из первой во вторую кол-во байт равное размеру первой. Значение второго short во вложенной мне до лампочки. (Вообще. Хоть будь там мусор, а не 0. ЗАЧЕМ ты показываешь, что второго шорта в первой нет? Думаешь я не в курсе?) И это работает(хоть и не по стандарту), хоть на 32, хоть на 64 битах. Потому что выравнивание short будет одинаковым в обоих случаях. Но я таки привел не совсем верный пример, но, как я сказал выше, телепаты меня поняли. На деле кейс такой:

{int, short}
{int, {short, short,....,{....,char*}}
В этом случае вложенная структура и первый short в ней имеют требования выравнивания 4 байта на 32битах (поэтому все и работало, ибо что в первой структуре, что во второй этот short имеет одинаковое выравнивание), но на 64 битах short во вложенной структуре имеет выравнивание 64бита, а в простой структуре 16. Поэтому при memcpy они друг на друга не ложатся.

Код изначально кривой и он уже переписан. Но идея топика в том, чтобы ПОНЯТЬ причины такого поведения, а не найти фикс. Инженеры не только фиксят, что не работают, но и пытаются понять почему не работало.

А ты рассказал вещи которые и так всем понятны, но к вопросу отношения не имеют.

Dudraug ★★★★★
() автор топика
Последнее исправление: Dudraug (всего исправлений: 1)
Ответ на: комментарий от Dudraug

Напиши, я опять не так понел, что тебе нужно. Вот я посмотрел код функции memcpy

void *
memcpy (void *dest, const void *src, size_t len)
{
  char *d = dest;
  const char *s = src;
  while (len--)
    *d++ = *s++;
  return dest;
}
Получается что вот эта память
FF:00:00:00:0A:00:00:00 - Data
FF:00:00:00:0A:00:0F:00 - Data2
Последнему short'у будет тоже скопирован 0. Или я не о том пишу? Или тебе надо понять, почему если два short'а, то они пакуются в один int, и почему один short пакуется в один int?

u0atgKIRznY5
()
Ответ на: комментарий от u0atgKIRznY5

Или я не о том пишу?

Не о том. Посмотри внимательно в примере в ОП посте, что и куда копируется и с каким размером. И пример в ОП посте рабочий (хоть и UB по стандарту).

Или тебе надо понять

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

Dudraug ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.