Расположение в памяти членов класса и функций члена
Вот простой класс 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 указывает на таблицу виртуальных функций, которая имеет количество слотов функций. Каждый слот содержит адрес функции, который хранится в сегменте кода.
Чтобы понять это, вам нужно узнать о структуре памяти программы. код будет доступен объектам. и все объекты будут иметь свою собственную копию данных.