Здравствуйте, xexe2, Вы писали:

X>Уважаемый ол, чему же именно равен размер объекта в куче?


Собственно сначала отвечу про sizeof

На самом деле для любого типа в C++ можно вычислить, кроме sizeof ещё один параметр -- выравнивание.
Выравнивание -- это число, которому должны быть кратны смещения полей этого типа и которому должен быть кратен его sizeof.

Посчитать выравнивание можно, например, так:

template<typename T>
class Align {
    struct AlignStruct {
        char forAlign;
        T t;
    };
public:
    enum { Result = sizeof( AlignStruct ) - sizeof( T ) };
};


Используя эту конструкцию и sizeof можно поисследовать размеры разных объектов. А используя макрос OFFSET_OF ещё и смещениея разных явно заданных полей.

1) Для стандартных типов можно составить табличку.
2) Для случая структуры или класса без виртуальных методов и без предков размер и смещения полей вычисляются так:

текущее_смещение_поля = 0;
текущее_выравнивание_структуры = 1;
foreach( поля ) {
    текущее_выравнивание_структуры = max( текущее_выравнивание_структуры, align( текущее_поле ) );
    текущее_смещение_поля = округлить_вверх( текущее_смещение_поля, align( текущее_поле ) );
    разместить( текущее_поле, текущее_смещение_поля );
    текущее_смещение_поля += sizeof( текущее_поле );
}
размер_структуры = max( округлить_вверх( текущее_смещение_поля ), 1 );
выравнивание_структуры = текущее_выравнивание_структуры;


тут функция округлить_вверх( a, b ) возвращает наименьшее целое n, такое что a <= n и n кратно b

3) Для случая union в качестве выравнивания берётся максимальное выравнивание поля, а в качестве sizeof максимальный sizeof поля округлённый вверх на выравниване union

При этом не важно, сколь сложно устроены поля. Я пока не встретил ещё компилятора, который бы не придерживался такой стратегии. Тут в целом есть довольно мало места для произвола. Основное -- порядок итерации полей в приведённом выше алгоритме. Обычно они итерируются в порядке описания, но возможны варинаты. Немного спасает то, что для структур приходится всем компиляторам согласовывать свои правила, чтобы все могли звать API. Но в случае private секций руки у них уже довольно развязаны.

4) В классе, где есть виртуальные методы, обычно заводится дополнительные скрытыие поля, со своими sizeof и align. В принципе их можно воспринимать как поля стандартных типов. Соответсвенно поэкспериментировав с классами вида
class A { virtual ~A() {} };

Можно узанать значения sizeof и align этих переменных. Обычно они такие же, как и у указателя.

5) В случае простого нследования, объекты базовых типов размещаются в объекте наследнике по тому же алгоритму, что и поля. За исключением одной тонкости. Компилятор имеет право на оптимизацию, суть которой состоит в том, что можно вовсе не размещать объекты баз, в которых нет полей.
Но есть тонкости, в случае, когда есть виртуальные базы. Их всего две
5.1) Наличие виртуальной базы может порождать доп. скрытые поля (может и не порождать)
5.2) объект виртуальной базы всегда один, но вот выравнивание "остатков" от его наследников может быть разным. В целом обычно компиляторы ведут себя так, что "остаток" является как бы отдельным полем, а виртуальная база отодельным.

Собственно это примерно исчерпывает правила, которых обычно придержиываются компиляторе в вопросе определния sizeof объектов.

При этом стоит обратить вниание на то, что обычно есть параметр компиляции, который как бы задаёт максимальное выравнивание стандартного типа. Так что если вы какую-то структуру скомпилируете со значением этого параметра, скажем 2, то потом выравнивание этой структуры будет 2, что бы вы с ней не делали.
Автор: Erop    Оценить