Как определяется размер classа C ++?
Резюме : Как компилятор статически определяет размер classа C ++ во время компиляции?
Детали :
Я пытаюсь понять, какие правила предназначены для определения того, сколько памяти будет использовать class, а также как будет выровняться память.
- Производительность LINQ Any vs FirstOrDefault! = Null
- Почему некоторые типы не имеют буквенных модификаторов
- Выбор значений атрибутов с помощью html Agility Pack
- проверка имени пользователя или электронной почты пользователя уже существует
- Добавление строки C
Например, следующий код объявляет 4 classа. Первые 2 равны 16 байт. Но 3 – 48 байтов, хотя в нем есть те же самые элементы данных, что и первые 2. В то время как четвертый class имеет те же самые элементы данных, что и третий, только в другом порядке, но это 32 байта.
#include #include 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; }
Я знаю, что ответ имеет отношение к выравниванию членов данных classа. Но я пытаюсь понять, что именно эти правила и как они применяются во время шагов компиляции, потому что у меня есть class, у которого есть __m128i
данных __m128i
, но элемент данных не выровнен по 16 байт, и это приводит к segfault, когда компилятор генерирует код, используя movaps
для доступа к данным.
- Возможно ли «ожидание возврата доходности DoSomethingAsync ()»
- Как использовать SQLite в многопоточном приложении?
- Каков самый быстрый способ конвертировать float в int на x86
- C #: должны ли переменные объектов присваиваться null?
- Функции C # 6.0, не работающие с Visual Studio 2015
- Создание битовой карты из массива байтов данных пикселей
- Прикрепить отладчик к экземпляру IIS
- Как разбить строковый литерал на несколько строк в C / Objective-C?
Для POD (простые старые данные) правило обычно:
- Каждый член в структуре имеет некоторые размеры s и некоторые требования к выравниванию a.
- Компилятор начинается с размера S, установленного на ноль, и требования к выравниванию A, установленного в один (байт).
- Компилятор обрабатывает каждый элемент в структуре по порядку:
- Рассмотрим требование выравнивания члена a. Если S в настоящее время не кратно a, тогда добавьте достаточно байтов S, чтобы он был кратным a. Это определяет, куда будет идти член; он будет идти со смещением S от начала структуры (для текущего значения S).
- Задайте A наименьшим общим кратным A и a.
- Добавьте s в S, чтобы выделить место для члена.
- Когда вышеописанный процесс выполняется для каждого члена, рассмотрим требование выравнивания структуры A. Если S в настоящее время не кратно A, добавьте достаточно до S, чтобы он был кратным A.
Размер структуры – это значение S, когда это сделано.
Дополнительно:
- Если какой-либо элемент является массивом, его размер представляет собой число элементов, умноженное на размер каждого элемента, а его требование к выравниванию – требование выравнивания элемента.
- Если какой-либо элемент является структурой, его размер и требования к выравниванию рассчитываются, как указано выше.
- Если какой-либо член является профсоюзом:
- Установите S в размер самого большого члена.
- Задайте A наименьшим общим кратным выравниваний всех членов.
- Если S не кратно A, добавьте достаточно для S, чтобы сделать его кратным A.
Рассмотрим ваш TestClass3
:
- S начинается с 0 и начинается с 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 увеличивается наchar buf2[8]
, а A остается 16. - В конце S равно 24, что не кратно A (16), поэтому S должно быть увеличено на 8-32.
Таким образом, размер TestClass3
составляет 32 байта.
Для элементарных типов ( int
, double
и т. Д.) Требования к выравниванию зависят от реализации и во многом определяются аппаратным обеспечением. На многих процессорах быстрее загружать и хранить данные, когда они имеют определенное выравнивание (обычно, когда его адрес в памяти кратен его размеру). Помимо этого, вышеприведенные правила в значительной степени зависят от логики; они помещают каждый член, где он должен удовлетворять требованиям выравнивания, не используя больше места, чем необходимо.
Это зависит только от компилятора, как определяется размер classа. Компилятор обычно компилируется для соответствия определенному двоичному интерфейсу приложения, зависящему от платформы.
Однако поведение, которое вы наблюдали, довольно типично. Компилятор пытается выровнять элементы так, чтобы каждый из них начинал с кратного их размера. В случае 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
порядок членов для получения TestClass4
макет становится следующим:
0 8 16 24 32 48 ┌───────────────┬───────────────┬───────────────────────────────┬┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ char[8] │ char[8] │ __m128i │ └───────────────┴───────────────┴───────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
Теперь все правильно выровнено – массивы находятся в смещениях, которые кратно 1 (размер их элементов), а объект __m128i
имеет смещение, кратное 16, а общий размер 32 байта.
Причина, по которой компилятор не просто выполняет эту перегруппировку, состоит в том, что стандарт указывает, что более поздние члены classа должны иметь более высокие адреса:
Нестационарные члены данных (non-union) classа с одним и тем же контролем доступа (раздел 11) распределяются так, что более поздние члены имеют более высокие адреса в объекте classа.
Правила устанавливаются в соответствии с используемой спецификацией Application Binary Interface, которая обеспечивает совместимость между различными системами для программ, использующих этот интерфейс.
Для GCC это Itanium ABI.
(К сожалению, он больше не доступен для публики, хотя я нашел зеркало ).
если вы хотите обеспечить привязку, вы должны использовать «pragma pack (1)» в своем h-файле, посмотрите этот пост: http://tedlogan.com/techblog2.html