Подтвердить что ты не робот

В С++, где в памяти помещаются функции класса?

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

Правильно ли это?

Итак, если я создаю 100 объектов на С++, я могу оценить, что мне понадобится пространство для всех переменных-членов, которым принадлежит объект, умноженное на 100 (возможные проблемы выравнивания здесь), а затем мне нужно пространство в сегменте кода для одной копии кода для каждой функции-члена для этого типа объекта (не 100 копий кода).

Влияют ли виртуальные функции, полиморфизм, фактор наследования?

Как насчет объектов из динамически связанных библиотек? Я полагаю, что dlls получают свои собственные сегменты стека, кучи, кода и данных.

Простой пример (не может быть синтаксически правильным):

// parent class
class Bar
{
public:
    Bar()  {};
    ~Bar() {};

    // pure virtual function
    virtual void doSomething() = 0;

protected:
    // a protected variable
    int mProtectedVar;
}

// our object class that we'll create multiple instances of
class Foo : public Bar
{
public:
    Foo()  {};
    ~Foo() {};

    // implement pure virtual function
    void doSomething()          { mPrivate = 0; }

    // a couple public functions
    int getPrivateVar()         { return mPrivate; }
    void setPrivateVar(int v)   { mPrivate = v; }

    // a couple public variables
    int mPublicVar;
    char mPublicVar2;

private:
    // a couple private variables
    int mPrivate;
    char mPrivateVar2;        
}

О том, сколько памяти должно содержать 100 динамически выделенных объектов типа Foo, включая комнату для кода и всех переменных?

4b9b3361

Ответ 1

Не обязательно верно, что "каждому объекту - когда он создан - будет предоставлено пространство в HEAP для переменных-членов". Каждый объект, который вы создаете, займет некоторое ненулевое пространство где-то для его переменных-членов, но где зависит от того, как вы сами выделяете объект. Если объект имеет автоматическое (стековое) распределение, то также будут его элементы данных. Если объект выделен в свободном хранилище (куче), то тоже будут его членами данных. В конце концов, каково распределение объекта, отличного от объекта его данных?

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

Для объектов с виртуальными функциями каждый будет иметь указатель vtable, выделенный, как если бы он был явно объявленным элементом данных внутри класса.

Что касается функций-членов, код для них, скорее всего, не отличается от кода свободной функции в терминах того, куда он идет в исполняемом изображении. В конце концов, функция-член в основном является свободной функцией с неявным указателем "this" в качестве первого аргумента.

Наследование ничего не меняет.

Я не уверен, что вы имеете в виду, когда DLL получает свой собственный стек. DLL не является программой и не нуждается в стеке (или куче), поскольку объекты, которые он выделяет, всегда выделяются в контексте программы, которая имеет свой собственный стек и кучу. То, что в DLL есть код (текст) и сегменты данных, имеет смысл, хотя я не разбираюсь в реализации таких вещей в Windows (которые, я полагаю, вы используете, учитывая вашу терминологию).

Ответ 2

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

Тогда он становится несколько сложным. Некоторые из проблем...

  • Компилятор может, если он хочет или инструктирован, встроенный код. Поэтому, хотя это может быть простая функция, если она используется во многих местах и ​​выбрана для вложения, может быть сгенерировано много кода (распространяется по всему программному коду).
  • Виртуальное наследование увеличивает размер полиморфизма каждого члена. VTABLE (виртуальная таблица) скрывается вместе с каждым экземпляром класса с использованием виртуального метода, содержащего информацию для диспетчеризации времени выполнения. Эта таблица может расти довольно большой, если у вас много виртуальных функций или множественное (виртуальное) наследование. Уточнение: VTABLE относится к классу, но указатели на VTABLE сохраняются в каждом экземпляре (в зависимости от структуры типа предка объекта).
  • Шаблоны могут вызвать раздувание кода. Каждое использование шаблонного класса с новым набором параметров шаблона может генерировать совершенно новый код для каждого участника. Современные компиляторы стараются и сворачивают это как можно больше, но это сложно.
  • Выравнивание/добавление структуры может привести к тому, что простые экземпляры классов будут больше, чем вы ожидаете, поскольку компилятор создает структуру целевой архитектуры.

При программировании используйте оператор sizeof для определения размера объекта - никогда не жесткого кода. Используйте приблизительную метрику "Сумма размера переменной члена + какая-то VTABLE (если она существует)" при оценке того, насколько дороги будут большие группы экземпляров, и не беспокойтесь о размере кода. Оптимизируйте позже, и если какой-либо из неочевидных проблем вернется к чему-то, я буду очень удивлен.

Ответ 3

