问题描述
我只是好奇,当您只提供输入范围时,某些 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
算法 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*) (/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::allocator<int> > >,std::allocator<int> > > >(__gnu_cxx::__normal_iterator<int*,std::allocator<int> > >) (/tmp/stackoverflow/a.out+0x390c) #4 0x55afc64f322e in __gnu_cxx::__normal_iterator<int*,std::allocator<int> > > std::copy<__gnu_cxx::__normal_iterator<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*,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.: compiler flag -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::__debug::vector<int>,std::random_access_iterator_tag>; _OI =
__gnu_debug::_Safe_iterator<__gnu_cxx::__normal_iterator<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::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。不过文档有点不全,可以试一下。
请注意所有工具都有性能下降,因此它仅在测试构建中有用,不适合生产构建。