2016-07-27 1 views
1

Так что я пытаюсь узнать немного больше о различиях между C-стиль-проливает, static_cast, dynamic_cast и я решил попробовать этот пример, который должен отражать различия между C-стиль-бросает и static_cast довольно хорошо.Метод вызова после того, как недействительный гипсе C-стиль работы

class B 
{ 
public: 
    void hi() { cout << "hello" << endl; } 
}; 

class D: public B {}; 

class A {}; 

int main() 
{ 
    A* a = new A(); 
    B* b = (B*)a; 
    b->hi(); 
} 

Ну этот фрагмент кода должен отражать, что C-стиль литья идет очень неправильно и плохо литая не обнаруживается вообще. Частично это происходит именно так. Плохой приказ не обнаружен, но я был удивлен, когда программа, а не сбой в b->hi();, напечатала на экране слово «привет».

Теперь, почему это происходит? Какой объект использовался для вызова такого метода, когда объект B не создан? Я использую g ++ для компиляции.

+0

Кастинг для несвязанного типа, подобного этому (что фактически является 'reinterpret_cast' в C++), является неопределенным поведением. Это означает, что реализация может делать все, что захочет. – Yuushi

+0

СОВЕТ: даже сломанные часы показывают правильное время два раза в день. –

+0

C style cast похож на программиста, говорящего компилятору «Поверьте мне, это B-объект». У компилятора нет выбора, кроме как согласиться. То, что происходит, когда оно выполняется, не определено. Именно поэтому c-отливки стиля настолько опасны. –

ответ

3

Как говорили другие, это неопределенное поведение.

Почему он работает? Вероятно, потому, что вызов функции связан статически, во время компиляции (это не виртуальная функция). Функция B::hi() существует, поэтому она называется. Попробуйте добавить переменную в class B и использовать ее в функции hi(). Тогда вы будете видеть проблему (значение мусора) на экране:

class B 
{ 
public: 
    void hi() { cout << "hello, my value is " << x << endl; } 

private: 
    int x = 5; 
}; 

В противном случае вы могли бы сделать функцию hi() виртуальной.Тогда функция связана динамически, во время выполнения и аварийное завершение работы программы сразу:

class B 
{ 
public: 
    virtual void hi() { cout << "hello" << endl; } 
}; 
+1

Обратите внимание, что стандарт не гарантирует, что любое из ваших предложений должно привести к сбою. – user2079303

+0

@ user2079303 Это правда. Может случиться так, что мой компьютер начнет миссию на Марс после такого звонка или начнет Terminator: Genisys. Но если он использует gcc, то, вероятно, результаты будут точно такими же, как мои: какое-то странное значение в первом и аварийное во втором примере. – Lehu

1

Теперь, почему это происходит?

Потому что это может произойти. Все может случиться. Данное поведение является undefined.

Тот факт, что что-то неожиданное произошло, хорошо показывает, почему UB настолько опасен. Если бы это всегда вызывало крах, тогда было бы гораздо легче справиться.

Какого объект используется для вызова такого метода

Скорее всего, компилятор слепо доверяет вам, и предполагает, что b делает точку на объект типа B (который не делает). Вероятно, он использовал бы остроконечную память, как если бы предположение было истинным. Функция-член не имела доступа к какой-либо из памяти, принадлежащей объекту, и поведение оказалось таким же, как если бы существовал объект правильного типа.

Будучи неопределенным, поведение может быть совершенно иным. Если вы попытаетесь снова запустить программу, стандарт не гарантирует, что демоны не вылетят из вашего носа.

1

Это работает только из-за реализации самого hi() метода, и самобытная часть спецификации C++ называется неопределенное поведение.

Кастинг с использованием приведения типа C к несовместимому типу указателя является неопределенным поведением - буквально все может произойти.

В этом случае компилятор, очевидно, решил просто доверять вам, и решил поверить, что b действительно является действительным указателем на экземпляр B - это на самом деле все, что делает C-стиль, поскольку они не содержат поведения во время выполнения. При вызове hi() на нем, метод работает, потому что:

  • Он не имеет доступа к любому экземпляру переменных, принадлежащих к B, но не A (на самом деле, он не имеет доступа к любым переменным экземпляра на всех)
  • Это а не виртуальные, так что не нужно смотреть в b «s виртуальные таблицы будут называться

поэтому он работает, но почти во всех нетривиальных случаях такого искаженного броска с последующим вызовом метода приведет в случае сбоя или повреждения памяти. И вы не можете полагаться на такое поведение - undefined не означает, что он должен быть одинаковым при каждом запуске. Компилятор прекрасно вписывается в этот код, чтобы вставить генератор случайных чисел, и после генерации 1 запустил полную копию оригинала Doom. Помните об этом, когда все, что связано с неопределенным поведением, похоже, работает, потому что это может не сработать завтра, и вам нужно относиться к нему так.

Смежные вопросы