You have a A * that is pointing to an instance of A, not A * that is actually pointing to an instance of B. That is why dynamic_cast returns a null pointer of type B *, it is not related to these being incomplete types or anything (in the linked code, both A and B are complete types), and thus this is defined behaviour for dynamic_cast. However after that a null pointer is accessed; a clever compile can know that dynamic_cast can fail at that point and the pb->func1(); can do anything including not causing a null pointer exception or anything at all, or even calling the pb1->func  on something that is not B.
An example with an incomplete type would be:
#include <iostream>
using namespace std;
class A {
public:
    virtual void func() {
        cout << "func():: in class A" << endl;
    }
    void func1(){
        cout<< "func1():: in class A";
    }
};
class B;
int main() {
    A a;
    A* pa = &a;
    B* pb = dynamic_cast<B*>(pa);
    return 0;
}
Now if you compile this with G++, you get
y.cc: In function ‘int main()’:
y.cc:20:32: error: cannot dynamic_cast ‘pa’ (of type ‘class A*’) to 
     type ‘class B*’ (target is not pointer or reference to complete type)
     B* pb = dynamic_cast<B*>(pa);
that is all sane C++ compilers will reject such code.