我们看到2个例子都存在悬空引用:
例子 A:
我们使用同一个主要的函数来调用它们两个:
在示例A中,我得到了编译器警告和读取悬空引用时预期的段错误。在示例B中,既没有警告也没有段错误,并返回了意外的a正确值。
为什么B没有警告?
已在两台机器上进行测试。 - 编译器7.4.0 Ubuntu - 编译器7.5.0 Ubuntu
这不是关于什么是悬空引用的问题,而是关于警告和扩展编译器行为的问题!这是未定义的行为。是的,程序理论上可以做任何事情,甚至可能会使世界爆炸或实际工作。“这是未定义的行为”并不是一个令人满意的答案,因为它只回答了程序能够做什么,而不是为什么编译器甚至没有检测到Example B中的问题。
因此,这不是这个问题的重复。
程序在Example B中似乎没有运行时错误,而在那里也没有警告,这可能只是巧合,也可能不是。
我已经利用Compiler Explorer查看了在g++ 7.5下生成的代码,特别是
int& getref()
{
int a;
return a;
}
例子 B:
int& getref()
{
int a;
int&b = a;
return b;
}
我们使用同一个主要的函数来调用它们两个:
int main()
{
cout << getref() << '\n';
cout << "- reached end" << std::endl;
return 0;
}
在示例A中,我得到了编译器警告和读取悬空引用时预期的段错误。在示例B中,既没有警告也没有段错误,并返回了意外的a正确值。
为什么B没有警告?
已在两台机器上进行测试。 - 编译器7.4.0 Ubuntu - 编译器7.5.0 Ubuntu
这不是关于什么是悬空引用的问题,而是关于警告和扩展编译器行为的问题!这是未定义的行为。是的,程序理论上可以做任何事情,甚至可能会使世界爆炸或实际工作。“这是未定义的行为”并不是一个令人满意的答案,因为它只回答了程序能够做什么,而不是为什么编译器甚至没有检测到Example B中的问题。
因此,这不是这个问题的重复。
程序在Example B中似乎没有运行时错误,而在那里也没有警告,这可能只是巧合,也可能不是。
我已经利用Compiler Explorer查看了在g++ 7.5下生成的代码,特别是
getref()
在汇编中的操作。示例A: getref():
push rbp
mov rbp, rsp
mov eax, 0
pop rbp
ret
示例 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
现在我的汇编语言有点生疏了,但是在例子B中涉及到更多的堆栈内存,这理论上会创建更多悬垂引用的可能性,因此更容易被检测到,因为不太可能被优化。我对编译器在处理寄存器时能够检测到悬垂引用感到惊讶,但在实际内存(如示例B的汇编代码)中未能检测到。
也许有人可以解释为什么B比A更难以检测。
如果感兴趣,这里是示例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
.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
-O2
时引用被合并,并且分析部分捕获到这是一个本地引用。我对不比较未优化的汇编的评论与此观点无直接关系。 - Human-Compiler