I'm trying to understand how type safety is preserved for value types and for reference types, specifically in languages with managed memory (such as C#). I understand that reference types (i.e. classes) have data allocated onto a managed heap, with a reference to that data allocated on the stack. Is there metadata about the type of the data passed around with the pointer? Is there a new 'pointer type' created along with the class itself?
There usually is metadata about the runtime class of an object contained within the object itself, regardless of what language you're working in. This metadata is essential in languages like C# that have automatic memory management. In order to clean up an object that is no longer referred to by any other objects, you need to clean up all of its members too, which means deciphering exactly what class it is.
Just how that metadata is stored is up to the individual language implementation. Some languages make classes themselves into objects, and the metadata will consist of a pointer to the class. For example, this approach is used in some Common Lisp implementations (for reasons that we don't need to get into).
Other languages are a little more subtle about it. For example, in many object-oriented languages, an object stores a reference to a virtual function table or vtable. The vtable is a struct consisting of pointers to all of the implementations of that class's methods; the language runtime is responsible for setting up all of the vtables. Every instance of a class will store a reference to the same copy of that class's vtable, so you can figure out what class an object belongs to by asking which vtable it points to. This is exactly how object-oriented programming was mimicked in plain old C (see this article) and how it works under the hood in C++.
These kinds of complications are necessary because of inheritance. If I have an object
x and I know it's of class
x could really be an instance of any subclass
A, and the language runtime needs to ascertain what the real class is in order to do method dispatch or garbage collection properly. In C#, struct types don't have inheritance at all, unlike class types. So a struct might have no metadata -- if the type of
S is a struct type, it can't possibly be anything else.
I hope this at least addresses your narrow question about metadata. As for the much broader issue of type safety, have a look at Pierce's Types and Programming Languages, especially the chapters on subtyping.