Где функции объекта, хранящегося в памяти?

Предположим, что у нас есть класс:

class Foo
{
private:
    int a;
public:
    void func()
    {
       a = 0;
       printf("In Func\n");
    }
}

int main()
{
    Foo *foo = new Foo();
    foo->func();
    return 0;
}

Когда объект класса Foo создается и инициализируется, я понимаю, что целое число a будет занимать 4 байта памяти. Как хранится функция? Что происходит в памяти/стек/регистры/с помощью счетчика программ при вызове функции foo- > func()?

Ответ 1

Ваша функция не является виртуальной, поэтому статически называется: компилятор вставляет прыжок в сегмент кода, соответствующий вашей функции. Никакая дополнительная память не используется для каждого экземпляра.

Если бы ваша функция была виртуальной, ваш экземпляр имел бы vpointer, который был бы разыменован, чтобы найти его класс 'vtable, который затем будет проиндексирован, чтобы найти указатель функции, который будет вызываться, и, наконец, прыгните туда. Таким образом, дополнительная стоимость - это один vtable для каждого класса (возможно, размер одного указателя функции, количество виртуальных функций вашего класса) и один указатель на один экземпляр.

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

Ответ 2

Короткий ответ: Он будет храниться в текстовой или кодовой секции двоичного файла только один раз независимо от количества экземпляров созданного класса.

Функции не хранятся отдельно нигде для каждого экземпляра класса. Они обрабатываются так же, как и любая другая функция, не являющаяся членом. Единственное отличие заключается в том, что компилятор фактически добавляет дополнительный параметр функции, который является указателем типа класса. Например, компилятор будет генерировать прототип функции следующим образом:

void func(Foo* this);

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

Любая ссылка на переменную-член будет заменена на

this-><member> //for your ex. a=0 translates to this->a = 0;

Итак, строка foo- > func(); примерно соответствует:

  • Введите значение Foo * в стек. #Compiler зависимый
  • вызов func, который заставит указатель инструкции перейти к смещению func в исполняемом #architecture зависимом Read this и this
  • Func выведет значение из стека. Любую дополнительную ссылку на переменную-член будет предшествовать разыменование этого значения

Ответ 3

Функции-члены как обычные функции, они хранятся в разделе "код" или "текст". Есть одна особенность с (нестатическими) функциями-членами, и это "скрытый" аргумент this, который передается вместе с функцией. Поэтому в вашем случае адрес foo будет передан в func.

Точно так же передается этот аргумент, а то, что происходит с регистрами и стеком, определяется ABI (Application Binary Interface) и варьируется от процессора к процессору. Для этого нет строгого определения, если вы не сообщите нам, какая комбинация компилятора, ОС и процессора используется (и предполагая, что информация тогда общедоступна), не все поставщики компилятора/ОС расскажут это очень четко). Например, x86-64 будет использовать RCX для this в WIndows и RDI в Linux, а инструкция вызова автоматически будет перенаправлять адрес возврата в стек. На процессоре ARM [в Linux, но я думаю, что то же самое относится и к Windows, я просто никогда не смотрел на это], R0 используется для указателя this, а инструкция BX используется для вызова, который как часть самого себя хранит lr с ответом pc инструкции. lr затем должен быть сохранен [возможно, в стеке] в func, так как он вызывает printf.