首页 > 解决方案 > std::condition_variable 的内存位置可能导致 futex 错误

问题描述

我们的软件中有一个错误,结果令人恐惧:

futex 工具返回了意外的错误代码。

我们将其追溯到一个问题,即 std::condition_variable 在 malloc 的内存区域内的位置导致 futex 错误。如果 std::condition_variable 未在 16 字节字上对齐 - 那么当您尝试wait. 在示例中,前两个wait_for调用有效,但最后一个调用因 futex 错误而中止程序。

void futex_error()
{
    /* init */
    std::mutex mtx;

    /* Normal one works  */
    std::cout << "Doing normal" << "\n";
    std::condition_variable* con_var = (std::condition_variable*)malloc(sizeof(std::condition_variable));
    new (con_var) std::condition_variable{};

    {
        std::unique_lock<std::mutex> lck(mtx);
        con_var->wait_for(lck, std::chrono::seconds(1));
    }

    /* Clean */
    con_var->std::condition_variable::~condition_variable();
    free(con_var);

    std::cout << "Doing 16 bytes" << "\n";
    /* Works on 16 byte alignment  */
    uint8_t* ptr_16 = (uint8_t*)malloc(sizeof(std::condition_variable) + 16);
    std::condition_variable* con_var_16 = new (ptr_16 + 16) std::condition_variable{};

    {
        std::unique_lock<std::mutex> lck(mtx);
        con_var_16->wait_for(lck, std::chrono::seconds(1));
    }

    /* Clean */
    con_var_16->std::condition_variable::~condition_variable();
    free(ptr_16);

    std::cout << "Doing 1 byte" << "\n";
    /* Futex error */
    uint8_t* bad_ptr = (uint8_t*)malloc(sizeof(std::condition_variable) + 1);
    std::condition_variable* bad = new (bad_ptr + 1) std::condition_variable{};

    {
        std::unique_lock<std::mutex> lck(mtx);
        bad->wait_for(lck, std::chrono::seconds(1)); //<--- error here?
    }

    /* Clean */
    bad->std::condition_variable::~condition_variable();
    free(con_var);
}

我似乎找不到有关 futex 错误的文档以及对齐为何会导致这种情况。有谁知道为什么会发生这种情况?这是在使用 gcc 9.3 的 linux(Arch 和 Ubuntu)上。

标签: c++memory-managementmemory-alignment

解决方案


为什么对齐会导致这种情况

来自C++ 对齐草案 p1

对象类型具有对齐要求([basic.fundamental]、[basic.compound]),这些要求限制了可以分配该类型对象的地址。

表达方式:

new (bad_ptr + 1) std::condition_variable{};

bad_ptr + 1在未对齐的系统上调用未定义的行为alignof(std::condition_variable). 使用 gcc10在Godboltalignof(std::confition_variable)上测试等于8.

两种bad->访问都是未对齐的访问,并且都是未定义的行为。

有谁知道为什么会发生这种情况?

检查strace可执行文件的执行输出,我们可以看到:

futex(0x557da3e262e9, FUTEX_WAIT_BITSET_PRIVATE, 0, {tv_sec=2439, tv_nsec=619296657}, FUTEX_BITSET_MATCH_ANY) = -1 EINVAL (Invalid argument)

因为uaddr应该是调用指针int的第一个参数futex没有对齐_Alignof(int),内核在这里检测到它并 futex 返回EINVAL。标准库然后退出应用程序,这是未定义行为的完美行为。


推荐阅读