The answer is very simple: struct
and union
types can be declared as opaque types, ie: without an actual definition of the struct
or union
details. If the representation of pointers was different depending on the structures' details, how would the compiler determine what representation to use for opaque pointers appearing as arguments, return values, or even just reading from or storing them to memory.
The natural consequence of the ability to manipulate opaque pointer types is all such pointers must have the same representation. Note however that pointers to struct
and pointers to union
may have a different representation, as well as pointers to basic types such as char
, int
, double
...
Another distinction regarding pointer representation is between pointers to data and pointers to functions, which may have a different size. Such a difference is more common in current architectures, albeit still rare outside operating system and device driver space. 64-bit for function pointers seems a waste as 4GB should be amply sufficient for code space, but modern architectures take advantage of this extra space to store pointer signatures to harden code against malicious attacks. Another use is to take advantage of hardware that ignores some of the pointer bits (eg: x86_64 ignores the top 16 bits) to store type information or to use NaN values unmodified as pointers.
Furthermore, the near/far/huge pointer attributes from legacy 16 bit code were not correctly addressed by this remark in the C Standard as all pointers could be near, far or huge. Yet the distinction between code pointers and data pointers in mixed model code was covered by it and seems still current on some OSes.
Finally, Posix mandates that all pointers have the same size and representation so mixed model code should quickly become a historical curiosity.
It is arguable that architectures where the representation is different for different data types are vanishingly rare nowadays and it be high time to clean up the standard and remove this option. The main objection is support for architectures where the addressable units are large words and 8-bit bytes are addressed using extra information, making char *
and void *
larger than regular pointers. Yet such architectures make pointer arithmetics very cumbersome and are quite rare too (I personally have never seen one).