Почему программа не сбой при вызове функции-члена через нулевой указатель в С++?
#include "iostream"
using namespace std;
class A
{
public:
void mprint()
{
cout<<"\n TESTING NULL POINTER";
}
};
int main()
{
A *a = NULL;
a->mprint();
return 0;
}
Я получаю вывод как "TESTTING NULL POINTER". Может кто-нибудь объяснить, почему эта программа печатает выходные данные вместо сбоев. Я проверил его на Dev С++, и компилятор aCC дал тот же результат.
Ответы
Ответ 1
Вы не используете какие-либо переменные-члены из A
- функция полностью не зависит от экземпляра A
, и поэтому сгенерированный код не содержит ничего, что разыгрывает 0. Это все еще undefined поведение - возможно, это может случиться с некоторыми компиляторами. Undefined поведение означает, что "все может случиться" - в том числе, что программа работает, как ожидал программист.
Если вы, например, make mprint
virtual вы можете получить сбой - или вы не сможете его получить, если компилятор видит, что на самом деле ему не нужна виртуальная таблица.
Если вы добавите переменную-член в и распечатаете это, вы получите сбой.
Ответ 2
В соответствии с спецификацией С++, эта программа имеет поведение undefined, потому что вы вызываете функцию-член на пустом приемнике.
Причина, по которой это работает, заключается в том, что не виртуальные функции-члены обычно реализуются как обычные функции, которые принимают указатель "this" как неявный первый аргумент. Следовательно, если вы вызываете функцию-член по нулевому указателю, если вы не используете этот указатель, ваша программа не будет разбиваться. Конечно, вы не можете полагаться на это; действительный компилятор С++ может привести к сбою.
Однако виртуальные функции - это другая история, потому что функция, которая фактически вызвана, должна быть разрешена во время выполнения. Обычно это связано с интроспекцией таблицы виртуальных функций приемника. Таким образом, если вы попытаетесь вызвать виртуальную функцию-член на нулевом указателе, даже если функция te не имеет доступа к этому, это все равно приведет к сбою. Попробуйте это, если вам интересно!
Ответ 3
Результаты вызова функции-члена с использованием нулевого указателя на объект undefined behavour в С++, чтобы он мог что-то сделать.
В этом случае это, вероятно, потому, что оно переписало вашу функцию, поскольку это было похоже на это
void mprint(A* this);
и ваш вызов, подобный этому
mprint(0);
Таким образом, он просто назвал его, как если бы он был обычной функцией, и передал нулевой указатель в качестве параметра, который вы никогда в действительности не используете. Это объясняет, почему это не сбой, но компилятор волен делать что угодно.
Ответ 4
Простой ответ: поскольку mprint()
не использует ни одну из переменных-членов класса
Подробный ответ. Когда вызывается метод класса, экземпляр класса передается на функцию вызываемого абонента (обычно как первый аргумент, однако, в некоторых соглашениях о вызовах, таких как __thiscall, это передается в регистр). Этот экземпляр класса используется для доступа ко всем переменным-членам, которые используются в методе calllee.
В этом случае этот экземпляр имеет NULL, но это не имеет никакого значения, поскольку в методе callle не используются переменные-члены. Попробуйте изменить код таким образом, чтобы вы печатали значение переменной-члена в методе mprint()
, и вы получите сбой.
Ответ 5
Возможность вызова не виртуальных функций-членов в недействительных указателях даже позволяет кодировать информацию, связанную с объектом в самом указателе. Например:
#include <iostream>
class MagicInteger {
public:
static MagicInteger* fromInt (int x) {
return reinterpret_cast<MagicInteger*>(x);
}
int getValue() {
return static_cast<int>(reinterpret_cast<intptr_t>(this));
}
private:
// forbid messing around
MagicInteger ();
MagicInteger (MagicInteger&);
MagicInteger& operator=(const MagicInteger&);
};
int main (void) {
MagicInteger* i = MagicInteger::fromInt(6);
std::cout << "Value is " << i->getValue() << std::endl;
return 0;
}
Это также можно использовать для реализации помеченных указателей, то есть указателей, содержащих метаинформацию о pointee.
Эти две идиомы используются в Google Chrome javascript VM V8 для представления 31-битных целых чисел
Ответ 6
Это полный юридический вызов.
позволяет понять, как это работает
когда создается новый объект, создаются его переменные-члены.
Как насчет функций-членов? Функция-член не выделяется новостями, всегда есть одна копия всей функции-члена. По умолчанию переменная-член добавляется к каждой функции-члену, которая является этим указателем, который указывает на сам объект.
Когда нет объекта, который является указателем объекта, является нулевым значением. Это не имеет значения, потому что вы не обращаетесь к нему каким-либо образом. У вас возникнут проблемы, если вы используете этот указатель любой из переменной-члена в методе. Это связано с тем, что переменная-член недействительна в случае нулевого указателя.
в MFC у нас есть метод GetSafeHwnd() для CWnd. Это работает по тому же принципу.