自定义 Std::sort 对比函数时的陷阱

Table Of Contents

前言:记录这个案例的目的,是为了说明:

  • 堆内存溢出这样的错误,可能是由于错误地自定义了一个 compare 函数而引起的。

这两者之间似乎没什么直接联系,若不是碰巧有 Address Sanitizer 的帮助,基于之前的认知,可能发现问题和定位原因都很困难。


之前在 LeetCode 上做【179. 最大数 】这道题时写了如下代码:

 1bool comp3(const int a, const int b) {
 2    if (a == b) {
 3        return true;
 4    }
 5    string sa = to_string(a);
 6    string sb = to_string(b);
 7    return sa + sb > sb + sa;
 8}
 9class Solution {
10public:
11    string largestNumber(vector<int>& nums) {
12        sort(nums.begin(), nums.end(), comp3);
13        //...
14    }
15};

可以看到,此处涉及自定义 std::sort 的比对方法。此段代码提交后,平台报“执行错误”:

1AddressSanitizer: heap-buffer-overflow on address ...

可以看出错误是由 Address Sanitizer 报出来的。在 Linux 上通过如下方式编译后运行,可以复现该错误:

1g++ -std=c++14 -O -g -fsanitize=address ./main.cpp
2./a.out
 1=================================================================
 2==129056==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61400000ffd0 at pc 0x403093 bp 0x7fff13dbe9b0 sp 0x7fff13dbe9a0
 3READ of size 4 at 0x61400000ffd0 thread T0
 4    #0 0x403092 in operator()<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, __gnu_cxx::__normal_iterator<int*, std::vector<int> > > /usr/include/c++/4.9/bits/predefined_ops.h:121
 5    #1 0x403092 in __unguarded_partition<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, __gnu_cxx::__ops::_Iter_comp_iter<bool (*)(int, int)> > /usr/include/c++/4.9/bits/stl_algo.h:1901
 6    #2 0x403092 in __unguarded_partition_pivot<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, __gnu_cxx::__ops::_Iter_comp_iter<bool (*)(int, int)> > /usr/include/c++/4.9/bits/stl_algo.h:1922
 7    #3 0x403092 in void std::__introsort_loop<__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, long, __gnu_cxx::__ops::_Iter_comp_iter<bool (*)(int, int)> >(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, long, __gnu_cxx::__ops::_Iter_comp_iter<bool (*)(int, int)>) /usr/include/c++/4.9/bits/stl_algo.h:1952
 8    #4 0x40329d in __sort<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, __gnu_cxx::__ops::_Iter_comp_iter<bool (*)(int, int)> > /usr/include/c++/4.9/bits/stl_algo.h:1967
 9    #5 0x40329d in sort<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, bool (*)(int, int)> /usr/include/c++/4.9/bits/stl_algo.h:4717
10    #6 0x40329d in Solution::largestNumber(std::vector<int, std::allocator<int> >&) main.cpp:23
11    #7 0x401d62 in main main.cpp:46
12    #8 0x7fa8defec83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
13    #9 0x401508 in _start (/home/g00535345/a.out+0x401508)
140x61400000ffd0 is located 0 bytes to the right of 400-byte region [0x61400000fe40,0x61400000ffd0)
15allocated by thread T0 here:
16    #0 0x7fa8df9e8eaf in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.1+0x57eaf)
17    #1 0x401c44 in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) /usr/include/c++/4.9/ext/new_allocator.h:104
18    #2 0x401c44 in std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) /usr/include/c++/4.9/bits/alloc_traits.h:488
19    #3 0x401c44 in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) /usr/include/c++/4.9/bits/stl_vector.h:170
20    #4 0x401c44 in _M_range_initialize<int const*> /usr/include/c++/4.9/bits/stl_vector.h:1287
21    #5 0x401c44 in vector /usr/include/c++/4.9/bits/stl_vector.h:377
22    #6 0x401c44 in main main.cpp:43
23SUMMARY: AddressSanitizer: heap-buffer-overflow /usr/include/c++/4.9/bits/predefined_ops.h:121 operator()<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, __gnu_cxx::__normal_iterator<int*, std::vector<int> > >
24Shadow bytes around the buggy address:
25  0x0c287fff9fa0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
26  0x0c287fff9fb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
27  0x0c287fff9fc0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
28  0x0c287fff9fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
29  0x0c287fff9fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
30=>0x0c287fff9ff0: 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa
31  0x0c287fffa000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
32  0x0c287fffa010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
33  0x0c287fffa020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
34  0x0c287fffa030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
35  0x0c287fffa040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
36Shadow byte legend (one shadow byte represents 8 application bytes):
37  Addressable:           00
38  Partially addressable: 01 02 03 04 05 06 07 
39  Heap left redzone:       fa
40  Heap right redzone:      fb
41  Freed heap region:       fd
42  Stack left redzone:      f1
43  Stack mid redzone:       f2
44  Stack right redzone:     f3
45  Stack partial redzone:   f4
46  Stack after return:      f5
47  Stack use after scope:   f8
48  Global redzone:          f9
49  Global init order:       f6
50  Poisoned by user:        f7
51  Contiguous container OOB:fc
52  ASan internal:           fe
53==129056==ABORTING

可见这是一个堆越界的错误,从堆栈中可以看出是 std::sort 出了问题,那么出问题的只可能是 comp3() 这个函数,但是看来看去都不知道问题出在哪里。无奈之下使出夹逼法,发现删去开头的 if 判断,问题就消失了。

仔细查阅 std::sort 的文档,发现其在对入参的描述处有提到:

1template< class RandomIt, class Compare >
2void sort( RandomIt first, RandomIt last, Compare comp );

Parameters

Type requirements

  • ……
  • Compare must meet the requirements of Compare .

然后顺着这个链接就可以找到对 comp 的要求,其中一个就是:

For all a, comp(a,a)==false

破案了,这里的 comp3() 不符合这个要求……

所以最后的最后,把那块去掉就好了:

1class Solution {
2public:
3    string largestNumber(vector<int>& nums) {
4        sort(nums.begin(), nums.end(), [](int a, int b){
5            return to_string(a) + to_string(b) > to_string(b) + to_string(a);
6        });
7        //...
8    }
9};

并查集与搜索的联系
"Tricks and Craft" in Bash Scripting