throw() with clang++ 显示可能的内存泄漏

throw() with clang++ shows possible memory leak

我有一段代码,用 g++ 编译时没有显示任何内存泄漏。 然而,使用 clang++ 编译时同样显示,可能存在内存泄漏。

这是踪迹,

==7115==
==7115== HEAP SUMMARY:
==7115==     in use at exit: 16 bytes in 1 blocks
==7115==   total heap usage: 2,324 allocs, 2,323 frees, 2,166,060 bytes allocated
==7115==
==7115== 16 bytes in 1 blocks are still reachable in loss record 1 of 1
==7115==    at 0x4C2BFB9: calloc (vg_replace_malloc.c:762)
==7115==    by 0x4129830: __cxxabiv1::__calloc_with_fallback(unsigned long, unsigned long) (in /opt/xxx/lib64/libc++abi.so.1)
==7115==    by 0x4128946: __cxa_get_globals (in /opt/xxx/lib64/libc++abi.so.1)
==7115==    by 0x412B287: __cxa_throw (in /opt/xxx/lib64/libc++abi.so.1)
==7115==    by 0x4E712AE: Lib::GenCmd::RaiseException(Status, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >) (LibBase.cpp:291)
==7115==
==7115== LEAK SUMMARY:
==7115==    definitely lost: 0 bytes in 0 blocks
==7115==    indirectly lost: 0 bytes in 0 blocks
==7115==      possibly lost: 0 bytes in 0 blocks
==7115==    still reachable: 16 bytes in 1 blocks
==7115==         suppressed: 0 bytes in 0 blocks
==7115==
==7115== For counts of detected and suppressed errors, rerun with: -v

好吧,无法共享代码片段,但我可以告诉你 RaiseException() 是我调用 throw() 的函数(在第291) 一个例外。这是函数片段:

void GenCmd::RaiseException(Status status, std::string AdditionalMsg) throw(Status) {
    s_last_error = GetStatusString(status);

    if (false == AdditionalMsg.empty()) {
        s_last_error = s_last_error + AdditionalMsg;
    }
    throw(status);
}

Status 是一个结构,定义如下(以及默认、参数化和复制构造函数)

typedef struct _Status {
    const u64_t m_status          : 8;
    const u64_t ReservedByte1     : 8;
    const u64_t m_action          : 8;
    const u64_t ReservedByte3     : 5;
    const u64_t m_testbit1        : 1;
    const u64_t m_testbit2        : 1;
    const u64_t m_cmd_failure     : 1;
    const u64_t m_module_code     : 4;
    const u64_t m_file_code       : 8;
    const u64_t ReservedByte7     : 4;
    const u64_t m_line_no         : 16;
}Status

事实是,GCC 没有发现泄漏,但只有 Clang 出现,这让我认为这是 Clang 的一些问题。 (对于 Clang,我的意思是它也可以是 libcxxabi

我正在浏览 clang 的源代码,& __cxa_get_globals()calloc() 的函数打电话。我还不确定 clang 的执行流程。

任何想法或任何输入都可以确认这是一个 Clang 问题而不是我的代码问题?

这是我使用的 clang 版本。代码是用 C++11 编译的,另外还有 '-stdlib=libc++, '-lc++', '-lc++abi'。

[user~]$ clang --version
clang version 7.1.0
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin

更新 我写了另一个虚拟代码来查看 Clang 的行为,& 看来问题实际上与 Clang (libc++abi) 有关。

看看下面的原始代码

#include <iostream>
#include <stdexcept>

class Positive {
    int m_number1;
 public:
    Positive() : m_number1(10) {
    }

    Positive(int no) {
        if (no < 0) {
//            throw 100;
              throw std::invalid_argument("Send positive nu");
        } else {
            m_number1 = no;
        }
    }

    ~Positive() {
    }

    void print() {
        std::cout<< "Value of member is: " <<m_number1 <<std::endl;
    }
};

int main() {
    try {
        Positive p1;
        p1.print();

        Positive p2(100);
        p2.print();

        Positive p3(-10);
        p3.print();

    } catch(...) {
        std::cout << "Some Exception occured" <<std::endl;
    }
   return 0;
}

即使在执行上面的代码时,我在 Valgrind 上也看到了相同的结果。这是输出:

[user]$ valgrind --leak-check=full --leak-resolution=high --show-leak-kinds=all ./a.out
==119789== Memcheck, a memory error detector
==119789== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==119789== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==119789== Command: ./a.out
==119789==
Value of member is: 10
Value of member is: 100
Some Exception occured
==119789==
==119789== HEAP SUMMARY:
==119789==     in use at exit: 16 bytes in 1 blocks
==119789==   total heap usage: 3 allocs, 2 frees, 201 bytes allocated
==119789==
==119789== 16 bytes in 1 blocks are still reachable in loss record 1 of 1
==119789==    at 0x4C2BFB9: calloc (vg_replace_malloc.c:762)
==119789==    by 0x40FF830: __cxxabiv1::__calloc_with_fallback(unsigned long, unsigned long) (in /usr/local/lib/libc++abi.so.1.0)
==119789==    by 0x40FE946: __cxa_get_globals (in /usr/local/lib/libc++abi.so.1.0)
==119789==    by 0x4101287: __cxa_throw (in /usr/local/lib/libc++abi.so.1.0)
==119789==    by 0x4014B0: Positive::Positive(int) (in /home/user/test/a.out)
==119789==    by 0x4010F9: main (in /home/user/test/a.out)
==119789==
==119789== LEAK SUMMARY:
==119789==    definitely lost: 0 bytes in 0 blocks
==119789==    indirectly lost: 0 bytes in 0 blocks
==119789==      possibly lost: 0 bytes in 0 blocks
==119789==    still reachable: 16 bytes in 1 blocks
==119789==         suppressed: 0 bytes in 0 blocks
==119789==
==119789== For counts of detected and suppressed errors, rerun with: -v
==119789== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
[user]$

有趣的是,它显示了 3 次分配。我假设它与第三个对象相关,但我如何确保它被清除(或未自行分配)?

可能,同样的事情可以帮助我修复我的原始代码。

我也发现了这个问题。如果您的代码至少抛出一次任何东西,它实际上会发生,所以最简单的重现场景就是 运行 这段代码:

int
main()
{
    try {
        throw 42;
    } catch (int) {}
}

但是,如果您多次抛出,仍然只有一个 16 字节的“泄漏”块(这并不是真正的泄漏,因为它仍然可以从某个全局变量访问)。深入研究 libc++abi 库 sources 表明,此类块实际上为每个线程分配一次以保存一些异常处理上下文,并且当线程被销毁时每个块都被释放,因为它使用 TLS 并注册了适当的析构函数。所以毕竟,它看起来完全安全,不是问题。