The formal C standard text is C17 6.3.2.3:
An integer may be converted to any pointer type. Except as previously specified, the
result is implementation-defined, might not be correctly aligned, might not point to an
entity of the referenced type, and might be a trap representation.
Any pointer type may be converted to an integer type. Except as previously specified, the
result is implementation-defined. If the result cannot be represented in the integer type,
the behavior is undefined. The result need not be in the range of values of any integer
type.
Notably there is no requirement that it should be possible to convert back again after doing either of the above two conversions on its own.
So there are some situations like alignment, trap representations etc where conversions might invoke undefined behavior. The range of an integer type isn't relevant in case of uintptr_t
, since it is guaranteed to be "large enough". Most of the time converting to/from pointers to uintptr_t
is merely "implementation-defined", as in depends on the system and compiler.
As for how things work in the real world outside the C standard, the vast majority of all systems let a pointer simply correspond to an address (physical or virtual). They don't have to, but in practice that's what the vast majority of computers/compilers do. For the rare occasion where you have exotic pointer formats or exotic memory maps, it is usually dealt with by adding a non-standard extension such as near
/far
keywords, rather than keeping different internal pointer representations depending on type.
Or in case of Harvard architectures, perhaps other non-standard extensions depending on if dealing with code or data. On such CPUs function pointers and object pointers may behave differently, as one example. Whereas that's less likely on von Neumann CPUs.
Either way, as soon as you have converted a pointer to an integer, it's of course all implementation-defined from there on. The C compiler doesn't really know or care about the various memory ranges present on a certain system, that business is handled by the linker/linker script.
So in case you have a non-exotic pointer format and know the valid address ranges for the given memory, then sure you can do arithmetic on uintptr_t
just fine. Code like that is common in embedded systems, while coding bootloaders, on-chip flash/eeprom drivers and similar.