std::sort coredump 说起

本文探讨了C++ std::sort函数可能导致程序coredump的原因,特别是在自定义比较函数时。当比较函数对相等元素返回true,违反Strict_weak_orderings原则,可能导致coredump。当元素数量大于等于16时,std::sort使用快速排序,这种情况下问题尤为明显。此外,文章还提到一种隐藏更深的坑,即错误的比较函数可能导致数据错误但程序不崩溃,提醒开发者要特别注意。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@(c++)

core 的原因

c++ 标准库 sort() 默认采用 < 这个 operator 来排序的, 另个一个重载函数增加第三个参数,指定一个比较的函数,函数接受两个参数。
对于基础类型(int,float..),直接调用 sort(start,end) 即可,对于非基础类型的结构体,可以通过重载对象的 < 运算符或者提供一个比较函数。
详见

本文从一个 core 说起 相信很多人在编写使用 sort 都在这个地方翻车,

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

class CompareGreater {
    public:
        bool operator()(const int* left, const int* right)
        {
            return *left >= *right;
        }
};

int main(int argc, char *argv[])
{
    vector<int *> verPInt;
    for (int i = 0; i < 18; i++) { // will coredump
        int *pInt = new int;
        if (pInt == NULL) {
            cout << "new faile" << endl;
            goto final;
        }
        *pInt = 1;
        verPInt.push_back(pInt);
    }
    sort(verPInt.begin(), verPInt.end(), CompareGreater());
    for (vector<int *>::iterator iter = verPInt.begin(); iter != verPInt.end(); ++iter) {
        cout << **iter;
    }
    cout << endl;
final:
    for (vector<int *>::iterator iter = verPInt.begin(); iter != verPInt.end(); ++iter) {
        delete *iter;
        *iter = NULL;
    }
    return 0;
}

gdb 查看堆栈,发现是 core 在比较函数里面。

Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000055e88121e00c in CompareGreater::operator()(int const*, int const*) ()

上述代码,所有元素值相同,当个数大于等于 16个 的时候就会 coredump,查看说明,core 的原因是 :

std::sort()在排序时,比较函数对相等的元素应该返回 false!如果返回 true,会导致程序coredump

因为定义 cmp(x,x) 返回 true 不符合 Strict_weak_orderings,标准库算法里面很多都要求满足 Strict_weak_orderings,使用时需要格外注意,避免翻车。
上述例子代码只需修改比较函数中,将 >= 改为 >即可修复。

为什么是元素个数大于等于 16个 呢,从 STL 源码可以发现,由于 std::sort() 的排序分2种,当元素个数 >16 (_S_threshold = 16) 时选择快速排序,<=16 个则选择插入排序 (对象少时快排性能不理想)。按照快排原理,每次都是遍历所有值和一个中间值比较,小的放左边,大的放右边。从STL源代码可看出,std::sort() 在遍历比较时,是没有加边界保护的。如果比较相等的元素返回真,则在极端情况下 (如所有元素值相等时) __first 会出现访问越界,导致coredump。
STL 源码 : /usr/include/c++/7/bits/stl_algo.h(具体目录)

深层次的坑

写测试代码时候,发现比较元素从 vector<int *> 改为 vector<int>,比较函数同样错误的写为 >=,运行程序并不会 core,但是打印比较好的数据,发现数据错了!!坑爹啊,这样的坑更深沉。

测试代码


参考

