【问题标题】:How does the C++ algorithm library check the range of the output and not create segfaults when it is smaller than the range of the input?C++算法库如何检查输出的范围,在小于输入的范围时不产生段错误?
【发布时间】:2021-07-23 12:41:18
【问题描述】:

我只是好奇,当您只提供输入范围时,一些 C++ 算法如何检查结果/输出容器的范围? 比如下面的代码

#include <iostream>
#include <algorithm>
#include <vector>

int main()
{
  std::vector<int> a = {4, 2, 1, 7};
  std::vector<int> b = {1, 2};
  
  std::copy(a.begin(), a.end(), b.begin());
  for(auto val : b)
    std::cout << val << std::endl;
}

输出:

4
2

我不明白算法如何知道输出容器 b 的容量为 2。我原以为它假定与输入容器的范围相同,因此会产生某种分段错误。

【问题讨论】:

  • 你查看过std::copy()的文档吗?看看例如cppreference.com 开始。
  • 没有检查;它确实超出了向量并写出边界。这会导致未定义的行为,但这并不意味着可以保证段错误。由于内存保护的工作方式,“小”溢出覆盖可能用于或不用于重要事情的内存是很常见的,没有段错误。
  • 如前所述,没有这样的检查。作为程序员,您有责任确保此类事情不会发生。
  • 此外,它有助于检查互联网上通常的研究起点,这将为您提供例如stepanovpapers.com/STL/DOC.PDF,由 STL 的一位作者编写。此外,请注意 STL 本身已失效,其名称有时用于指代 C++ 标准库,另请参见 stl 标记的描述。

标签: c++ c++11 stl stl-algorithm


【解决方案1】:

copy 算法doesn't check 迭代器范围,你的程序有heap-buffer-overflow 问题。

copy的实现很简单,就是一个简单的循环,可以查看libcxx的实现here。

这是一个普遍的内存问题,感谢编译器中强大的内存清理工具,我们可以快速定位问题。虽然你的程序现在用 SEGSEV 没有崩溃,但是它确实有内存缓冲区溢出,如果这是一个大程序,很可能导致其他代码崩溃(可能在不同的线程,不同的库中),并且很难排除故障原因,因为崩溃代码只是受害者。

使用-fsanitize=address构建,然后我们运行程序后得到清晰的报告:

==227==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000038 at pc 0x7f548b0f003d bp 0x7ffd43cf2ea0 sp 0x7ffd43cf2648                                                                            WRITE of size 16 at 0x602000000038 thread T0                                                                                                                                                                           #0 0x7f548b0f003c in __interceptor_memmove (/lib/x86_64-linux-gnu/libasan.so.5+0xa103c)                                                                                                                            #1 0x55afc64f43b5 in int* std::__copy_move<false, true, std::random_access_iterator_tag>::__copy_m<int>(int const*, int const*, int*) (/tmp/stackoverflow/a.out+0x43b5)                                            #2 0x55afc64f3eeb in int* std::__copy_move_a<false, int*, int*>(int*, int*, int*) (/tmp/stackoverflow/a.out+0x3eeb)                                                                                                #3 0x55afc64f390c in __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > std::__copy_move_a2<false, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > >(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >) (/tmp/stackoverflow/a.out+0x390c)                                                                                          #4 0x55afc64f322e in __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > std::copy<__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > >(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >) (/tmp/stackoverflow/a.out+0x322e)                                                                                                           #5 0x55afc64f2834 in main (/tmp/stackoverflow/a.out+0x2834)                                                                                                                                                        #6 0x7f548ac880b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)                                                                                                                                   #7 0x55afc64f238d in _start (/tmp/stackoverflow/a.out+0x238d)

