0

I'm writing a basic function that is testing the validity of an adress (the adress must be in a given memory range).

I was thinking to use uintptr_t. Something like:

bool verify_address(uintptr_t pAddress)
{
    if ( (pAddress < ...) ||  (pAddress > ...))
        return false;
    else
        return true;
}

But after looking to uintptr_t type I am wondering if this is correct. As far as I understood there is no guarantee that the value of a data pointer variable remains the same after a cast to uintptr_t (only guarantee is that it can be casted back to the same value).

For example: if I have a pointer unsigned int *ptr = (unsigned int *)0x20000000 is (uintptr_t)ptr == 0x20000000 necessarily true ?

Guillaume Petitjean
  • 2,408
  • 1
  • 21
  • 47
  • 1
    In practice, on most systems, yes. (I don't know for sure what the C standard says.) – pts Mar 13 '23 at 14:15
  • 1
    The pointer/integer conversion is implementation-dependent, so your implementation must document how it works. Implementations are encouraged (but not required) to use the conversion that is most natural for the architecture. Assuming natural conversions, your `verify_address` will work for linear architectures, but will fail for segmented ones. – Raymond Chen Mar 13 '23 at 14:25
  • Nothing like address is known to the C language. C language knows `*reference*`\ – 0___________ Mar 13 '23 at 15:27

2 Answers2

2

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.

Lundin
  • 195,001
  • 40
  • 254
  • 396
1

Values of data (object) pointers are not certainly linear encoded. Math (like <) on the integer type uintptr_t is not specified to work like math on the pointers themselves.

Converting an object pointer to uintptr_t and then back to the same pointer type results in an equivalent pointer, not necessarily the same bit pattern.

(unsigned int *)0x20000000 itself is problematic. @Lundin

Something like OP's code will "work" on many systems. Example @Eric Postpischil. It is not strictly portable.

Even (u)intptr_t is an optional type - yet very common.

Often the best approach is to re-think the overall goal.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 2
    We might note that GCC defines [conversions of pointers to integers and back to preserve the bits in the representations](https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Arrays-and-pointers-implementation.html#Arrays-and-pointers-implementation) when the widths are the same (and sign extend or truncate when the widths differ), so this will work when using GCC in implementations with flat address spaces. And Clang claims general GCC compliance too, so it should work as well. – Eric Postpischil Mar 13 '23 at 14:42
  • 1
    It is _not_ UB. "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." – Lundin Mar 13 '23 at 15:04
  • @Lundin Right you are. – chux - Reinstate Monica Mar 13 '23 at 15:14
  • (unsigned int *)0x20000000 itself is problematic. Could you elaborate ? – Guillaume Petitjean Mar 13 '23 at 15:34
  • "Often the best approach is to re-think the overall goal" well the goal is to check that a parameter of a function is valid (ie. is located in a valid adress range). On ARm cortex so no exotic memory mapping – Guillaume Petitjean Mar 13 '23 at 15:35
  • @GuillaumePetitjean Why does code need to check that a parameter of a function is valid by looking at it address range? What is the source of such addresses. Let us get to the higher level goal. Perhaps next time post a [mcve] that demos the need. – chux - Reinstate Monica Mar 13 '23 at 16:44
  • "Why does code need to check that a parameter of a function is valid by looking at it address range? " Context is functional safety (like IEC61508). Every function parameter must be checked (even if already checked in caller function). Checking only null pointers is a bit "light". – Guillaume Petitjean Mar 14 '23 at 08:24