(2) Функции-члены переписываются компилятором. Рассмотрим class A
следующим образом.
class A {
int i;
public:
A () : i (0) { }
void f (int a, char *b) { i = a; }
}
Тогда то, что делает компилятор из A::f
, выглядит примерно так (псевдокод):
void A::f (A * const this, int a, char *b) { this->i = a; }
Рассмотрим теперь вызов A::f
.
A a;
a.f (25, "");
Компилятор генерирует код, подобный следующему (псевдокод):
A a;
A::f (&a, 25, "");
Другими словами, компилятор работает со скрытым указателем this
на каждую нестатическую функцию-член, и каждый вызов получает ссылку на экземпляр, к которому он был вызван. Это, в частности, означает, что вы можете вызывать нестатические функции-члены для указателей NULL
.
A *a = NULL;
a->f (25, "");
Сбой происходит только тогда, когда вы фактически ссылаетесь на нестатические переменные-члены класса.
Полученный отчет о сбое также иллюстрирует ответ на вопрос (1). Во многих случаях вы не будете аварийно завершать работу по адресу 0
, но смещение этого. Это смещение соответствует смещению, которое переменная доступа имеет в структуре памяти, выбранной компилятором для class A
(в этом случае многие компиляторы фактически смещают его с помощью 0x0
, и class A
в памяти не будет отличаться от struct A { int i; };
). По сути, указатели на классы являются указателями на эквивалентную структуру C. Функции-члены реализованы как свободные функции, принимающие ссылку на экземпляр в качестве первого аргумента. Все и любые проверки доступа в отношении открытых, защищенных и закрытых членов выполняются компилятором заранее, сгенерированная сборка не имеет ни малейшего представления о какой-либо из этих концепций. Фактически, ранние версии C ++, как говорят, были наборами умных макросов C.
Структура памяти (как правило) немного меняется при наличии виртуальных функций.