0x602000000038 is located 0 bytes to the right of 8-byte region [0x602000000030,0x602000000038)
allocated by thread T0 here:
    #0 0x7f548b15e947 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10f947)
    #1 0x55afc64f469c in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (/tmp/stackoverflow/a.out+0x469c)
    #2 0x55afc64f431d in std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (/tmp/stackoverflow/a.out+0x431d)
    #3 0x55afc64f3d35 in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (/tmp/stackoverflow/a.out+0x3d35)
    #4 0x55afc64f3582 in void std::vector<int, std::allocator<int> >::_M_range_initialize<int const*>(int const*, int const*, std::forward_iterator_tag) (/tmp/stackoverflow/a.out+0x3582)
    #5 0x55afc64f2d98 in std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) (/tmp/stackoverflow/a.out+0x2d98)
    #6 0x55afc64f27bf in main (/tmp/stackoverflow/a.out+0x27bf)
    #7 0x7f548ac880b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/lib/x86_64-linux-gnu/libasan.so.5+0xa103c) in __interceptor_memmove
Shadow bytes around the buggy address:
  0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa 00 00 fa fa 00[fa]fa fa fa fa fa fa fa fa
  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: 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):

Memory sanitizer 是一种通用的记忆工具。当专注于 STL 迭代器调试时,STL 库确实有一些有用的工具:

对于Visual Studio 2019,将#define _ITERATOR_DEBUG_LEVEL 1 添加到您的代码中,或添加到项目配置中。运行您的代码时,调用堆栈出现异常:

>   iterator_check.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<int>>>::_Verify_offset(const __int64 _Off) Line 113  C++
    iterator_check.exe!std::_Get_unwrapped_n<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<int>>> &,__int64>(std::_Vector_iterator<std::_Vector_val<std::_Simple_types<int>>> & _It, const __int64 _Off) Line 1318  C++
    iterator_check.exe!std::copy<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<int>>>,std::_Vector_iterator<std::_Vector_val<std::_Simple_types<int>>>>(std::_Vector_iterator<std::_Vector_val<std::_Simple_types<int>>> _First, std::_Vector_iterator<std::_Vector_val<std::_Simple_types<int>>> _Last, std::_Vector_iterator<std::_Vector_val<std::_Simple_types<int>>> _Dest) Line 3813  C++
    iterator_check.exe!main() Line 13   C++
    [External Code] 

我们可以看到调用了一个额外的检查函数_Verify_offset,然后捕获了错误。

对于来自gcc的libstdc++,还有similar debugging mode.:编译器标志-D_GLIBCXX_DEBUG

在 GCC 9 下使用 compiler flag -D_GLIBCXX_DEBUG 构建您的代码并运行它,我们会收到错误报告:

/usr/include/c++/9/bits/stl_algobase.h:471:
In function:
    _OI std::copy(_II, _II, _OI) [with _II =
    __gnu_debug::_Safe_iterator<__gnu_cxx::__normal_iterator<int*,
    std::__cxx1998::vector<int, std::allocator<int> > >,
    std::__debug::vector<int>, std::random_access_iterator_tag>; _OI =
    __gnu_debug::_Safe_iterator<__gnu_cxx::__normal_iterator<int*,
    std::__cxx1998::vector<int, std::allocator<int> > >,
    std::__debug::vector<int>, std::random_access_iterator_tag>]

Error: attempt to subscript a dereferenceable (start-of-sequence) iterator 4
step from its current position, which falls outside its dereferenceable
range.

Objects involved in the operation:
    iterator "__result" @ 0x0x7fff292a8dd0 {
      type = __gnu_cxx::__normal_iterator<int*, std::__cxx1998::vector<int, std::allocator<int> > > (mutable iterator);
      state = dereferenceable (start-of-sequence);
      references sequence with type 'std::__debug::vector<int, std::allocator<int> >' @ 0x0x7fff292a8e70
    }
Aborted

这也很有帮助!

对于来自 LLVM 的 libcxx,还有something similar。不过文档有些不完整,大家可以试试看。

请注意,所有工具都有性能下降,因此它仅在测试构建中有用,不适用于生产构建。

【讨论】:

    猜你喜欢
    • 2013-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 2021-07-20
    • 2012-06-10
    • 2015-01-19
    相关资源
    最近更新 更多