Знающие люди, просветите пожалуйста.
Как выравниваются в памяти сложные объекты ?
Может где-нибудь это уже доступно изложено ? Тогда ткните URLем плиз.
Edit: грамматика...
что ты имеешь ввиду?
Чорт, такое ощущение, что куда-то пропадают темы форума. Буквально тройку месяцев назал писал огромнейший пост про выравнивание структур. Не могу найти в поиске. Доберусь до дома, попробую посмотреть повнимательнее. Пока что (если с английским в порядке), можно погуглить structure alignment или structure padding.
К примеру:
есть некий класс с кучей внутренних данных - char-ы, long-и, float-ы, vector-ы, char[]-ы, и всё такое прочее.
Как выполняется выравнивание всего объекта целиком ?
Зависит ли это от того, какой тип имеет первый "внутренний" мембер ?
Зависит ли выравнивание всего объекта от #pragma pack ?
Может объект выравнивается на размер строчки кэша ?
В общем, интересует вся инфа из этой области.
dDIMA
> можно погуглить structure alignment или structure padding.
Пробовал, но попадал лишь на всякую форумную переписку.
Кое-какие вопросы там прояснены, но ссылок на стандарт нигде нет.
Считается ли всё это platform- и implementation-depended ?
Nikopol
хм... вопрос конечно интересный...
afaik implementation-dependent.
работает так - если следующий элемент данных структуры/класса при занесении в неё пересекает границу выравнивания (начальный оффсет считается 0), то он переносится на эту границу.
по поводу всего объекта целиком - вопрос интересный. У MS даже свой (implementation-specific) модификатор "__declspec( align( # ) ) declarator" есть на эту тему. И даже он не решает вопроса по поводу стековых (локальных ф-ии/методу) переменных.
Что творится в new/delete тоже загадка, потому как:
> To create an array whose base is properly aligned, use _aligned_malloc, or write your own allocator. Note that normal allocators, such as malloc, C++ operator new, and the Win32 allocators return memory that will most likely not be sufficiently aligned for __declspec(align(#)) structures or arrays of structures.
По ходу компиляторы на платформы где выравнивание обязательно сами всё котролируют, а компиляторы под платформы где выравнивание необязательно не контролируют ничего вовсе....
Nikopol
Так и не нашел текста, надеюсь, что хоть этот пост не пропадет даром :). Wat, если хочешь, можно занести это в типсы или куда либо еще.
Итак.
1. Для простых структур и классов (без наследования)
- каждый примитивный элемент структуры выравнивается на адрес, кратный либо своему sizeof(), либо своему модификатору align. Т.е. char запишется вплотную к предыдущему элементу, DWORD выровняется на адрес, кратный 4, double - на адрес кратный 8, declspec(aligned 8) short - тоже на 8. Это правило действует для всех типов, размер которых не превышает default structure align (по умолчанию в VC - 8 байт).
- default structure align для интеловского компилятора можно выставить другим. Например, поставить в опциях компилятора или в #pragma pack значение 1. Тогда все поля будут записаны вплотную. Но может сильно упасть перформанс при считывании переменных по некратным адресам. Компиляторы для других процессоров могут не иметь указанной опции, если процессор не поддерживает считывание по некратным адресам.
- в том случае, если класс имеет виртуальные функции, в начало класса записываются 4 байта указателя на vtbl, далее все пишется в соответствии с указанными выше правилами
- в конце структуры добавляется padding - выравнивание структуры таким образом, чтобы ее суммарный sizeof() был кратен максимальному sizeof() ее полей. Т.е. если в структуре встречается void*, ее общий sizeof() должен быть кратен 4, если double() - то 8, если declspec(16), то 16. Это делается для того, чтобы в случае объявления массива из нескольких элементов адреса последующих элементов массива также были правильно выровнены.
- в том случае, если структура встречается как member другой структуры, она выравнивается на адрес, кратный максимальному из sizeof() ее полей.
2. Для классов с одиночным невиртуальным наследованием
- в начале пишется parent-структура, затем - наследованная структура. Выравнивания полей ведутся также, как и в предыдущем пункте (когда структура - member другой структуры).
3. Для классов с множественным невиртуальным наследованием
- структуры пишутся в порядке наследования от последней к первой. В ряде случаев (в зависимости от наличия виртуальных функций) компилятор может переставлять структуры местами для уменьшения общего количества vtbl
- 4-байтовый указатель на vtbl автоматически вставляется в начало каждого из базовых классов, первый vtbl автоматом является общим для самого "правого" в списке наследованных классов и результирующего класса
4. Для классов с множественным виртуальным наследованием
- все плохо
Все вышесказанное полностью compiler-specific и platform-specific
Декларация #pragma pack и declspec(align) - compiler-specific
Попытка выравнивания полей на адреса, кратные > 16 не используется, так как стандартные средства распределения памяти не умеют выдавать память, выровненную по бОльшим числам. Это касается как стандартных операторов new, так и стековой аллокации.
Примеры
struct A { // pragma pack = 8
char a1; // выравнивание на 0-й адрес
WORD a2; // 1 байт пропуска, выравнивание на 2-й адрес
void* a3; // адрес 4, пропуска нет
char a4; // адрес 8, пропуска нет
double a5; // адрес 16, 7 байт пропуска
short a6; // адрес 24, пропуска нет
}; // Общий размер – 26 байт + 6 байт padding
В случае, если pragma pack = 1, sizeof(A) == 20 байт
Class B {
Virtual ~B();
char b1;
} // Размер класса – 8 байта (vtbl + 1 байт + 3 padding)
Class C : public B {
public:
char c1;
A c2;
}
В начале класса идет vtbl, за ним должна пойти структура B, потом – структура С. Переменная с1 начнется со смещения 8. Далее потребуется
выравнивание на 8 байт - будет пропущено 7 байт между с1 и с2. Общий размер структуры после padding – 16+32 = 48 байт.правка: чертов Punto Switcher немного подгадил код :)
В типсы это надо !
Или статейку небольшую оформи.
Инфо архиполезное.
Nikopol
Т.е. собственно основная идея в следующем. Если стоит #pragma pack 8, то это означает, что компилятор обязан все адреса простых типов с sizeof() <=8 выровнять на ближайший кратный их sizeof() адрес. Это в компактном виде краткий результат всех рассмотренных моментов.
dDIMA
#pragma pack считается непереносимым хаком..:)
А так - в TIPS!
В Optimization Guide от AMD сказано:
1. Sort the structure members according to their type sizes, declaring members with larger type sizes ahead of members with smaller type sizes.
2. Pad the structure so the size of the structure is a multiple of the largest member’s type size.
Examples
Avoid structure declarations in which the members are not declared in order of their type sizes and the size of the structure is not a multiple of the size of the largest member’s type:
struct{ char a[5]; \\ Smallest type size (1 byte * 5) long k; \\ 4 bytes in this example double x; \\ Largest type size ( 8 bytes) } baz;
Instead, declare the members according to their type sizes (largest to smallest) and add padding to ensure that the size of the structure is a multiple of the largest member’s type size:
struct { double x; \\ Largest type size (8 bytes) long k; \\ 4 bytes in this example char a[5]; \\ Smallest type size ( 1 byte * 5) char pad[7]; \\ Make structure size a multiple of 8. } baz;
Мне кажется, что при использовании компиляторов от Intel или Microsoft данное замечание не стоит принимать как руководство к действию - они и так это делают.
Ghost2
>Мне кажется, что при использовании компиляторов от Intel или Microsoft данное
>замечание не стоит принимать как руководство к действию - они и так это делают.
Ммммм. Не очень понял. "1. Sort" означает инструкцию программисту, а не информацию о том, что делает компилятор. Порядок следования данных в структуре жестко определяется порядокм перечисления в файле. Перестановки полей здесь нет и быть не может.
То, что рекомендуют "вначале размещать большие поля, а потом маленькие" есть завуалированное правило постов №5 и 7 для тех, кому лень отсчитывать байты в структуре.
Nikopol
Еще в кучу стандарт С++ (что бы уж точно знать, что гарантирует стандарт)
3.7.3.1/2
The allocation function attempts to allocate the requested amount of storage. If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size. There are no constraints on the contents of the allocated storage on return from the allocation function. The order, contiguity, and initial value of storage allocated by successive calls to an allocation function is unspecified. The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function). ...
Еще 5.3.4/10.
Выравнивание нужно для того, чтобы обратиться к элементу данных за наименьшее число тактов процессора.
Для 32 разрядных шины за раз идет обращение к 4 байтам. Поэтому когда вы описываете структуру, элементы укладываются друг за другом, но если элемент попадает на границу раздела, он сдвигается. В конце вся структура выравнивается на границу наибольшего элемента.
Например структура char, char, dword будет занимать 8 байт: char, char, пусто, пусто, dword
структура dword, char, char будет занимать 8 байт: char, char, пусто, пусто, dword
Что мне непонятно, для чего выравнивать структуру в конце?
Например для dword, char, char структуры размер будет все тот же: dword, char, char, пусто, пусто.
> Что мне непонятно, для чего выравнивать структуру в конце?
MySuperPuperStruct m_Fignya[666];
Nikopol
А зачем?
Тема в архиве.