Вызов метода класса с помощью указателя класса NULL

У меня есть следующий фрагмент кода:

class ABC{
public:
        int a;
        void print(){cout<<"hello"<<endl;}
};

int main(){
        ABC *ptr = NULL:
        ptr->print();
        return 0;
}

Выполняется успешно. Может кто-нибудь объяснить это?

Ответ 1

(Я не могу вспомнить, где у меня есть это знание, поэтому я мог быть совершенно неправ)

Под капотом большинство компиляторов преобразует ваш класс в нечто подобное:

struct _ABC_data{  
    int a ;  
};  
// table of member functions 
void _abc_print( _ABC_data* this );  

где _ABC_data является структура C-стиля

и ваш вызов ptr->print(); преобразуется в:

_abc_print( NULL)

что хорошо во время выполнения, так как вы не используете this arg.


ОБНОВЛЕНИЕ: (Спасибо программисту Windows за правильный комментарий)
Такой код работает только для CPU, который его выполняет.
Нет абсолютно разумной причины использовать эту функцию реализации. И вот почему:

  • Поскольку стандартные состояния дают поведение undefined (может ли кто-нибудь дать ссылку или хотя бы ссылку (глава N, par M...)?)
  • Если вам действительно нужна возможность вызова функции-члена без экземпляра, использование ключевого слова static дает вам все проверки совместимости и компиляции

Ответ 2

Вызов функций-членов с помощью указателя, который не указывает на действительный объект, приводит к поведению undefined. Может произойти все, что угодно. Он мог работать; он может упасть.

В этом случае он работает, потому что указатель this, который не указывает на действительный объект, не используется в print.

Ответ 3

В большинстве ответов сказано, что поведение undefined может включать в себя "появление", и они правы.

Ответ Александра Малахова дал детали реализации, которые являются обычным явлением и объясняют, почему ваша ситуация, похоже, работала, но он сделал небольшое искажение. Он написал: "Это нормально во время исполнения, так как вы не используете этот аргумент", но означало "что, казалось, было хорошо во время исполнения, так как вы не используете этот аргумент".

Но будьте осторожны, ваш код по-прежнему является undefined. Он напечатал то, что вы хотели, и он перенес баланс вашего банковского счета на мой. Я благодарю вас.

(стиль SO говорит, что это должен быть комментарий, но он слишком длинный. Я сделал это CW, хотя.)

Ответ 4

Это приводит к поведению undefined. Я положил часть работы в объяснение, почему.:) Но это более технический ответ.

В принципе, поведение undefined означает, что вам больше не гарантировано ничего о выполнении программы; С++ просто нечего сказать. Он мог бы работать точно так, как вы хотите, или он мог бы потерпеть неудачу, или он мог бы сделать это в случайном порядке.

Таким образом, кажется, что работа - прекрасный результат поведения undefined, который вы видите. Практическая причина, почему, в вашей реализации (и, честно говоря, каждой реализации), указатель this (адрес вызываемого экземпляра) вообще не используется в вашей функции. Тем не менее, если вы попытаетесь использовать указатель this (например, обратившись к переменной-члену), вы, скорее всего, потерпите крах.

Помните, что вышеприведенный параграф является чем-то конкретным для вашей реализации и его текущим поведением. Это просто предположение и то, на что вы не можете положиться.

Ответ 5

Выражение ptr->print(); будет неявно преобразовано в (*ptr).print(); в соответствии со стандартом С++ (5.2.5/3). И разыменование нулевого указателя приводит к поведению undefined. Случайно, что этот код работает без ошибок в вашем случае. Вы не должны полагаться на это.

5.2.5/3:

Если E1 имеет тип "указатель на класс" X ", то выражение E1- > E2 является преобразуется в эквивалентную форму (* (E1)) E2. оставшаяся часть 5.2.5 будет обращаться только к первому варианту (Точка) 59). сокращающий objectexpression. id-выражение как E1.E2, то тип и lvalue свойства этого выражения определяется следующим образом. в остаток 5.2.5, cq представляет либо const, либо отсутствие const; vq представляет собой либо изменчивую, либо отсутствие летучих. cv представляет собой произвольный набор cv-квалификаторов, так как определенном в 3.9.3.

Ответ 6

Хотя я не уверен, что это точный ответ, это мое понимание. (Кроме того, моя терминология для CPP плоха - игнорируйте, если это возможно)

Для С++, когда объявляется какой-либо класс (т.е. еще не создано мгновение), функции помещаются в секцию .text создаваемого двоичного файла. Когда создается момент, функции или методы не дублируются. То есть, когда компилятор разбирает файл CPP, он заменяет вызовы функций для ptr->print() соответствующим адресом, определенным в разделе .text.

Таким образом, весь компилятор выполнил бы замену соответствующего адреса на основе типа ptr для функции print. (Который также означает некоторую проверку соответствующих общедоступных/частных/наследований и т.д.)

Я сделал следующее для вашего кода (с именем test12.cpp):

