We see 2 Examples which both have dangling references: Example A:
int& getref()
{
        int a;
        return a;
}
Example B:
int& getref()
{
        int a;
        int&b = a;
        return b;
}
We call them both with this same main function:
int main()
{
        cout << getref() << '\n';
        cout << "- reached end" << std::endl;
        return 0;
}
In Example A I get a compiler warning and the expected Segfault on reading the dangling reference. In Example B I get neither the warning nor the Segfault and returns the correct value of a unexpectedly.
Why is there no warning with B?
Tested on 2 machines so far.
- Compiler 7.4.0 Ubuntu
- Compiler 7.5.0 Ubuntu
This is not a question about what dangling references are, its about warnings and in extent compiler behavior! This is undefined behaviour. Yes, the program could theoretically do anything, even explode the world or actually work. "This is undefined behavior" is not a satisfactory answer, as it answers only what the program is capable of doing and not why the compiler does not even detect this in Example B.
This is therefore no duplicate of this question.
The fact that the program seems to have no runtime errors reproducably in the Example B where there is also no warning could just be a coincidence or not.
I've taken the liberty of using Compiler Explorer to look at generated code
under g++ 7.5, specifically what getref() does in assembly.
Example A:
    getref():
    push    rbp
    mov     rbp, rsp
    mov     eax, 0
    pop     rbp
    ret
    
Example B:
    getref():
    push    rbp
    mov     rbp, rsp
    lea     rax, [rbp-12]
    mov     QWORD PTR [rbp-8], rax
    mov     rax, QWORD PTR [rbp-8]
    pop     rbp
    ret
Now my assembly is a bit rusty, but in Example B even more stack memory seems to be involved, which would, theoretically, create even more potential of memory being referenced dangly and therefore more detectable, as it is less likely to be subjected to optimization. I am surprised by the compiler detecting the dangling reference whilst only handling registers, but not when actual memory is involved, like in the assembly of Example B.
Maybe anyone here as any insight as to why B is harder to detect than A.
Here is the complete assembly of Example B in case it is of interest:
getref():
        push    rbp
        mov     rbp, rsp
        lea     rax, [rbp-12]
        mov     QWORD PTR [rbp-8], rax
        mov     rax, QWORD PTR [rbp-8]
        pop     rbp
        ret
.LC0:
        .string "- reached end"
main:
        push    rbp
        mov     rbp, rsp
        call    getref()
        mov     eax, DWORD PTR [rax]
        mov     esi, eax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     esi, 10
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
        mov     eax, 0
        pop     rbp
        ret
__static_initialization_and_destruction_0(int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        cmp     DWORD PTR [rbp-4], 1
        jne     .L7
        cmp     DWORD PTR [rbp-8], 65535
        jne     .L7
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        call    __cxa_atexit
.L7:
        nop
        leave
        ret
_GLOBAL__sub_I_getref():
        push    rbp
        mov     rbp, rsp
        mov     esi, 65535
        mov     edi, 1
        call    __static_initialization_and_destruction_0(int, int)
        pop     rbp
        ret
 
    