Хотя некоторые аспекты этого являются зависимыми от поставщика компилятора. Весь скомпилированный код переходит в раздел памяти в большинстве систем под названием "текст". это отдельно от секций кучи и стека (четвертый раздел "данные" содержит большинство констант). Создание нескольких экземпляров класса приводит к пробелу во время выполнения только для его переменных экземпляра, а не для любых его функций. Если вы используете виртуальные методы, вы получите дополнительный, но небольшой бит памяти, зарезервированный для виртуальной таблицы поиска (или эквивалентной для компиляторов, которые используют какую-то другую концепцию), но ее размер определяется количеством виртуальные методы умножают количество виртуальных классов и не зависят от количества экземпляров во время выполнения

Это относится к статически и динамически связанному коду. Фактический код все живет в "текстовом" регионе. Большинство операционных систем на самом деле могут совместно использовать DLL-код для нескольких приложений, поэтому, если несколько приложений используют одну и ту же DLL, только одна копия находится в памяти, и оба приложения могут ее использовать. Очевидно, что нет дополнительной экономии от общей памяти, если только одно приложение использует связанный код.

Ответ 4

Вы не можете полностью точно сказать, сколько памяти будет занимать класс или объекты X в ОЗУ.

Однако, чтобы ответить на ваши вопросы, вы правы, что код существует только в одном месте, он никогда не "распределяется". Таким образом, код является per-class и существует независимо от того, создаете ли вы объекты или нет. Размер кода определяется вашим компилятором, и даже тогда компиляторам часто может потребоваться оптимизировать размер кода, что приводит к различным результатам.

Виртуальные функции ничем не отличаются, сохраняя (небольшие) дополнительные накладные расходы таблицы виртуальных методов, которая обычно находится в классе.

Что касается библиотек DLL и других библиотек... правила не различаются в зависимости от того, откуда пришел код, поэтому это не является фактором использования памяти.

Ответ 5

если скомпилировано как 32 бит. то sizeof (Bar) должен давать 4. Foo должен добавить 10 байт (2 ints + 2 символа).

Так как Foo наследуется от Bar. Это не менее 4 + 10 байт = 14 байт.

GCC имеет атрибуты для упаковки структур, поэтому нет отступов. В этом случае 100 записей занимают 1400 байт + небольшую накладную плату для выравнивания распределения + некоторые накладные расходы для управления памятью.

Если атрибут упакованного не указан, он зависит от выравнивания компиляторов.

Но это не учитывает, сколько памяти vtable занимает и размер скомпилированного кода.

Ответ 6

Информация, приведенная выше, очень помогает и дает мне некоторое представление о структуре памяти С++. Но я хотел бы добавить, что независимо от количества виртуальных функций в классе всегда будет только 1 VPTR и 1 VTABLE для каждого класса. В конце концов VPTR указывает на VTABLE, поэтому нет необходимости в более чем одном VPTR в случае нескольких виртуальных функций.

Ответ 7

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

Элементы-члены (и виртуальные функции) из любых базовых классов также являются частью класса, поэтому включите их.

Как и в c, вы можете использовать оператор sizeof (classname/datatype) для получения размера в байтах класса.

Ответ 8

Да, это правильно, код не дублируется при создании экземпляра объекта. Что касается виртуальных функций, то правильный вызов функции определяется с помощью vtable, но это не влияет на создание объекта как таковое.

DLL (общие/динамические библиотеки в целом) отображаются в пространство памяти процесса. Каждая модификация выполняется как Copy-On-Write (COW): одна DLL загружается только один раз в память, и для каждой записи в изменяемое пространство создается копия этого пространства (обычно размер страницы).

Ответ 9

Очень сложно дать точный ответ на ваш вопрос, поскольку это зависит от реализации, но приблизительные значения для 32-разрядной реализации могут быть:

int Bar::mProtectedVar;    // 4 bytes
int Foo::mPublicVar;        // 4 bytes
char Foo::mPublicVar2;     // 1 byte

Здесь есть все проблемы, и итоговая сумма может составлять 12 байт. У вас также будет vptr - сказать anoter 4 байта. Таким образом, общий размер данных составляет около 16 байт на экземпляр. Невозможно сказать, сколько места займет код, но вы правы в мышлении, что есть только одна копия кода, разделяемого между всеми экземплярами.

Когда вы спрашиваете

Я предполагаю, что dlls получают свой собственный стек, кучи, кода и данных.

В ответ на то, что между данными в DLL и данными в приложении действительно нет большой разницы - в основном они разделяют все между ними. Это должно быть так, когда вы думаете об этом - если у них разные стеки ( например), как могут работать вызовы функций?