Расположение в памяти членов класса и функций члена

Вот простой класс C++:

class A
{
public:
    explicit A() : m_a(0) { }
    explicit A(int a) m_a(a) { }
    int getA() const { return m_a; }
    void setA(int a) { m_a = a; }

private:
    int m_a;
}

Это то, что я знаю до сих пор:

  • когда вы объявляете объект экземпляра класса, для этого объекта выделяется память. Выделенная память эквивалентна памяти его членов, суммированных. Так что в моем случае sizeof(A) = sizeof(int) = sizeof(m_a)
  • все функции-члены класса A хранятся где-то в памяти, и все экземпляры класса A используют одни и те же функции-члены.

Вот чего я не знаю:

Где хранятся функции-члены и как они на самом деле хранятся? Допустим, что int например хранится на 4 байтах; я могу вообразить расположение оперативной памяти с 4 смежными ячейками, каждая из которых хранит часть этого int. Как я могу представить этот макет для функции?(Это может звучать глупо, но я думаю, что функции должны иметь место в памяти, потому что вы можете иметь указатель на них). Также как и где хранятся инструкции функций? Мое первое восприятие состояло в том, что функции и инструкции функций хранятся в исполняемом файле программы (и его динамических или статических библиотеках), но если это так, что произойдет, когда вы создадите указатель на функцию? Указатели функций AFAIK указывают на места в оперативной памяти, могут ли они указывать на места в двоичных файлах программы? Если да, то как это работает?

Может ли кто-нибудь объяснить мне, как это работает, и указать, является ли то, что я знаю, правильным или неправильным?

3 ответа

Решение

Во-первых, вам нужно понять роль компоновщика и что такое исполняемые файлы (обычно выполняемые в виртуальной памяти) и адресные пространства и процессы. В Linux читайте об ELF и системном вызове execve(2). Читайте также книгу " Линкеры и загрузчики" от Levine и операционные системы: три простых предмета.

Функции-члены могут быть виртуальными или обычными функциями.

  • Равнина (не virtual) функция-член похожа на функцию C (за исключением того, что она имеет this как неявный, часто первый, параметр). Например, ваш getA Метод реализован подобно следующей функции C (вне объекта, например, в сегменте кода исполняемого двоичного файла):

    int C$getA(A*thisptr) const { return thisptr->m_a; }
    

    тогда представьте, что компилятор переводит p->getA() в C$getA(p)

  • Виртуальная функция-член обычно реализуется через виртуальную таблицу ( таблица виртуальных методов). Объект с некоторыми виртуальными функциями-членами (включая деструктор) обычно имеет в качестве своего первого (неявного) поля-члена указатель на такую ​​таблицу (созданную в другом месте компилятором). Ваш class A не имеет никакого виртуального метода, но представьте, если бы у него был дополнительный virtual void print(std::ostream&); метод, то ваш class A будет иметь такой же макет, как

    struct A$ {
       struct A$virtualmethodtable* _vptr;
       int m_a;
    };
    

    и виртуальная таблица может быть

    struct A$virtualmethodtable {
      void (*print$fun) (struct A$*, std::ostream*);
    };
    

    (поэтому добавление других виртуальных функций означает просто добавление слота внутри этой таблицы); а потом звонок как p->print(std::cout); будет переведен почти как p->_vptr.print$fun(p,&std::cout);... Кроме того, компилятор будет генерировать в виде константных таблиц различные таблицы виртуальных методов (по одной на класс).

NB: вещи более сложны с множественным или виртуальным наследованием.

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

NB. Если вы компилируете с недавним GCC (то есть с g++) вы можете передать его, например, -fdump-tree-all flag: он создаст сотни файлов дампа, показывающих частично - в текстовой форме - некоторые внутренние представления компилятора, которые вы можете проверить с помощью пейджера (например, less) или текстовый редактор. Вы также можете использовать MELT или посмотреть код сборки, созданный с g++ -S -fverbose-asm -O1....

Все локальные нестатические переменные и не виртуальные функции сохраняются в сегменте кода / текста.

Все статические и глобальные переменные сохраняются в сегменте статических данных.

Класс с виртуальными функциями или унаследованный от класса с виртуальными функциями будет вставлять указатель vptr компилятором. Vptr указывает на таблицу виртуальных функций, которая имеет количество слотов функций. Каждый слот содержит адрес функции, который хранится в сегменте кода.

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

Другие вопросы по тегам