您的位置:首页 > 其它

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

2014-05-07 10:15 645 查看
一、问题
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个元素,运行如下:

 



正确运行。
 
至此,用《effective stl 》里面一句话总结全文:永远让比较函数对相等的值返回false
 
---(全文完)---
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: