两年未写总结博客,今天先来练练手,总结最近遇到的一个crash case。
 注意:以下的分析都基于GCC4.4.6

一、解决crash

我们有一个复杂的排序,涉及到很多个因子,使用自定义排序函数的std::sort做排序。Compare函数类似下文的伪代码:

bool compare(const FakeObj& left, const FakeObj& right) {
if (left.a != right.a) {
return left.a > right.a;
}
if (left.b != right.b) {
return left.b > right.b;
}
....
}

后来,我们给排序函数加了更多的复杂逻辑:

bool compare(const FakeObj& left, const FakeObj& right) {
if (left.a != right.a) {
return left.a > right.a;
}
if (left.b != right.b) {
return left.b > right.b;
}
if (left.c != && right.c != && left.c != right.c) {
// 当C属性都存在的时候使用C属性做比较
return left.c > right.c;
}
if (left.d != right.d) {
return left.d > right.d;
}
....
}

服务发布之后,进程就开始出现偶现的crash,使用gdb查看,调用堆栈如下:

/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../include/c++/4.4.6/bits/stl_algo.h:5260
/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../include/c++/4.4.6/bits/stl_algo.h:2194
/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../include/c++/4.4.6/bits/stl_algo.h:2161
/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../include/c++/4.4.6/bits/stl_algo.h:2084

crash发生位置:在标准库调用compare函数执行比较的时候出现了越界:

这时候,开始怀疑compare函数没有按照标准库的规范实现,查看相关资源:

  https://stackoverflow.com/questions/41488093/why-do-i-get-runtime-error-when-comparison-function-in-stdsort-always-return-t

  https://en.cppreference.com/w/cpp/named_req/Compare

仔细看官方的文档可以发现:

我们的compare函数对c属性的判断,没有严格遵守可传递性:if comp(a,b)==true and comp(b,c)==true then comp(a,c)==true。假设存在A、B、C三个对象,

1、A、B对象有属性c,且A.c > B.c,按照我们的比较函数,这时候A>B;

2、C对象没有c属性,且C.d>A.d,这时候C>A;

3、C对象没有c属性,且B.d < C.d,这时候B>C

综上,A>B 且 B>C,但是C>A,这就违反了strict weak ordering的transitivity。

到这里,我们的case就解决了,但实际上,基于以下几个原因,这个case花费了很长的时间:

1、  我们的compare函数的代码不是逐步添加的,而是一次性写完,导致没有立即怀疑c属性的比较有bug;

2、  对官方文档不够重视,只关注到了非对称性:comp(a,b) ==true then comp(b,a)==false,忽略了可传递性;

辗转了很久才注意到传递性要求。后续在解决问题时,应该更细致,不放过每一个细节。

二、crash更深层的原因

业务上的crash问题已经解决,但crash的直接原因是什么还是未知的,需要继续探索。

找到std::sort的源码:

https://github.com/gcc-mirror/gcc/blob/gcc-4_4-branch/libstdc%2B%2B-v3/include/bits/stl_algo.h

再结合其他人分析std::sort源码的总结:

https://www.cnblogs.com/AlvinZH/p/8682992.html

https://liam.page/2018/09/18/std-sort-in-STL/

简单的总结:std::sort为了提高效率,综合了快排、堆排序、插入排序,可以分为两阶段:

1、  快排+堆排序(__introsort_loop),对于元素个数大于_S_threshold的序列,执行快排,当快排的递归深入到一定层次(__depth_limit)时,不再递归深入,对待排序元素执行堆排序;对于元素个数小于_S_threshold的序列则不处理,交给后面的插入排序。

2、  插入排序(__final_insertion_sort),当元素个数小于_S_threshold时,执行普通的插入排序(__insertion_sort);当大于_S_threshold时,执行两批次的插入排序,首先是普通的插入排序排[0, _S_threshold);然后是无保护的插入排序(__unguarded_insertion_sort),从_S_threshold位置开始排,直到end,注意这里可能还会处理到_S_threshold之前的元素(因为这个函数只用比较结果来判断是否停止,而不强制要求在某个位置点上停止)。

我们的crash发生在__unguarded_insertion_sort阶段,也就是无保护的插入排序。看下这块的代码:

/// This is a helper function for the sort routine.
template<typename _RandomAccessIterator, typename _Compare>
inline void __unguarded_insertion_sort(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType;
for (_RandomAccessIterator __i = __first; __i != __last; ++__i)
std::__unguarded_linear_insert(__i, _ValueType(*__i), __comp);
} /// This is a helper function for the sort routine.
template<typename _RandomAccessIterator, typename _Tp, typename _Compare>
void __unguarded_linear_insert(_RandomAccessIterator __last, _Tp __val,
_Compare __comp) {
_RandomAccessIterator __next = __last;
--__next;
while (__comp(__val, *__next)) {
*__last = *__next;
__last = __next;
--__next;
}
*__last = __val;
}

可以看到,__unguarded_linear_insert 函数比较的终止条件是compare函数返回false,否则就一直排序下去,这里之所以可以这么做,是因为之前的快排+堆排代码保证了[0,X)序列的元素肯定大于(假设是递减排序)[X, end),其中0<X<=_S_threshol,一旦无法保证,则会导致--__next越界,最终导致crash。

再回到我们的crash case,因为compare函数不满足传递性,虽然[0,X)区间的所有元素都大于X,且(X,end]区间的所有元素都小于X,但是并不能保证(X,end]的元素都小于[0,X)区间的元素,在__unguarded_linear_insert函数里,对(X,end]区间的元素执行插入排序时, 某元素大于[0,X)区间的所有元素,这时候就发生了越界crash。