<think>嗯,用户这次明确询问std::mutex导致coredump的原因及解决方法,看来遇到了多线程调试的典型难题。从引用内容看,用户应该是有一定C++底层知识的开发者,可能正在调试生产环境的核心转储问题。需要特别注意两点:一是用户引用了多个技术片段(如eh_terminate.cc源码和shared_ptr实现),说明需要底层细节;二是用户特意强调"不是真正需求",暗示要聚焦在mutex相关崩溃场景。翻看引用材料,[2]明确提到多线程内存越界会引发coredump,而[3]涉及线程创建失败的极端情况。用户没直接问这些,但需要关联思考——mutex使用不当可能与其他线程问题叠加。比如[4]的promise案例若缺少mutex保护共享数据,同样会崩溃。这提醒我回答时要区分纯mutex问题和复合型问题。技术层面需分三层展开:首先是mutex自身行为,比如重复解锁或销毁后使用,这在[1]的terminate机制里能看到abort痕迹;其次是资源边界情况,[3]的mmap耗尽会影响mutex初始化;最后是使用规范,[2]强调的同步机制正是关键。用户示例中的__scoped_lock实现也暗示了锁竞争的重要性。用户可能更关心实战解决方案而非理论。重点准备:如何用gdb分析mutex导致的coredump(结合引用[2]的调试建议)、预防性编程技巧(如RAII包装)、特殊场景处理(如信号处理函数中使用pthread_mutex)。对于高级需求,还要考虑futex竞争监测、TSAN工具使用等延伸内容。考虑到用户引用shared_ptr细节([5]),潜在提示智能指针与mutex的配合问题,例如跨线程传递带mutex的对象时控制块析构顺序。虽然用户没明说,但在"解决方法"里应该补充该用例。最后准备用钢铁厂排班这样的具象比喻帮助理解竞争条件,呼应引用[4]的生产者-消费者模型。</think> 以下结合C++标准库实现原理和多线程开发经验,详细分析`std::mutex`导致coredump的常见原因及解决方案: --- ### **一、核心原因分析** #### 1. **未初始化或重复销毁** ```cpp std::mutex* m = nullptr; m->lock(); // 操作空指针 → SIGSEGV ``` - **原理**:未初始化的mutex指针或重复调用析构函数,导致访问非法内存[^2][^5]。 #### 2. **跨线程所有权问题** ```cpp void thread_func(std::mutex& m) { m.unlock(); // 非锁定线程尝试解锁 → 未定义行为 } ``` - **违反标准**:线程必须解锁自己锁定的互斥量,否则触发`std::system_error`或直接abort[^1][^2]。 #### 3. **资源耗尽(极端情况)** - **虚拟地址耗尽**:线程过多或大内存分配导致`mmap`区域不足(引用[3]),使mutex初始化失败。 - **表现**:`pthread_mutex_init`返回`EAGAIN`,若未处理则后续使用崩溃[^3]。 #### 4. **死锁引发连锁反应** ```cpp std::mutex m1, m2; // 线程A:锁m1后尝试锁m2 // 线程B:锁m2后尝试锁m1 // 死锁! ``` - **后果**:未处理死锁可能导致线程阻塞或超时,若结合异常处理不当触发`std::terminate()`[^1]。 --- ### **二、解决方案** #### 1. **规范使用RAII包装** ```cpp { std::lock_guard<std::mutex> lock(m); // 自动管理锁定/解锁 } // 作用域结束自动释放 ``` - **优势**:避免遗漏解锁,保证异常安全[^2][^4]。 #### 2. **检查初始化状态** ```cpp std::mutex m; // 确保构造完成 assert(&m != nullptr); // 运行时检查(调试阶段) ``` #### 3. **处理资源耗尽** - **监控系统限制**: ```bash sysctl vm.max_map_count # Linux查看mmap上限 ``` - **解决方案**: 1. 增大虚拟内存限制:`sysctl -w vm.max_map_count=655300` 2. 优化线程数量或改用线程池[^3]。 #### 4. **死锁防御策略** - **顺序锁定**:统一锁定顺序(如先锁m1再锁m2)。 - **使用`std::lock()`原子锁定多个mutex**: ```cpp std::lock(m1, m2); // 原子性锁定 std::lock_guard<std::mutex> lk1(m1, std::adopt_lock); std::lock_guard<std::mutex> lk2(m2, std::adopt_lock); ``` #### 5. **调试工具辅助** - **Valgrind/ASan**:检测内存越界和初始化问题。 - **GDB分析coredump**: ```bash gdb -c corefile ./a.out bt full # 查看完整堆栈 ``` - **日志追踪**:在锁定/解锁点添加日志,定位非法操作线程。 --- ### **三、最佳实践总结** | **风险点** | **防御措施** | |---------------------|--------------------------------------| | 未初始化 | 声明为全局/成员变量,禁用指针 | | 跨线程解锁 | 严格使用RAII,禁止手动lock/unlock | | 死锁 | 固定锁定顺序,或使用`std::lock()` | | 资源耗尽 | 限制线程数,监控系统资源 | > 通过RAII和原子锁定操作可预防80%的mutex相关崩溃,剩余问题需结合系统级资源管理[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值