是什么原因导致std :: sort()访问超出范围的地址

时间:2014-06-04 21:37:20

标签: c++ sorting stl strict-weak-ordering

我理解使用std :: sort()时,compare函数必须是严格的弱顺序,否则会因访问地址超出范围而崩溃。 (https://gcc.gnu.org/ml/gcc-bugs/2013-12/msg00333.html

但是,当比较函数不是严格的弱顺序时,为什么std :: sort()访问越界地址?它想要比较什么?

我也想知道STL中是否还有其他陷阱需要注意。

2 个答案:

答案 0 :(得分:14)

首先,使用不符合要求的比较器调用算法是未定义的行为,任何事情都会发生......

但除此之外,我假设您有兴趣知道如果比较器不好,哪种类型的实现最终可能会超出范围。 在首先访问元素之前,实现是否应该检查边界?即在调用比较器之前

答案是绩效,这只是可能导致此类问题的可能因素之一。排序算法有不同的实现,但通常情况下,std::sort是建立在快速排序变体之上的,它会在不同的排序算法(例如mergesort)上退化,以避免快速排序最差情况下的性能。

快速排序的实现选择一个轴,然后围绕枢轴分割输入,然后对两侧进行独立排序。选择枢轴有不同的策略,但常见的是三个中间值:算法获取第一个,最后一个和中间元素的值,选择三个中值并将其用作枢轴值。

从概念上讲,分区从左侧走,直到找到一个不小于枢轴的元素,然后从右侧走,试图找到一个小于枢轴的元素。如果两个游标相遇,则分区完成。如果找到不合适的元素,则交换值,并且该过程在两个游标确定的范围内继续。从左边走到找到要交换的元素的循环看起来像:

while (pos < end && value(pos) < pivot) { ++pos; }

虽然通常分区不能假设pivot的值在范围内,但是quicksort 知道它是,毕竟它选择了范围内元素的枢轴。在这种情况下,常见的优化是将中值的值交换为循环的最后一个元素。这可以保证value(pos) < pivotpos == end之前为真(最差情况:pos == end - 1)。这里的含义是我们可以放弃检查范围的结束,我们可以使用unchecked_partition(选择您的名字),条件更简单:

while (/*pos < end &&*/ value(pos) < pivot) ++pos;

除了<拼写为comparator(value(pos), pivot)之外,一切都非常好。现在,如果comparator未正确实现,您最终可能会遇到comparator(pivot,pivot) == true并且光标将超出范围。

请注意,这只是算法优化的一个示例,它可以删除边界检查性能:假设有效顺序,如果快速排序,则不可能走出上述循环中的数组调用此修改后的分区之前,将数据透视设置为最后一个元素

回到问题:

  

在首先访问元素之前,实现是否应该检查边界?即在调用比较器之前

不,如果它通过证明它不会走出数组而删除了边界检查,但是该证明建立在比较器有效的前提下。

答案 1 :(得分:1)

std::sort确实要求给定的比较器建立严格的弱排序,否则排序实际上没有多大意义。

至于访问超出范围,您发布的链接是错误报告,即它不应该实际执行此操作。像任何其他软件一样的编译器可以并且将会有错误。正如亚当所指出的那样,这个特殊的错误报告被拒绝了,因为它不是一个真正的错误。

当您没有严格的弱排序时,究竟发生了什么并没有被标准定义,这样做是没有意义的,因此被标准排除在外。因此,省略未定义未定义表示任何事情都可能发生,甚至超出范围。

至于避免“陷阱”,只需要了解您使用的算法和功能的要求。对于C ++,我经常使用一个很好的参考站点:cppreference

the page of std::sort上的内容:

  

comp - 比较函数对象(即满足Compare要求的对象),如果第一个参数小于(即在之前排序)第二个参数,则返回true。

指向Compare

的说明链接