Я столкнулся с очень странным поведением при использовании G ++ (4.5.2) на разных платформах; вот код:
class Class
{
private:
std::string rString;
public:
Class()
{
this->rString = "random string";
std::cout << "Constructor of Class" << std::endl;
}
virtual ~Class()
{
std::cout << "Destructor of Class" << std::endl;
}
void say() const
{
std::cout << "Just saying ..." << std::endl;
if (this == NULL)
std::cout << "Man that's really bad" << std::endl;
}
void hello() const
{
std::cout << "Hello " << this->rString << std::endl;
}
};int main()
{
Class *c = NULL;
/* Dereferencing a NULL pointer results
in a successful call to the non-static method say()
without constructing Class */
(*c).say(); // or c->say()
/* Dereferencing a NULL pointer and accessing a random
memory area results in a successful call to say()
as well */
c[42000].say();
/* Dereferencing a NULL pointer and accessing a
method which needs explicit construction of Class
results in a Segmentation fault */
c->hello();
return (0);
}
Вопрос в том, почему два первых утверждения в основной функции не вызывают сбой программы? Это неопределенное поведение, или компилятор просто вызывает Class :: say (), как если бы он был статическим, поскольку он не разыменовывает указатель «this» внутри метода?
Да, это неопределенное поведение. Вы не можете вызвать функцию-член с нулевым указателем.
На практике первые два действительно работают, потому что this
никогда не разыменовывается, поэтому ваше неопределенное поведение не должно проявляться так, как это происходит в третьем, где к памяти действительно обращаются неправильно.
(Во всех случаях вы умираете немного внутри каждый раз, когда это называется, так что не делайте этого.)
Неопределенное поведение просто означает, что то, что происходит, не определено. Это не значит «сбой».
Неопределенное поведение может делать что угодно, в том числе работать так, как вы хотите.
Это неопределенное поведение. Поскольку вы не используете функции как «виртуальные», они просто будут вызывать функции статически.
Это неопределенное поведение, хотя в первых двух случаях оно было оптимизировано для вызова Class::say()
без использования каких-либо переменных-членов самого объекта (следовательно, this-> не разыменовывается / не используется, вызывая sigserv), но третий пытается получить доступ к его элементу. Аналогичным образом, следующее может вызвать ошибку на другом компиляторе, таком как VC ++.
Внутренне компилятор реализует вызовы не статических, не виртуальных функций, просто передавая указатель объекта this
в качестве дополнительного параметра или, скорее, вместе со всеми надлежащими параметрами в стеке.
Стандарт не предписывает, что должно происходить, когда функция-член вызывается с чем-то, что не является областью памяти, содержащей фактический объект соответствующего класса или структуры. Требование этого потребовало бы проверок не-noop времени выполнения (если вообще возможно) и поэтому нецелесообразно запрашивать у соответствующей реализации.
Система ввода уже гарантирует, что у вас есть все возможности для вызова функции-члена с объектом неправильного типа, но проверка NULL
или же nullptr
вызовы не могут быть осмысленно охвачены типом указателя.
Если вы хотите, чтобы ваш двоичный файл зависал при вызове функции-члена с нулевым указателем, делая функцию-член virtual
должен делать работу, потому что компилятор будет иметь разыменовать vptr из this
и операционная система ответит, показывая вам палец.