А) Это неопределенное поведение. Может произойти любое поведение.
B) Поскольку вы не вызываете виртуальный метод, довольно легко объяснить, почему неопределенное поведение на самом деле делает это (и я проверял это практически на каждом компиляторе, который мог найти).
В C ++ вызов метода-члена эквивалентен (на практике, если не определению) вызову члена со скрытой переменной this. Если метод виртуальный, он должен пройти через vftable, но не для не виртуального метода.
So
Foo::Bar(){}
является грубым эквивалентом
Foo_Bar(Foo *this){}
и в коде вызова
Foo *foo = new Foo();
foo->bar();
вторая строка примерно соответствует моральному эквиваленту
Foo_Bar(foo);
Теперь здесь происходит некоторое упрощение, и, как я уже сказал, частично это может быть деталью реализации, а не спецификацией. Тем не менее, поведение остается верным (хотя полагаться на это является ошибкой).
Но, учитывая предыдущее, посмотрите на реализацию:
void Foo::Bar(){printf("Hello, world!\n");}
и телефонный код:
Foo *foo = 0;
foo->Bar();
Как мы уже говорили, это примерно эквивалент (так как мы не виртуальные):
Foo *foo = 0;
Foo::Bar(foo);
Это означает, что мы вызываем эквивалент следующего метода:
void Foo_Bar(Foo* this)
{ printf("Hello, world\n"); }
Теперь, учитывая этот метод, мы на самом деле не разыменовываем указатель this! Итак, совершенно ясно, почему, в этом случае, метод будет работать и не потерпит неудачу.
Практическим результатом этого является то, что вызов не виртуальных методов для нулевого указателя, когда метод не разыменовывает член, обычно приводит к такому наблюдаемому поведению. Но полагаться на любое неопределенное поведение - это, в основном, зло.