На самом деле вы задаете много разных вопросов, поэтому я постараюсь ответить на каждый из них по очереди.
Сначала вы хотите узнать, как выравниваются элементы данных. Выравнивание элементов определяется компилятором, но из-за того, как процессоры обрабатывают несогласованные данные, все они, как правило, следуют одному и тому же
Рекомендация, согласно которой структуры должны быть выровнены на основе наиболее ограничивающего члена (который обычно, но не всегда, является самым большим внутренним типом), а структуры всегда выравниваются таким образом, чтобы все элементы массива были выровнены одинаково.
Например:
struct some_object
{
char c;
double d;
int i;
};
Эта структура будет иметь размер 24 байта. Поскольку класс содержит double, он будет выровнен на 8 байт, то есть char будет дополнен 7 байтами, а int будет дополнен на 4, чтобы гарантировать, что в массиве some_object все элементы будут выровнены на 8 байтов (размер объекта всегда кратно его выравниванию). Вообще говоря, это зависит от компилятора, хотя вы обнаружите, что для данной архитектуры процессора большинство компиляторов выравнивают данные одинаково.
Второе, о чем вы упомянули, - это члены производного класса. Упорядочивание и выравнивание производных классов - это своего рода боль. Классы индивидуально следуют правилам, которые я описал выше для структур, но когда вы начинаете говорить о наследовании, вы попадаете в беспорядок. Учитывая следующие классы:
class base
{
int i;
};
class derived : public base // same for private inheritance
{
int k;
};
class derived2 : public derived
{
int l;
};
class derived3 : public derived, public derived2
{
int m;
};
class derived4 : public virtual base
{
int n;
};
class derived5 : public virtual base
{
int o;
};
class derived6 : public derived4, public derived5
{
int p;
};
Схема памяти для базы будет такой:
int i // base
Схема памяти для производного будет:
int i // base
int k // derived
Схема памяти для производного2 будет:
int i // base
int k // derived
int l // derived2
Схема памяти для производного3 будет следующей:
int i // base
int k // derived
int i // base
int k // derived
int l // derived2
int m // derived3
Вы можете заметить, что база и производная появляются здесь дважды. Это чудо множественного наследования.
Чтобы обойти это, у нас есть виртуальное наследование.
Схема памяти для производного4 будет следующей:
void* base_ptr // implementation defined ptr that allows to find base
int n // derived4
int i // base
Схема памяти для производного5 будет следующей:
void* base_ptr // implementation defined ptr that allows to find base
int o // derived5
int i // base
Схема памяти для производного6 будет следующей:
void* base_ptr // implementation defined ptr that allows to find base
int n // derived4
void* base_ptr2 // implementation defined ptr that allows to find base
int o // derived5
int i // base
Вы заметите, что производные 4, 5 и 6 имеют указатель на базовый объект. Это необходимо для того, чтобы при вызове любой из базовых функций у нее был объект, который нужно передать этим функциям. Эта структура зависит от компилятора, потому что она не указана в спецификации языка, но почти все компиляторы реализуют ее одинаково.
Ситуация усложняется, когда вы начинаете говорить о виртуальных функциях, но, опять же, большинство компиляторов реализуют их одинаково. Возьмите следующие классы:
class vbase
{
virtual void foo() {}
};
class vbase2
{
virtual void bar() {}
};
class vderived : public vbase
{
virtual void bar() {}
virtual void bar2() {}
};
class vderived2 : public vbase, public vbase2
{
};
Каждый из этих классов содержит как минимум одну виртуальную функцию.
Схема памяти для vbase будет такой:
void* vfptr // vbase
Схема памяти для vbase2 будет такой:
void* vfptr // vbase2
Схема памяти для vderoduction будет следующей:
void* vfptr // vderived
Схема памяти для vdehibited2 будет следующей:
void* vfptr // vbase
void* vfptr // vbase2
Люди многого не понимают в принципах работы vftables. Первое, что нужно понять, это то, что классы хранят только указатели на vftables, а не целые vftables.
Это означает, что независимо от того, сколько виртуальных функций имеет класс, у него будет только один vftable, если только он не унаследует vftable откуда-то еще через множественное наследование. Практически все компиляторы помещают указатель vftable перед остальными членами класса. Это означает, что между указателем vftable и членами класса может быть некоторый отступ.
Я также могу сказать вам, что почти все компиляторы реализуют возможности пакета прагм, которые позволяют вручную принудительно выравнивать структуру. Обычно вы не хотите этого делать, если вы действительно не знаете, что делаете, но это есть, и иногда это необходимо.
Последнее, о чем вы спрашивали, - можете ли вы контролировать порядок. Вы всегда контролируете заказ. Компилятор всегда будет упорядочивать вещи в том порядке, в котором вы их записываете. Я надеюсь, что это длинное объяснение затронет все, что вам нужно знать.
person
Beanz
schedule
05.01.2010
unsigned char
, а затем загрузить элементы из этого буфера. Точно так же записывать члены в буфер, а вывод - в буфер. Это предотвратит проблемы с выравниванием или упаковкой, которые могут нанести ущерб вашей программе. - person Thomas Matthews   schedule 05.01.2010