Как C++ хранит функции и объекты в памяти?
Допустим, у нас есть класс
class A
{
int x;
public:
void sayHi()
{
cout<<"Hi";
}
};
int main()
{
A *a=NULL;
a->sayHi();
}
Приведенный выше код скомпилируется на Turbo C (где я тестировал) и напечатает Hi
в качестве вывода.
Я ожидал аварии, потому что a
является NULL
, Более того, если я сделаю sayHi()
функция виртуальная, это говорит
Abnormal temination(Segmentation fault in gcc)
Я знаю, что многое зависит от реализации, но если бы кто-нибудь мог пролить свет на любую реализацию или просто дать обзор, это было бы очень приятно.
4 ответа
Очевидно, что код имеет неопределенное поведение, т. Е. Все, что вы получаете, случайно. Тем не менее, системе не нужно знать об объекте при вызове не виртуальной функции-члена: она может быть вызвана только на основе сигнатуры. Кроме того, если функция-член не нуждается в доступе к члену, ей вообще не нужен объект, и она может просто выполняться. Это то, что вы заметили, когда код напечатал какой-то вывод. Однако не определено, так ли это реализовано в системе, т. Е. Ничто не говорит о том, что она работает.
При вызове виртуальной функции система типов начинает поиск записи информации о типе, связанной с объектом. При вызове виртуальной функции на NULL
Указатель, такой информации не существует, и попытка доступа к ней, вероятно, приведет к некоторому сбою. Тем не менее, это не обязательно, но для большинства систем.
КСТАТИ, main()
всегда возвращается int
,
В C++ методы класса не хранятся внутри экземпляров этого класса. Это просто "специальные" функции, которые прозрачно принимают this
указатель в дополнение к аргументам, указанным программистом.
В вашем случае sayHi()
Метод не ссылается ни на одно из полей класса, поэтому this
указатель (который NULL
) никогда не следует.
Не делайте ошибку, однако, это все еще неопределенное поведение. Ваша программа может отправлять неприятные электронные письма в ваш список контактов, когда вы вызываете это. В данном конкретном случае это делает худшее, и, кажется, работает.
virtual
Случай метода был добавлен, так как я ответил на вопрос, но я не буду уточнять свой ответ, так как он включен в ответы других.
Как обобщение, макет объекта, созданного из класса без суперклассов и виртуальных функций, выглядит следующим образом:
* - v_ptr ---> * pTypeInfo
| |- pVirtualFuncA
| |- pVirtualFuncB
|- MemberVariableA
|- MemberVariableB
v_ptr
является указателем на v-таблицу, которая содержит адреса виртуальных функций и данные RTTI для объекта. Классы без виртуальных функций не имеют v-таблиц.
В вашем примере выше, class A
не имеет виртуальных методов и, следовательно, не имеет V-таблицы. Это означает реализацию sayHi()
Вызов может быть определен во время компиляции и является инвариантным.
Компилятор генерирует код, который устанавливает неявный this
указатель на a
а затем прыгает в начало sayHi()
, Поскольку реализация не нуждается в содержимом объекта, тот факт, что он работает, когда указатель NULL
это счастливое совпадение.
Если бы вы должны были сделать sayHi()
Виртуальный компилятор не может определить реализацию, вызываемую во время компиляции, поэтому вместо этого генерирует код, который ищет адрес функции в v-таблице и вызывает ее. В вашем примере где a
является NULL
компилятор читает содержимое адреса 0
, вызывая прерывание.
Если вы вызываете не виртуальный метод класса, то для компилятора достаточно знать, к какому классу принадлежит функция, и, разыменовав - хотя и NULL - указатель на класс для вызова метода, компилятор получает эту информацию. Метод sayHi() - это просто функция, которая принимает указатель на экземпляр класса в качестве скрытого параметра. Этот указатель имеет значение NULL, но это не имеет значения, если вы не ссылаетесь на какие-либо атрибуты в методе.
Как только вы сделаете этот метод виртуальным, ситуация изменится. Компилятор больше не знает, какой код связан с методом во время компиляции, и должен выяснить это во время выполнения. Что он делает, так это смотрит в таблицу, которая в основном содержит указатели на функции для всех виртуальных методов; эта таблица связана с экземпляром класса, поэтому она смотрит на часть памяти относительно указателя NULL и поэтому в этом случае дает сбой.