跳转至

C++常见错误:窥探std::Sort的cmp函数用错,导致的程序abort分析

std::sort 函数的Compare函数

c++ std::sort函数是经常被使用到的,但是不知道大家注意没有,定义的Compare函数是需要满足一定条件的。这个条件就是:strict weak ordering。

cppreference 的英文原文:

comparison function object (i.e. an object that satisfies the requirements of Compare) which returns ​true if the first argument is less than (i.e. is ordered before) the second.

strict weak ordering描述了以下三条:第一条描述的是,如果a,b作为cmp的输入,当a和b相等时,return的值必须是false。

一个容易发生且错误使用std::sort的例子

在一段业务代码中,发现了一个不满足cmp函数strict weak ordering的例子,引起了内存相关的问题。以下面的代码为例:

#include <iostream>
#include <vector>
#include <algorithm>

void func()
{
    std::vector<int> v1;

    for (auto i = 0; i < 18; i++)
    {
        v1.push_back(1);
    }
    std::cout << "v1 addr : " << &v1[0] << std::endl;
    std::cout << "befor sort" << std::endl;
    std::sort(v1.begin(), v1.end(), [](int a, int b) { return a >= b; });
    std::cout << "after sort" << std::endl;
}

int main(int, char **)
{
    func();
}
v1 addr : 0x55e0ab6d1f30
befor sort
after sort
double free or corruption (out)
fish: Job 1, './hello/…' terminated by signal SIGABRT (Abort)

崩溃原因分析

执行上面的程序Abort了,原因就是cmp函数中的=等号。为什么会引起这个问题呢,通过gdb查看vector变量附近的内存。

(gdb) b 14
(gdb) b 16
(gdb) r
(gdb) x/26wd 0x55555576df20
0x55555576df20: 0       0       145     0
0x55555576df30: 1       1       1       1
0x55555576df40: 1       1       1       1
0x55555576df50: 1       1       1       1
0x55555576df60: 1       1       1       1
0x55555576df70: 1       1       0       0
0x55555576df80: 0       0
(gdb) until 2
(gdb) x/26wd 0x55555576df20
0x55555576df20: 0       0       145     1
0x55555576df30: 1       0       1       1
0x55555576df40: 1       1       1       1
0x55555576df50: 1       1       1       1
0x55555576df60: 1       1       1       1
0x55555576df70: 1       1       0       0
0x55555576df80: 0       0

可以看到内存数据被改了, 比如0x55555576df20一行地址的145,0被改成了145,1。比如0x55555576df30一行地址的1,1,1,1被改成了1,0,1,1。

当把set *(0x55555576df2c)=0,就不会报错了。这里与vector的析构函数调用有关。

在项目代码中,如果vector中是一个class或者std::string,那么报错现象可能会是std::bad_alloc,析构std::string出错,或者析构class出错,导致问题不易察觉。但本质是因为std::sort的cmp函数定义不正确,导致内存数据被更改,所以代码执行出错了。

这里找到了一篇古老的文章,从源码上进行了分析:一次stl sort调用导致的进程崩溃,讲解得很细致清楚。

评论