6
$\begingroup$

I would not be surprised if C++ style int &y = x; was no more than int *y = &x; behind the scenes and every use of y was implicitly *y, and that this would this incur the computational expense of pointer indirection. And since the pointer is being stored anyway, there would be no memory-saving benefits here.

However, would implementing C++ style references in a manner that was direct, and more efficient than just using pointers behind the scenes, such as y accessing x on the stack directly the same way simply writing x does (besides a preprocessor macro) be possible? The same way x refers to a certain register at translation time, y could be made to refer to the same register. If there is a struct and a reference is declared to point to the member, could the reference just be made to refer to the register member at translation time without pointers? What would implementing this take?

$\endgroup$
1
  • 1
    $\begingroup$ is compiler inlining a consideration here? Or is this question ruling out compiler inlining? $\endgroup$
    – starball
    Commented Jun 7, 2023 at 0:41

2 Answers 2

11
$\begingroup$

From an implementation perspective, there is one very important difference between a pointer and a reference in C++: references cannot be null.

This is undefined behaviour:

int* px = nullptr;
int &rx = *px;

This doesn't make a difference except in one specific scenario: casting in the presence of multiple inheritance.

Consider this:

struct base1 {
    uint64_t member1;
};

struct base2 {
    uint64_t member2;
};

struct derived : public base1, base2 {
};

What code should this function compile to?

base2& cast_from_derived_to_base2_ref(derived& pd)
{
    return (base2&)pd;
}

If you said "basically add 8", you would be correct. Clang 16.0.0 on x64 at -O2 generates this code:

cast_from_derived_to_base2_ref(derived&):
        lea     rax, [rdi + 8]
        ret

Now, what code should this function compile to?

base2* cast_from_derived_to_base2_ptr(derived* pd)
{
    return (base2*)pd;
}

If you said "the same code", you would be wrong. It would give an incorrect answer if pd were a null pointer; in that case, this function should also return a null pointer.

This is the code that Clang 16.0.0 generates:

cast_from_derived_to_base2_ptr(derived*):
        lea     rax, [rdi + 8]
        test    rdi, rdi
        cmove   rax, rdi
        ret

Of course, there are also high-level semantic differences (references are immutable, for example), but this is the key difference in implementation.

Having said all that, implementing a reference as a value rather than a pointer would be an optimisation. The main barrier would be escape analysis; the compiler would have to be convinced that the location referred to isn't visible from outside the current function (e.g. another thread).

$\endgroup$
4
$\begingroup$

I believe such optimizations are carried out at a later stage of compilation. The compiler back-end, when analyzing the function's IR, is able to notice that a reference is only used within the function, and then replace its uses with the underlying variable directly. The same thing can be automatically applied to pointers as well. It's essentially just a slight modification of the copy propagation algorithm

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .