下面这段代码稳定复现bug
#include <bits/stdc++.h>
using namespace std;
bool compare (int a, int b){
return a <= b;
}
int main()
{
vector<int> vec;
for(int i = 0; i < 17; i++)
vec.push_back(1); // 数组中是17个1
sort(vec.begin(), vec.end(), compare);
for (auto i : vec)
cout << i << endl;
}
修复bug有两种方式
- 将比较器中的a<=b改成a<b
- 将数组长度17改成16
熟悉stl规范的知道,出现bug的原因是sort要求传入一个,即满足:当a和b相等的时候,a compare b返回false,显然<=不符合条件,<是符合的。
那core是怎么产生的呢,具体原因如下:
- 为什么和长度有关?
stl的sort函数在容器长度小于16时会直接使用插入排序,大于16时会先使用快排将数据分成长度小于16的数据段,再在段内使用插入排序。因此长度小于16时不会出现bug。 - core在哪里?
stl中使用的快排经过优化,选择头、尾、中间,三个数的中位数作为pivot,代码如下:
inline _RandomAccessIterator
__unguarded_partition_pivot(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
_RandomAccessIterator __mid = __first + (__last - __first) / 2; // 取中间
std::__move_median_to_first(__first, __first + 1, __mid, __last - 1, __comp); // 将头、尾、中间三者的中位数作为pivot并放在容器头
return std::__unguarded_partition(__first + 1, __last, __first, __comp); // 将小于pivot的数放在左边,大于pivot的数放在右边,pivot在中间
}
问题出现在__unguarded_partition函数,前一步是取了3个值的中位数作为pivot的,因此这个函数有一个假设,即全部数据中一定有至少一个值比pivot大,一个比pivot小。
_RandomAccessIterator
__unguarded_partition(_RandomAccessIterator __first,
_RandomAccessIterator __last,
_RandomAccessIterator __pivot, _Compare __comp)
{
while (true)
{
while (__comp(__first, __pivot)) //找到比pivot大的那个数的位置,当数组中的所有数相等的时候,这句会一直执行直到越界在界外找到符合条件的位置
++__first;
--__last;
while (__comp(__pivot, __last)) //找到比pivot小的那个数的位置,当数组中的所有数相等的时候,这句会一直执行直到越界在界外找到符合条件的位置
--__last;
if (!(__first < __last))
return __first;
std::iter_swap(__first, __last);
++__first;
}
}
当传入的比较器不是弱比较器时,如果数组中所有数字相等,注释的2个while循环无法正确退出。相反,如果比较器满足1 compare 1为false,则不会越界。
除此之外
弱比较器还用在set、map等有序容器中,map的key要求唯一,判断两个key是否相等的方式是!(a<b)&&!(b<a),即a不小于b同时b不小于a则认为a和b相等。如果传入的是<=,则a<=b成立,b<=a也成立,会认为这两个数不相等而插入,因此打破了map的树结构,引入了未定义行为。