выравнивание памяти — Как определяется размер класса C ++?

Резюме: Как компилятор статически определяет размер класса C ++ во время компиляции?

подробности:

Я пытаюсь понять, каковы правила определения объема памяти, используемого классом, а также того, как память будет выровнена.

Например, следующий код объявляет 4 класса. Первые 2 — это 16 байтов. Но значение 3 составляет 48 байтов, хотя оно содержит те же элементы данных, что и первый 2. Хотя четвертый класс имеет те же элементы данных, что и третий, только в другом порядке, но это 32 байта.

#include <xmmintrin.h>
#include <stdio.h>

class TestClass1 {
__m128i vect;
};

class TestClass2 {
char buf[8];
char buf2[8];
};

class TestClass3 {
char buf[8];
__m128i vect;
char buf2[8];
};

class TestClass4 {
char buf[8];
char buf2[8];
__m128i vect;
};TestClass1 *ptr1;
TestClass2 *ptr2;
TestClass3 *ptr3;
TestClass4 *ptr4;
int main() {
ptr1 = new TestClass1();
ptr2 = new TestClass2();
ptr3 = new TestClass3();
ptr4 = new TestClass4();
printf("sizeof TestClass1 is: %lu\t TestClass2 is: %lu\t TestClass3 is: %lu\t TestClass4 is: %lu\n", sizeof(*ptr1), sizeof(*ptr2), sizeof(*ptr3), sizeof(*ptr4));
return 0;
}

Я знаю, что ответ имеет отношение к выравниванию данных членов класса. Но я пытаюсь точно понять, что это за правила и как они применяются на этапах компиляции, потому что у меня есть класс, который имеет __m128i элемент данных, но элемент данных не выровнен по 16 байтам, и это приводит к segfault, когда компилятор генерирует код, используя movaps для доступа к данным.

10

Решение

Для POD (простые старые данные) правила, как правило:

  • Каждый элемент в структуре имеет некоторый размер s и некоторые требования к выравниванию a.
  • Компилятор запускается с размером S, установленным в ноль, и требованием выравнивания A, установленным в единицу (байт).
  • Компилятор обрабатывает каждый элемент в структуре в следующем порядке:
    1. Рассмотрим требование выравнивания члена a. Если S в данный момент не кратно a, то добавьте достаточно байтов S, чтобы оно было кратным a. Это определяет, куда член пойдет; он будет идти по смещению S от начала структуры (для текущего значения S).
    2. Установите A для наименьшего общего кратного A и a.
    3. Добавьте s к S, чтобы выделить место для члена.
  • Когда описанный выше процесс будет выполнен для каждого члена, рассмотрите требование выравнивания структуры A. Если S в настоящее время не кратно A, то достаточно просто добавить S, чтобы оно было кратным A.

Размер структуры — это значение S, когда вышеупомянутое сделано.

Дополнительно:

  • Если какой-либо элемент является массивом, его размер — это число элементов, умноженное на размер каждого элемента, а его требование выравнивания — это требование выравнивания элемента.
  • Если какой-либо элемент является структурой, его размер и требования к выравниванию рассчитываются, как указано выше.
  • Если какой-либо член является профсоюзом:
    1. Установите S для размера наибольшего члена.
    2. Установите A для наименьшего общего кратного из всех выравниваний членов.
    3. Если S не кратно A, добавьте к S достаточно, чтобы сделать его кратным A.

Считай свой TestClass3:

  • S начинается с 0, а A начинается с 1.
  • char buf[8] требуется 8 байтов и выравнивание 1, поэтому S увеличивается на 8–8, а A остается 1.
  • __m128i vect требуется 16 байтов и выравнивание 16. Во-первых, S нужно увеличить до 16, чтобы получить правильное выравнивание. Тогда A нужно увеличить до 16. Затем S нужно увеличить на 16, чтобы освободить место для vect, поэтому S теперь 32.
  • char buf2[8] требует 8 байтов и выравнивания 1, поэтому S увеличивается на 8 до 24, а A остается 16.
  • В конце S равно 24, что не кратно A (16), поэтому S нужно увеличить на 8 до 32.

Поэтому размер TestClass3 составляет 32 байта.

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

13

Другие решения

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

Однако поведение, которое вы наблюдали, довольно типично. Компилятор пытается выровнять элементы так, чтобы каждый из них начинался с кратного их размера. В случае TestClass3один из членов имеет тип __m128i а также sizeof(__m128i) == 16, Поэтому он попытается выровнять этот элемент, чтобы он начинался с байта, кратного 16. Первый член имеет тип char[8] так занимает до 8 байт. Если компилятор должен был разместить _m128i Объект сразу после этого первого члена будет начинаться с позиции 8, которая не кратна 16:

0               8               16              24              32              48
┌───────────────┬───────────────────────────────┬───────────────┬┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│    char[8]    │            __m128i            │    char[8]    │
└───────────────┴───────────────────────────────┴───────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄

Так что вместо этого он предпочитает делать это:

0               8               16              24              32              48
┌───────────────┬┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┬───────────────────────────────┬───────────────┐┄┄┄
│    char[8]    │               │           __m128i             │    char[8]    │
└───────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┴───────────────────────────────┴───────────────┘┄┄┄

Это дает ему размер 48 байтов.

Когда вы переупорядочиваете участников, чтобы получить TestClass4 макет становится:

0               8               16              24              32              48
┌───────────────┬───────────────┬───────────────────────────────┬┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│    char[8]    │    char[8]    │           __m128i             │
└───────────────┴───────────────┴───────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄

Теперь все правильно выровнено — массивы имеют смещения, кратные 1 (размер их элементов) и __m128i объект имеет смещение, кратное 16 — и общий размер составляет 32 байта.

Причина, по которой компилятор не просто выполняет эту перестановку, заключается в том, что стандарт определяет, что более поздние члены класса должны иметь более высокие адреса:

Нестатические члены данных (не объединяющего) класса с одинаковым контролем доступа (раздел 11) распределяются так, чтобы более поздние члены имели более высокие адреса в объекте класса.

7

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

Для GCC это Itanium ABI.

(К сожалению, это больше не является общедоступным, хотя я нашел зеркало.)

0

если вы хотите обеспечить соответствие, вы должны использовать «pragma pack (1)» в вашем h файле
посмотрите на этот пост:
http://tedlogan.com/techblog2.html

-1
По вопросам рекламы [email protected]