EDIT: добавление некоторых комментариев в ASM ниже (я действительно не знаком с ASM, я едва читаю его - достаточно, чтобы понять некоторые основные вещи) - лучше всего было бы читать эта ссылка Wikibook, которую я тоже сделал: D Если кто-то обнаружит ошибки в ASW, пожалуйста, оставьте комментарий - я был бы рад исправить их и узнать больше.

$ g++ test.cpp -S
$ cat test.s
...
         // Following snippet is part of main function call
         movl    $0, -8(%ebp)   //this is for creating the NULL pointer ABC* ptr=NULL
                                //It sets first 8 bytes on stack to '0'
         movl    -8(%ebp), %eax //Load the ptr pointer into eax register
         movl    %eax, (%esp)   //Push the ptr on stack for using in function being called below
                                //This is being done assuming that these elements would be used
                                //in the print() function being called
         call    _ZN3ABC5printE //Call to print function after pushing arguments (which are none) and 
                                //accesss pointer (ptr) on stack.
...

vWhere ZN3ABC5printEv представляет глобальное определение функции, определенной в class ABC:

  ...
  .LC0:                    //This declares a label named .LC0
          .string "hello"  // String "hello" which was passed in print()
          .section        .text._ZN3ABC5printEv,"axG",@progbits,_ZN3ABC5printEv,comdat
          .align 2
          .weak   _ZN3ABC5printEv             //Not sure, but something to do with name mangling
          .type   _ZN3ABC5printEv, @function
  _ZN3ABC5printEv:                   //Label for function print() with mangled name
  //following is the function definition for print() function
  .LFB1401:                          //One more lavbel
          pushl   %ebp               //Save the 'last' known working frame pointer
  .LCFI9:
          movl    %esp, %ebp         //Set frame (base pointer ebp) to current stack top (esp)
  .LCFI10:
          subl    $8, %esp           //Allocating 8 bytes space on stack
  .LCFI11:
          movl    $.LC0, 4(%esp)     //Pushing the string represented by label .LC0 in 
                                     //in first 4 bytes of stack
          movl    $_ZSt4cout, (%esp) //Something to do with "cout<<" statement
          call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
          movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
          movl    %eax, (%esp)
          call    _ZNSolsEPFRSoS_E   //Probably call to some run time library for 'cout'
          leave                      //end of print() function
          ret                        //returning control back to main() 
  ...

Таким образом, даже ((ABC *)0)->print(); работает отлично.

Ответ 7

возможно, он работает, потому что указатель класса не использует какую-либо переменную-член в функции печати... Если в функции печати вы пытаетесь получить доступ к ней, она не будет выполняться... поскольку неинициализированный указатель класса не может иметь инициализированную переменную-член...

Ответ 8

Как говорили другие, это поведение undefined. Что касается причины, по которой он работает, так это то, что вы не пытаетесь получить доступ к переменной-члену a внутри print(). Все экземпляры класса совместно используют одну и ту же память для кода print(), следовательно, указатель this не требуется для доступа к методу. Однако, если вы попытаетесь получить доступ к a внутри метода, вы, скорее всего, получите исключение нарушения доступа.

Ответ 9

Это работает на каждом компиляторе, который я когда-либо пробовал (И я пробовал это на многих). Да, это "undefined", но вы не разыскиваете указатель при вызове не виртуального участника. Вы даже можете писать код, используя эту "функцию", хотя пуристы будут кричать на вас и называть вас скверными именами и т.д.

Изменить: Кажется, есть некоторая путаница в вызове функций-членов. Вы не разыскиваете указатель 'this', когда вы вызываете не виртуального участника. Вы просто используете причудливый синтаксис для передачи его в качестве параметра. Это со всеми реализациями, которые я видел, но это не гарантировано. Если он не был реализован таким образом, ваш код будет работать медленнее. Функция-член - это просто функция с дополнительным полускрытым параметром. Это! конец истории. Это говорит о том, что может быть какой-то компилятор, написанный Cletus 'slack jaw software Co., у которого есть проблема с этим, но я еще не сталкивался с этим.

Ответ 10

Это объяснит вам больше, чем если бы я использовал простые слова. Попробуйте скомпилировать его с любым компилятором, которого вы хотите:) Но обратите внимание, что UB соответствует стандартам!

#include <iostream>
using namespace std;

class Armor
{
public:
    void set(int data)
    {
        cout << "set("<<data<<")\n";
        if(!this)
        {
            cout << "I am called on NULL object! I prefer to not crash!\n";
            return;
        }
        this->data = data;  //dereference it here
    }
    void get()
    {
        if(this) cout << "data = " << data << "\n";
        else cout << "Trying to dereference null pointer detected!\n";
    }
    int data;
};

int main()
{
   cout << "Hello World" << endl; 
   Armor a;
   a.set(100);
   a.get();


   Armor* ptr1 = &a;
   Armor* ptr2 = 0;

   ptr1->set(111);
   ptr2->set(222);

   ptr1->get();
   ptr2->get();

   return 0;
}

Затем прочитайте о __thiscall - и все комментарии выше.

Hello World
set(100)
data = 100
set(111)
set(222)
I am called on NULL object! I prefer to not crash!
data = 111
Trying to dereference null pointer detected!