Search code examples
c++language-lawyercpu-registersmemory-mapping

Is it legal for a pointer to point to a C++ register?


Let's say a C++ compiler compiled code for an architecture where CPU registers are not memory-mapped. And also let's say that same compiler reserved some pointer values for CPU registers.

For example, if the compiler, for whatever reason (optimization reasons for example), uses register allocation for a variable (not talking about register keyword), and we print the value of the reference to that variable, the compiler would return one of the reserved "address values".

Would that compiler be considered standard-compliant?

From what I could gather (I haven't read the whole thing - Working Draft, Standard for Programming Language C++), I suspect that the standard doesn't mention such a thing as RAM memory or operative memory and it defines its own memory model instead and the pointers as representation of addresses (could be wrong).

Now since registers are also a form of memory, I can imagine that an implementation which considers registers to be a part of the memory model could be legal.


Solution

  • Is it legal for a pointer to point to C++ register?

    Yes.

    Would that compiler be considered standard-compliant?

    Sure.

    C++ is not aware of "registers", whatever that is. Pointers point to objects (and functions), not to "memory locations". The standard describes the behavior of the program and not how to implement it. Describing behavior makes it abstract - it's irrelevant what is used in what way and how, only the result is what matters. If the behavior of the program matches what the standard says, it's irrelevant where the object is stored.

    I can mention intro.memory:

    1. A memory location is either an object of scalar type that is not a bit-field or a maximal sequence of adjacent bit-fields all having nonzero width.

    and compund:

    Compound types can be constructed in the following ways:

    • pointers to cv void or objects or functions (including static members of classes) of a given type,

    [...] Every value of pointer type is one of the following:

    • a pointer to an object or function (the pointer is said to point to the object or function), or
    • a pointer past the end of an object ([expr.add]), or
    • the null pointer value for that type, or
    • an invalid pointer value.

    [...] The value representation of pointer types is implementation-defined. [...]

    To do anything useful with a pointer, like apply * operator unary.op or compare pointers expr.eq they have to point to some object (except edge cases, like NULL in case of comparisons). The notation of "where" exactly objects are stored is rather vague - memory stores "objects", memory itself can be anywhere.


    For example, if compiler, for whatever reason(optimization reasons for example), uses register allocation for a variable(not talking about register keyword), we print the value of the reference to that variable, the compiler would return one of the reserved "address values"

    std::ostream::operator<< calls std::num_put and conversion for void* is %p facet.num.put.virtuals. From C99 fprintf:

    [The conversion %]p

    The argument shall be a pointer to void. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.

    But note that from C99 fscanf:

    [The conversion specified %]p

    Matches an implementation-defined set of sequences, which should be the same as the set of sequences that may be produced by the %p conversion of the fprintf function. The corresponding argument shall be a pointer to a pointer to void. The input item is converted to a pointer value in an implementation-defined manner. If the input item is a value converted earlier during the same program execution, the pointer that results shall compare equal to that value; otherwise the behavior of the %p conversion is undefined.

    What is printed has to be unique for that object, that's all. So a compiler has to pick some unique value for addresses in registers and print them whenever the conversion is requested. The conversions from/to uintptr_t will have also be implemented in an implementation-defined manner. But it would be all in implementation - the implementation details of how the behavior of the code is achieved is invisible to a C++ programmer.