Home > front end >  C &-Operator Overload - Addressable R-Value Syntax-Cheat
C &-Operator Overload - Addressable R-Value Syntax-Cheat

Time:09-02

I stumbled upon something weird. We all know the taking the address of the return value from a function-call is not allowed - since it's an r-value.

But this seems to be fine:

#include <iostream>

struct Foo {
    int val;
    Foo* operator &() { // <-- just like normal "&"
        return this;
    }
};

Foo test() {
    return Foo{3};
} 

int main() {  
    Foo* p = &test(); // <-- wtf? allowed and works
    printf("%d", p->val);
}

Maybe by overloading the "&"-operator, the return value of first brought into scope of the caller (living on the stack, I guess) and therefore addressable.

Yields deterministic results on (Clang-14 and GCC-12)

Question is: Undefined Behaviour or Legal?


Assembler output clearly shows, that the return value is cached.

[Clang 14.0.0 without arguments]
main:                                   # @main
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    call    test()
    mov     dword ptr [rbp - 16], eax <---
    lea     rdi, [rbp - 16]           <---
    call    Foo::operator&()
    mov     qword ptr [rbp - 8], rax
    mov     rax, qword ptr [rbp - 8]
    mov     esi, dword ptr [rax]
    movabs  rdi, offset .L.str
    mov     al, 0
    call    printf
    xor     eax, eax
    add     rsp, 16
    pop     rbp
    ret

CodePudding user response:

There's two separate questions here. First, will it compile? Second, will it work as intended?

The code in question compiles because

Foo* p = &test();

translates into

Foo* p = test().operator&();

which is perfectly legal. For example, compare against something like this:

std::string makeAString() {
    return "whee!";
}

auto length = makeAString().length(); // Perfectly fine

This code is legal, just as yours is.

However, there's a separate question about whether it will work correctly. And as mentioned in the comments, no it won't, because the pointer you're storing in p points to an object whose lifetime has ended. Using that pointer results in undefined behavior.

So, in that sense, your code will compile, but it's not a good idea to run it. :-)

CodePudding user response:

The behavior of your program is undefined.

User-defined operators have different semantics than the built-in operators. User-defined operators behave just like the function calls they actually are. Since your overloaded operator& is not ref-qualified, there's no reason it can't be called on an rvalue.

This doesn't change the lifetime of the object though. There is no pointer-lifetime-extension. Foo* p = &test() still yields a dangling pointer, since the lifetime of the temporary Foo object returned by test ends at the end of the full expression. Attempting to dereference p beyond that point results in undefined behavior. Remember, undefined behavior really means undefined. Anything can happen, and "appears to work" is included in "anything".

  •  Tags:  
  • c
  • Related