4

给定以下代码,其中xdanglingconst reference是一个消失的对象,因此是未定义的行为。

auto get_vec() { return std::vector<int>{1,2,3,4,5}; }
const auto& x = get_vec().back();

似乎GCC 7.3Clang 6.0MSVC都无法发出警告,即使启用了所有警告。有谁知道在这些情况下是否可以发出警告?在这些情况下const auto&和之间有什么区别吗?auto&&

注意,如果back()按值返回,它不会是未定义的行为,因为生命周期临时对象 x 被扩展为函数 scoop。

长话短说:我有一个代码库,const auto&用作初始化变量的默认方式,出于某种奇怪的原因,这些案例使用 MSVC 正确执行,但是当使用 Clang for android 编译时,每次出现都会导致错误分配的值。目前,该解决方案似乎调查const auto&了整个代码库中的每一个。此外,在许多情况下,const auto&引用引用返回的重对象,因此简单地删除&不是解决方案。

还有一件事,我负责误用const auto&:)

4

4 回答 4

4

我现在唯一能想到的就是将 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”构建运行。

于 2018-03-21T00:43:05.460 回答
4

几乎可以肯定,没有办法对此发出警告。编译器不知道返回的引用对象是否back()会超过该行,如果是的话,没有问题(尽管我很难想到一个现实情况,即非静态成员函数调用临时对象返回对比临时对象寿命长的对象的引用)。

听起来,编写该代码的人阅读了最重要的 const,并从中完全吸取了错误的教训。

于 2018-03-21T00:37:30.447 回答
2

我有一个代码库,其中 const auto& 用作初始化变量的默认方式

哎哟。:(

由于某些奇怪的原因,这些案例使用 MSVC 正确执行,但是当使用 Clang for android 编译时,每次出现都会导致错误分配的值

UB是UB innit。

目前,该解决方案似乎调查了整个代码库中的每个 const auto&

是的。

正如您无法一眼看出特定情况是否“安全”/正确,编译器也无法仅从函数签名中判断。

如果它总是可以访问每个函数的完整定义,它可以在某些情况下向您发出警告(并且分析工具-fsanitize=address会尽力做到这一点),但是编译器没有通用的解决方案来检测悬空运行时的引用。

也恭喜您现在可以收到加薪,因为有罪的员工(作者和审稿人)已被解雇,对吧?:)

于 2018-03-21T00:50:00.747 回答
0

显然,对于上面的示例,可以编写如下内容:

std::vector<int> xx{1,2,3,4,5};
const auto& x = xx.back();

创建整个向量以仅保留其最后一个元素没有多大意义。如果你有一个像上面这样的表达式并且想使用一个表达式,那么你几乎不应该使用auto &开头。

如果对象很大,那么您应该使用移动语义或引用计数。所以也许你会有一个这样的函数GetLastValue,它会按值返回最后一个向量值的副本,然后将它移动到目标目的地。

你真的需要了解你在做什么。否则,您应该使用像 C# 这样的语言,您需要较少了解编译器的内部工作或确切的语言规范。

作为一般规则,我会说您不应该使用auto &,除非您确定要引用返回的项目。auto &我将使用或const auto &将用于基于范围的循环的最常见情况。例如,上面的向量命名为xx,我通常会写:

for (auto & item : xx) …

除非我知道它返回平凡的类型。

于 2018-03-21T01:12:32.580 回答