是否有任何 C++ 编译器可以对悬空引用发出警告?
Is there any C++ compiler which can issue a warning for a dangling reference?
给定以下代码,其中 x
是一个消失的对象的悬挂 const reference
,因此是未定义的行为。
auto get_vec() { return std::vector<int>{1,2,3,4,5}; }
const auto& x = get_vec().back();
似乎 GCC 7.3、Clang 6.0 和 MSVC 都无法发出警告,即使启用了所有警告。
有谁知道在这些情况下是否可以发出警告?
const auto&
和 auto&&
在这些情况下有什么区别吗?
请注意,如果 back()
按值 return,则不会是未定义行为,因为生命周期临时对象 x 已扩展为函数 scoop。
长话短说:我有一个代码库,其中 const auto&
被用作初始化变量的默认方式,出于某些奇怪的原因,这些情况使用 MSVC 正确执行,但是当使用Clang for android,每次出现都会导致错误分配值。目前,该解决方案似乎调查了整个代码库中的每个 const auto&
。
此外,在许多情况下,const auto&
指的是一个重对象 return 通过引用编辑,因此简单地删除 &
不是解决方案。
还有一件事,我对 const auto&
的错误使用负责 :)
几乎可以肯定没有办法对此发出警告。编译器不知道 back()
返回的引用对象是否会超过该行,如果是,也没有问题(尽管我很难想象 non-static 调用临时对象的成员函数 returns 对比临时对象存在时间更长的对象的引用)。
听起来好像编写该代码的人阅读了 the most important const,并从中吸取了完全错误的教训。
我现在唯一能想到的就是将 CLANG 与 -fsanitize=address 一起使用。但是当然这只会在 运行 时有所帮助,但是你会得到像这样的好东西:
==102554==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000020 at pc 0x00000050db71 bp 0x7ffdd3a5b770 sp 0x7ffdd3a5b768
READ of size 4 at 0x603000000020 thread T0
#0 0x50db70 in main (/home/user/testDang+0x50db70)
#1 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
#2 0x41a019 in _start (/home/user/testDang+0x41a019)
0x603000000020 is located 16 bytes inside of 20-byte region [0x603000000010,0x603000000024)
freed by thread T0 here:
#0 0x50a290 in operator delete(void*) (/home/user/testDang+0x50a290)
#1 0x50eccf in __gnu_cxx::new_allocator<int>::deallocate(int*, unsigned long) (/home/user/testDang+0x50eccf)
#2 0x50ec9f in std::allocator_traits<std::allocator<int> >::deallocate(std::allocator<int>&, int*, unsigned long) (/home/user/testDang+0x50ec9f)
#3 0x50ec2a in std::_Vector_base<int, std::allocator<int> >::_M_deallocate(int*, unsigned long) (/home/user/testDang+0x50ec2a)
#4 0x50e577 in std::_Vector_base<int, std::allocator<int> >::~_Vector_base() (/home/user/testDang+0x50e577)
#5 0x50e210 in std::vector<int, std::allocator<int> >::~vector() (/home/user/testDang+0x50e210)
#6 0x50db16 in main (/home/user/testDang+0x50db16)
#7 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
previously allocated by thread T0 here:
#0 0x509590 in operator new(unsigned long) (/home/user/testDang+0x509590)
#1 0x50e9ab in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (/home/user/testDang+0x50e9ab)
#2 0x50e94b in std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (/home/user/testDang+0x50e94b)
#3 0x50e872 in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (/home/user/testDang+0x50e872)
#4 0x50e2ff in void std::vector<int, std::allocator<int> >::_M_range_initialize<int const*>(int const*, int const*, std::forward_iterator_tag) (/home/user/testDang+0x50e2ff)
#5 0x50deb7 in std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) (/home/user/testDang+0x50deb7)
#6 0x50dafb in main (/home/user/testDang+0x50dafb)
#7 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
SUMMARY: AddressSanitizer: heap-use-after-free (/home/user/testDang+0x50db70) in main
Shadow bytes around the buggy address:
0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c067fff8000: fa fa fd fd[fd]fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
也许您有自动化单元测试,您可以在 "sanizizer" 构建时轻松 运行。
I have a code base where const auto& is used as the default way of initializing variables
哎呀。 :(
for some odd reason these cases executes correctly using MSVC, but when compiled with Clang for android, every occurance results in a wrongly assigned value
UB 是 UB innit。
For now the solution seems to investigate every const auto& in the whole code base
是的。
正如您无法一眼看出特定情况是否 "safe"/正确一样,编译器也无法仅通过函数签名来判断。
如果它始终可以访问每个函数的完整定义,它会在某些情况下向您发出警告(并且像 -fsanitize=address
这样的分析工具会尽力而为),但是没有general-case 编译器在运行时检测悬挂引用的解决方案。
也祝贺你现在可以得到加薪,因为有罪的员工(作者和审稿人)已经被解雇了,对吧? :)
显然,对于上面的例子,可以这样写:
std::vector<int> xx{1,2,3,4,5};
const auto& x = xx.back();
创建一个完整的向量以仅保留其最后一个元素没有多大意义。如果你有一个类似上面的表达式并且想使用单个表达式,那么你几乎不应该使用 auto &
开头。
如果对象很大,那么你应该使用移动语义或引用计数。所以也许你会有一个像 GetLastValue
这样的函数,它会 returns 按值复制最后一个向量值,然后将其移动到目标位置。
你真的需要明白你在做什么。否则,您应该使用像 C# 这样的语言,这样您就不需要了解编译器的内部工作或确切的语言规范。
作为一般规则,我会说您不应使用 auto &
,除非您确定您想要引用返回的项目。当我使用 auto &
或 const auto &
时,最常见的情况是基于范围的循环。例如上面的vector命名为xx
,我一般会这样写:
for (auto & item : xx) …
除非我知道它 returns 琐碎的类型。
给定以下代码,其中 x
是一个消失的对象的悬挂 const reference
,因此是未定义的行为。
auto get_vec() { return std::vector<int>{1,2,3,4,5}; }
const auto& x = get_vec().back();
似乎 GCC 7.3、Clang 6.0 和 MSVC 都无法发出警告,即使启用了所有警告。
有谁知道在这些情况下是否可以发出警告?
const auto&
和 auto&&
在这些情况下有什么区别吗?
请注意,如果 back()
按值 return,则不会是未定义行为,因为生命周期临时对象 x 已扩展为函数 scoop。
长话短说:我有一个代码库,其中 const auto&
被用作初始化变量的默认方式,出于某些奇怪的原因,这些情况使用 MSVC 正确执行,但是当使用Clang for android,每次出现都会导致错误分配值。目前,该解决方案似乎调查了整个代码库中的每个 const auto&
。
此外,在许多情况下,const auto&
指的是一个重对象 return 通过引用编辑,因此简单地删除 &
不是解决方案。
还有一件事,我对 const auto&
的错误使用负责 :)
几乎可以肯定没有办法对此发出警告。编译器不知道 back()
返回的引用对象是否会超过该行,如果是,也没有问题(尽管我很难想象 non-static 调用临时对象的成员函数 returns 对比临时对象存在时间更长的对象的引用)。
听起来好像编写该代码的人阅读了 the most important const,并从中吸取了完全错误的教训。
我现在唯一能想到的就是将 CLANG 与 -fsanitize=address 一起使用。但是当然这只会在 运行 时有所帮助,但是你会得到像这样的好东西:
==102554==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000020 at pc 0x00000050db71 bp 0x7ffdd3a5b770 sp 0x7ffdd3a5b768
READ of size 4 at 0x603000000020 thread T0
#0 0x50db70 in main (/home/user/testDang+0x50db70)
#1 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
#2 0x41a019 in _start (/home/user/testDang+0x41a019)
0x603000000020 is located 16 bytes inside of 20-byte region [0x603000000010,0x603000000024)
freed by thread T0 here:
#0 0x50a290 in operator delete(void*) (/home/user/testDang+0x50a290)
#1 0x50eccf in __gnu_cxx::new_allocator<int>::deallocate(int*, unsigned long) (/home/user/testDang+0x50eccf)
#2 0x50ec9f in std::allocator_traits<std::allocator<int> >::deallocate(std::allocator<int>&, int*, unsigned long) (/home/user/testDang+0x50ec9f)
#3 0x50ec2a in std::_Vector_base<int, std::allocator<int> >::_M_deallocate(int*, unsigned long) (/home/user/testDang+0x50ec2a)
#4 0x50e577 in std::_Vector_base<int, std::allocator<int> >::~_Vector_base() (/home/user/testDang+0x50e577)
#5 0x50e210 in std::vector<int, std::allocator<int> >::~vector() (/home/user/testDang+0x50e210)
#6 0x50db16 in main (/home/user/testDang+0x50db16)
#7 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
previously allocated by thread T0 here:
#0 0x509590 in operator new(unsigned long) (/home/user/testDang+0x509590)
#1 0x50e9ab in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (/home/user/testDang+0x50e9ab)
#2 0x50e94b in std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (/home/user/testDang+0x50e94b)
#3 0x50e872 in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (/home/user/testDang+0x50e872)
#4 0x50e2ff in void std::vector<int, std::allocator<int> >::_M_range_initialize<int const*>(int const*, int const*, std::forward_iterator_tag) (/home/user/testDang+0x50e2ff)
#5 0x50deb7 in std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) (/home/user/testDang+0x50deb7)
#6 0x50dafb in main (/home/user/testDang+0x50dafb)
#7 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
SUMMARY: AddressSanitizer: heap-use-after-free (/home/user/testDang+0x50db70) in main
Shadow bytes around the buggy address:
0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c067fff8000: fa fa fd fd[fd]fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
也许您有自动化单元测试,您可以在 "sanizizer" 构建时轻松 运行。
I have a code base where const auto& is used as the default way of initializing variables
哎呀。 :(
for some odd reason these cases executes correctly using MSVC, but when compiled with Clang for android, every occurance results in a wrongly assigned value
UB 是 UB innit。
For now the solution seems to investigate every const auto& in the whole code base
是的。
正如您无法一眼看出特定情况是否 "safe"/正确一样,编译器也无法仅通过函数签名来判断。
如果它始终可以访问每个函数的完整定义,它会在某些情况下向您发出警告(并且像 -fsanitize=address
这样的分析工具会尽力而为),但是没有general-case 编译器在运行时检测悬挂引用的解决方案。
也祝贺你现在可以得到加薪,因为有罪的员工(作者和审稿人)已经被解雇了,对吧? :)
显然,对于上面的例子,可以这样写:
std::vector<int> xx{1,2,3,4,5};
const auto& x = xx.back();
创建一个完整的向量以仅保留其最后一个元素没有多大意义。如果你有一个类似上面的表达式并且想使用单个表达式,那么你几乎不应该使用 auto &
开头。
如果对象很大,那么你应该使用移动语义或引用计数。所以也许你会有一个像 GetLastValue
这样的函数,它会 returns 按值复制最后一个向量值,然后将其移动到目标位置。
你真的需要明白你在做什么。否则,您应该使用像 C# 这样的语言,这样您就不需要了解编译器的内部工作或确切的语言规范。
作为一般规则,我会说您不应使用 auto &
,除非您确定您想要引用返回的项目。当我使用 auto &
或 const auto &
时,最常见的情况是基于范围的循环。例如上面的vector命名为xx
,我一般会这样写:
for (auto & item : xx) …
除非我知道它 returns 琐碎的类型。