您的位置:首页 > 其它

使用std::sort()排序导致程序core问题分析

2015-04-22 22:09 543 查看
一、问题

std::sort()在排序的时候,如果对排序中的仿函数对相等的值返回true,会导致程序core掉。

二、解决办法

让比较函数对相等的值返回false

三、原因分析

std::sort()在排序是分两步进行的,首选进行快速排序,再对快速排序后的进行插入排序。但如果对于容器里面的对象个数较少的时候,快速排序的性能并不理想,所以STL的std::sort()里面增加了一个枚举常量_S_threshold ,用来判断容器里面对象个数,看是否需要进行快速排序。

我们知道快速排序主要就点:

(1)、设置一个比较值,遍历一次所有数据,把小于比较值的放左边,大于比较值的放右边

(2)、对左右两边的数据进行递归快速排序

STL的std::sort()的快速排序里面,对于第一点中的所有数据进行与中间值比较的时候是无边界保护的,它认为用来排序的容器里面对象恒有一个大值和小值,也就是在容器的对象里面,通过comp()函数进行比较,恒有两个值比较后返回false。问题也就出在这里,当我们的容器里面所有值都相等,而comp()函数对相等返回true的时候,在进行快速排序的时候,迭代器就会越界失效。

源代码如下:

//stl_algo.h





四、源码分析

让我们来跟一下源码,首选查到std::sort()代码。

在/usr/include/c++/4.1.2/bits 目录下,打开stl_algo.h代码

搜索sort,如下:





注:sort有两个版本,第一个是直接调用容器对象小于操作比较的,第二个是带仿函数的版本。我们取第二个版本。

真正的比较在2749行开始,进行快速排序。

我们再搜索一下__introsort_loop快速排序函数,如下图:





这里面我们看如下几点:

(1)、第2662行里面把容器里面的元素个数与_S_threshold 进行比较,我们搜索_S_threshold 发现,原来是个枚举常量





这个就是判断是否需要对容器里面的元素进行快速排序。

(2)、第2664行,这里对__depth_limit与0进行比较,这个是快速排序的递归的深度。这个判断是STL对快速排序的一个优化处理,这里不再细说这个问题,详见:《effective stl 》条款31.

(3)、第2671行,这里才真正进行快速排序的代码,其中的std::__median()函数用来取容器中第一个值、中间值 和 最后一个值的中间。

下面让我们进入__unguarded_partition ()这个函数





进入这个函数后,这里一看就很明显了,第2199行代码,如下

while(__comp(*__first,__pivot))

++__first

这两行代码里面,__pivot是中间值,__first是迭代器,假设我们的__comp函数对于相等的值返回true,那么如果一个容器里面最后一段元素所有值都相等,那么__comp(*__first,__pivot)就恒为真。

迭代器往前移的时候,终会移过最后一个元素,于是迭代器失效,程序core。

五、实例

上面啰嗦了这么多,让我们来用个实例跑一下。

(1)、源代码

我们定义个对象,里面有两个成员变量i,j,我们重载小于操作符,通过成员变量i进行排序,对于相等的i时候,返回true。如下:





在main函数里面定义一个vector<comp>容器对象,先往容器里面添加N个comp元素(N=程序第一个函数值)

全部源代码如下:

//sorttest.cpp

1 #include <vector>
2 #include <stdint.h>
3 #include <string>
4 #include <iostream>
5 #include <algorithm>
6
7 using namespace std;
8
9
10 class comp
11 {
12 public:
13 int i;
14 int j;
15 public:
16 comp(){}
17 ~comp(){}
18 public:
19 bool operator<(const comp & r) const
20 {
21 return this->i <= r.i;
22 }
23
24 };
25
26
27
28
29 int main(int argc,char ** argv)
30 {
31 if(argc != 2)
32 {
33 cout<<"wrong arguments.sorttest <vSize>"<<endl;
34 return 0;
35 }
36
37 std::vector<comp> v;
38
39
40 //insert object to vector
41 comp obj;
42 obj.i = 10;
43 for(int a = 0; a < strtoul(argv[1],NULL,10);++a)
44 {
45 obj.j = a;
46 v.push_back(obj);
47 }
48
49
50
51 //print sort before value
52 int i = 1;
53 for(vector<comp>::iterator it = v.begin();it != v.end(); it++)
54 {
55 std::cout<<"befort sort:"<<i<<":"<<"i="<<it->i<<";j="<<it->j<<endl;
56 i++;
57 }
58
59
60 //sort
61 std::sort(v.begin(),v.end());
62
63
64 //print end sort value
65 i = 1;
66 for(vector<comp>::iterator it = v.begin();it != v.end(); it++)
67 {
68 std::cout<<"befort sort:"<<i<<":"<<"i="<<it->i<<";j="<<it->j<<endl;
69 i++;
70 }
71
72 return 0;
73 }

(2)、编译
g++ -g -o sorttest sorttest.cpp

(3)、测试
测试一:
我们先往vector<>容器里面插入10个元素,看下结果:
运行 ./sorttest 10





能正确运行。

测试二:
我们往vector<>容器里面添加16个元素,看下结果:
运行 ./sorttest 16





正确运行

测试三:
我们往vector<>里面插入17个元素,如下:
./sorttest 17





程序core掉

这里,当容器里面的元素个数超过16个,到17个后程序就core掉,这也说明了上面讲到的,对于std::sort()里面,是否进行快速排序的前提条件是,容器里面元素的个数要大于_S_threshold 的枚举常量值,而stl的这个默认值是16,所以当容器里面元素个数超过16个的时候,就会进行快速排序,而这个测试程序的容器里面所以有的值对小于操作符都是返回true的,所以程序core掉。

我们再修改一下代码,把重载小于操作符的 <= 比较改成 < ,再来看看程序是否会core。
如下:





注:这里为了测试查看方便 ,不再打印日志,把打印日志的也一并注释掉

修改后源码如下:
//sorttest.cpp

1 #include <vector>
2 #include <stdint.h>
3 #include <string>
4 #include <iostream>
5 #include <algorithm>
6
7 using namespace std;
8
9
10 class comp
11 {
12 public:
13 int i;
14 int j;
15 public:
16 comp(){}
17 ~comp(){}
18 public:
19 bool operator<(const comp & r) const
20 {
21 //return this->i <= r.i;
22 return this->i < r.i;
23 }
24
25 };
26
27
28
29
30 int main(int argc,char ** argv)
31 {
32 if(argc != 2)
33 {
34 cout<<"wrong arguments.sorttest <vSize>"<<endl;
35 return 0;
36 }
37
38 std::vector<comp> v;
39
40
41 //insert object to vector
42 comp obj;
43 obj.i = 10;
44 for(int a = 0; a < strtoul(argv[1],NULL,10);++a)
45 {
46 obj.j = a;
47 v.push_back(obj);
48 }
49
50
51
60
61
62 //sort
63 std::sort(v.begin(),v.end());
64
65
74 std::cout<<"sort success"<<endl;
75 return 0;
76 }

编译运行,我们直接往容器里面插入100个元素,运行如下:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: