dynamic_cast is a little more than just casting from the base to a derived type, which is one of it's most common uses.
static_cast is performed at compile time, and is otherwise unaware of the object's internal layout at runtime.
dynamic_cast is performed at runtime using rtti information. Unlike static_cast, the return from a dynamic_cast can be null, if the cast is invalid. This means it can be used as a runtime check to see if a cast is valid. It can only be performed on objects with virtual functions (even if it's only a virtual destructor).
Also of considerable importance is the use of casting in an object created from multiple inheritance. The pointer returned form a dynamic cast may not match, bit for bit, the pointer given. This is because dynamic_cast may have to adjust the value in order to match the 'position' of the type to which a pointer is cast.
Consider an object C derived from both A and B. When the object is created, the pointer you see when observing the object as a type C will be, in colloquial terms, the 'entire' object. Assuming A is the 'first' parent, like:
class C : public A, public B
Then if you use dynamic_cast to cast a pointer of C to an A, the pointer value should be the same, because A is at the "base most" position in the object layout. However, if you cast C to a B, the pointer you get would be at the position of B within the layout, which will be a little farther down from the position of A (that is, in memory, the data of A should appear first, followed by the data of B). Think of it this way, if B is a class that can be instantiated by itself (new B for example), then, if the pointer you got by casting C to B didn't change, the contents of A would still be stored before the content of B, and the result would be invalid.
memory layout of C should be something like
This notion of pointer adjustment is a requirement in casting multiply derived objects, and any casting must perform a runtime adjustment. You'll see this happen "under the hood" if you track the "this" pointer inside member calls on an object of type C.
If you're debugging and watch, "this" will have a value in member functions of class C which will be adjusted when you call member functions in class B - but it will happen without your awareness unless you look for it. Optimization can simplify this, so you'll need to make this observation in a debug build - otherwise the compiler might obviate the necessity through it's own trickery.
Dynamic casts have a runtime penalty, so you should favor designs that don't use casting whenever possible.