Unless two types are the “same type,” they are different types:
int and long are different types even if they use exactly the same numbers of bits for the same things. We know this because C 2018 6.3.1.1 tells us they have different ranks (a hierarchy in rules about promotions and conversions).
struct foo { int x; } and struct bar { int x; } are different types even though they use exactly the same numbers of bits for the same things.
typedef defines a new name for an existing type; the new name is not a different type, just a different name for the same type.
char * and const char * are different types, as are int * and long *, again even if int and long are the same size.
Beyond that, C has a notion of “compatible” types. Roughly speaking, two types are compatible if one can be used in place of the other in certain situations. For example, an int [3] (array of three int) is compatible with an int [] (array of unspecified number of int) because an array of three int can supply a requirement for an array of an unspecified number of int. Filling in the size just completes the int [].
C 2018 6.7.6.1 tells us:
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
From this, we can see that not only are two different pointers types are different types but that some pointer types are not compatible with other pointer types. The rules about compatibility are somewhat involved, since they need to reconcile various flexible or incomplete parts of types, such as functions without complete parameter lists.
C 2018 6.3.2.3 tells us about conversions between pointer types, including:
- A pointer to an object type may be converted to
void * and back.
- A pointer to a non-qualified type may be converted to a pointer to a qualified types (such as
char * to const char *).
- A pointer to an object type may be converted to a pointer to another object type if it has the required alignment.
- A pointer to a function type may be converted to a pointer to another function type.
Again, the fact that only certain conversions are defined indicates to us that different types of pointers are different, and possibly in fundamental ways that could prevent a C implementation from converting between them.