这里使用__unguarded_insertion_sort而不是仅使用__insertion_sort的好处是可以节省边界判断。相关讨论:https://bytes.com/topic/c/answers/819473-questions-about-stl-sort

一个std::sort 自定义比较排序函数 crash的分析过程的更多相关文章

  1. KMP算法的next函数求解和分析过程

    转自 wang0606120221:http://blog.csdn.net/wang0606120221/article/details/7402688 假设KMP算法中的模式串为P,主串为S,那么 ...

  2. ptyhon 编程基础之函数篇(二)-----返回函数,自定义排序函数,闭包,匿名函数

    一.自定义排序函数 在Python中可以使用内置函数sorted(list)进行排序: 结果如下图所示: 但sorted也是一个高阶函数,可以接受两个参数来实现自定义排序函数,第一个参数为要排序的集合 ...

  3. 冒泡排序和sort,sorted排序函数

    冒泡: # 轮数 元素个数 比较次数# 1 6 5# 2 5 4# 3 4 3# 4 3 2# 5 2 1 # 列表有n个元素,则应比较n-1轮,即循环次数n-1 a=[85,7,4,89,34,2] ...

  4. PHP常用数字函数以及排序函数

    一:数字函数 .ceil() 进一取整 示例:ceil(0.9) 结果为1 .abs() 绝对值 示例:abs(-1) 结果为1 .rand() 随机数 示例:rand(1. 100) 1到100 以 ...

  5. c++中std::set自定义去重和排序函数

    c++中的std::set,是基于红黑树的平衡二叉树的数据结构实现的一种容器,因为其中所包含的元素的值是唯一的,因此主要用于去重和排序.这篇文章的目的在于探讨和分享如何正确使用std::set实现去重 ...

  6. std list/vector sort 自定义类的排序就是这么简单

    所以,自己研究了一下,如下:三种方式都可以,如重写<,()和写比较函数compare_index.但是要注意对象和对象指针的排序区别. 1.容器中是对象时,用操作符<或者比较函数,比较函数 ...

  7. (C++)STL排序函数sort和qsort的用法与区别

    主要内容: 1.qsort的用法 2.sort的用法 3.qsort和sort的区别 qsort的用法: 原 型: void qsort(void *base, int nelem, int widt ...

  8. C++ 中的sort()排序函数用法

    sort(first_pointer,first_pointer+n,cmp) 该函数可以给数组,或者链表list.向量排序. 实现原理:sort并不是简单的快速排序,它对普通的快速排序进行了优化,此 ...

  9. hdu 1263 水果 结构的排序+sort自定义排序

    水果 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submissi ...

随机推荐

  1. Tomcat配置https后,并发较大时,频繁超时情况。

    tomcat配置ssl后,出现频繁的访问超时情况. 通过脚本(感谢UCloud的技术支持 金晓帆-): netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a i ...

  2. Flask速成项目:Flask实现计算机资源的实时监控

    很多人都说使用Python开发WEB应用非常方便,那么对于WEB新手来说,到底有多方便呢?本文即将展示给你Python的魔法. 本文将通过一个实例:Flask实现计算机资源的实时监控,迅速带你入门Fl ...

  3. 更新mysql驱动5.1-47 Generated keys not requested. You need to specify Statement.RETURN_GENERATED_KEY

    今天在更新mysql驱动后运行程序突然报如下错误: java.sql.SQLException: Generated keys not requested. You need to specify S ...

  4. CentOS7配置mailx使用外部smtp服务器发送邮件

    转自huskiesir的博客: 发送邮件的两种方式: 1.连接现成的smtp服务器去发送(此方法比较简单,直接利用现有的smtp服务器比如qq.新浪.网易等邮箱,只需要直接配置mail.rc文件即可实 ...

  5. hcna(华为)_Telnet篇

    Telnet提供了一个交互式操作界面,允许终端远程登录到任何可以充当 Telnet服务器的设备.Telnet用户可以像通过Console口本地登录一样对 设备进行操作.远端Telnet服务器和终端之间 ...

  6. Spring系列__02IOC模块简介

    Spring的两大核心功能就是IOC和AOP,这篇文章主要介绍IOC. 简单来说,在面向对象思想下,A类中有一个B类的属性, 那么我们在创建A类时往往需要同时创建一个B类的对象,以便A类对其进行调用. ...

  7. IIS7配置伪静态把后缀名映射为html

    1.在IIS新建站点.[ 创建的时候不用去选择版本和模式,默认即可 ] 2.选中站点,切换到功能试图,找到“处理程序映射",双击之后,在打开窗口右侧的操作栏目下做如下设置: 1) 右边&qu ...

  8. 查找datatable 中的重复记录(只查询一个字段)

    StringBuilder str = new StringBuilder(); var res = new ResParameter() { code = ResponseCode.exceptio ...

  9. spring-cloud-Zuul学习(三)【中级篇】--Filter链 工作原理与Zuul原生Filter【重新定义spring cloud实践】

    这里开始记录zuul中级进阶内容.前面说过了,zuul主要是一层一层的Filter过滤器组成,并且Zuul的逻辑引擎与Filter可用其他基于JVM的语言编写,比如:Groovy. 工作原理 Zuul ...

  10. 详解node + mongoDb(mongoDb安装、运行,在node中连接、增删改查)

    一.序言 好久没写博客了,这次主要聊聊 node 和 mongoDb . 先说明一下技术栈  node + express + mongoose + mongoDb.这篇博客,主要讲述 mongoDb ...