当输出的范围小于输入的范围时,C++算法库如何检查输出的范围而不创建segfault



我只是想知道,当你只提供输入的范围时,一些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。我本以为它假设的范围与输入容器相同,因此会产生某种分段错误。

copy算法不检查迭代器范围,您的程序存在heap-buffer-overflow问题。

copy的实现很简单,只是一个简单的循环,您可以在这里查看libcxx的实现。

这是一个常见的内存问题,多亏了编译器中强大的内存清理工具,我们可以快速找到问题。虽然您的程序现在没有与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):

内存清理程序是一种通用的内存工具。当专注于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++,也有类似的调试模式编译器标志-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,也有类似的东西。但是这份文件有些不完整,你可以试一试。

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

最新更新