Если у вас много данных, подумайте об индексации. Пожалуйста, ознакомьтесь с этими ссылками:
Объект foo
является локальной переменной с типом Foo*
. Та переменная, вероятно, выделяется на стеке для эти main
функция, точно так же, как любая другая локальная переменная. Но значение сохраненный в foo
является нулевым указателем. Это не указывает нигде. Нет никакого экземпляра типа Foo
, представленного нигде.
Для вызывания виртуальной функции вызывающая сторона должна знать, какой объект к функции обращаются. Поэтому сам объект - то, что говорит, какая функция должна действительно быть вызвана. (Это часто реализуется путем давания объекту подсказки к vtable, списку указателей функции, и вызывающая сторона просто знает, что он, как предполагается, вызывает первую функцию в списке, не зная заранее, где тот указатель указывает.)
, Но назвать невиртуальную функцию, вызывающая сторона не должна знать все это. Компилятор знает точно, какая функция будет вызвана, таким образом, это сможет генерировать CALL
команда машинного кода для движения непосредственно в желаемую функцию. Это просто передает указатель на объект, к функции обратились как скрытый параметр к функции. Другими словами, компилятор переводит Ваш вызов функции в это:
void Foo_say_hi(Foo* this);
Foo_say_hi(foo);
Теперь, так как реализация той функции никогда не ссылается ни на каких членов объекта, на который указывает this
аргумент, Вы эффективно избегаете маркера разыменования нулевого указателя, потому что Вы никогда не разыменовываете тот.
Официально, звоня любой функция — даже невиртуальная — на нулевом указателе является неопределенным поведением. Один из позволенных результатов неопределенного поведения - то, что Ваш код, кажется, работает точно, как Вы предназначили. Вы не должны полагаться на это, хотя Вы будете иногда находить библиотеки от своего поставщика компилятора, которые делают , полагаются на это. Но поставщик компилятора имеет преимущество способности добавить дальнейшее определение тому, что иначе было бы неопределенным поведением. Не делайте этого сами.
say_hi()
функция членства обычно реализуется компилятором как
void say_hi(Foo *this);
, Так как Вы не получаете доступ ни к каким участникам, Ваш вызов успешно выполняется (даже при том, что Вы вводите неопределенное поведение согласно стандарту).
Foo
не становится выделенным вообще.
Разыменование Нулевого указателя вызывает "неопределенное поведение", Это означает, что что-либо могло произойти - Ваш код, может даже казаться, работает правильно. Вы не должны зависеть от этого однако - если Вы выполняете тот же код другой платформы (или даже возможно на той же платформе) он, вероятно, откажет.
В Вашем коде нет никакого объекта Foo, только указатель, который является initalised с ПУСТЫМ УКАЗАТЕЛЕМ значения.
Это - неопределенное поведение. Но большинство компиляторов сделало инструкции, которые обработают эту ситуацию правильно, если Вы не сделаете доступа к членским переменным и виртуальной таблице.
позволяют, видят, что дизассемблирование в Visual Studio для понимает то, что происходит
Foo* foo = 0;
004114BE mov dword ptr [foo],0
foo->say_hi(); // works well
004114C5 mov ecx,dword ptr [foo]
004114C8 call Foo::say_hi (411091h)
foo->say_virtual_hi(); // will crash the app
004114CD mov eax,dword ptr [foo]
004114D0 mov edx,dword ptr [eax]
004114D2 mov esi,esp
004114D4 mov ecx,dword ptr [foo]
004114D7 mov eax,dword ptr [edx]
004114D9 call eax
, поскольку Вы видите Foo:say_hi, названный как обычная функция, но с это в регистре ecx. Для упрощают Вас, может предположить, что это передало как неявный параметр, который мы никогда не используем в Вашем примере.
, Но во втором случае мы вычисляющий адрес функциональной должной виртуальной таблицы - должные нечто обращаются, и получает ядро.
a) Это работает, потому что это ничего не разыменовывает через неявное "этот" указатель. Как только Вы делаете это, бум. Я не на 100% уверен, но я думаю, что нулевой указатель разыменовывает, сделаны RW защита первого 1K пространства памяти, таким образом, существует маленький шанс nullreferencing, не будучи пойманным, если бы Вы только разыменовываете его мимо 1K строка (т.е. некоторая переменная экземпляра, которая выделить очень далеко, как:
class A {
char foo[2048];
int i;
}
тогда a-> я возможно был бы не пойман, когда A является пустым.
b) Нигде, Вы только объявили указатель, который выделяется на основном (): s стек.
Вызов к say_hi статически связывается. Таким образом, компьютер на самом деле просто делает стандартный вызов к функции. Функция не использует полей, таким образом, нет никакой проблемы.
вызов к virtual_say_hi динамично связывается, таким образом, процессор переходит к виртуальной таблице, и так как нет никакой виртуальной таблицы там, это переходит где-нибудь случайное и разрушает программу.
В исходные дни C++ код C++ был преобразован в C. Методы объекта преобразовываются в неметоды объекта как это (в Вашем случае):
foo_say_hi(Foo* thisPtr, /* other args */)
{
}
, Конечно, имя foo_say_hi упрощено. Для получения дополнительной информации ищите искажение имени C++.
, Как Вы видите, если thisPtr никогда не разыменовывается, то код прекрасен и успешно выполняется. В Вашем случае не использовались никакие переменные экземпляра или что-либо, что зависит от thisPtr.
Однако виртуальные функции отличаются. Существует много объектных поисков, чтобы удостовериться, что правильный объектный указатель передается как параметр функции. Это разыменует thisPtr и вызовет исключение.