20

Clang 3.9 非常重用临时人员使用的内存。

此代码为 UB(简化代码):

template <class T>
class my_optional
{
public:
    bool has{ false };
    T value;

    const T& get_or_default(const T& def)
    {
        return has ? value : def;
    }
};

void use(const std::string& s)
{
    // ...
}

int main()
{
    my_optional<std::string> m;
    // ...
    const std::string& s = m.get_or_default("default value");
    use(s); // s is dangling if default returned
}

我们有大量类似上面的代码(my_optional只是一个简单的例子来说明它)。

因为 UB 所有的 clang 编译器从 3.9 开始重用这个内存,这是合法的行为。

问题是:如何在编译时检测这种悬空引用,或者在运行时使用 sanitizer 之类的东西?没有clang消毒剂可以检测到它们。

更新。请不要回答:“使用std::optional”。仔细阅读:问题与它无关。
更新2。请不要回答:“你的代码设计不好”。仔细阅读:问题与代码设计无关。

4

3 回答 3

24

您可以通过添加额外的重载来检测此特定 API 的滥用:

const T& get_or_default(T&& rvalue) = delete;

如果给定的参数get_or_default是一个真正的右值,它将被选择,因此编译将失败。

至于在运行时检测此类错误,请尝试使用启用了 use-after-return ( ASAN_OPTIONS=detect_stack_use_after_return=1) 和/或 use-after-scope ( -fsanitize-address-use-after-scope) 检测的 Clang 的 AddressSanitizer。

于 2017-02-20T10:43:43.263 回答
4

您可以尝试来自Explicit库的lvalue_ref包装器。它可以防止不需要的绑定到一个声明中的临时变量,例如:

const T& get_or_default(lvalue_ref<const T> def)
{
    return has ? value : def.get();
}
于 2017-02-20T11:48:32.770 回答
3

这是一个有趣的问题。悬空引用的实际原因是您使用右值引用,就好像它是左值引用一样。

如果你没有太多的代码,你可以尝试以这种方式抛出异常:

class my_optional
{
public:
    bool has{ false };
    T value;

    const T& get_or_default(const T&& def)
    {
        throw std::invalid_argument("Received a rvalue");
    }

    const T& get_or_default(const T& def)
    {
        return has ? value : def;
    }
};

这样,如果您将 ref 传递给一个临时对象(这确实是一个右值),您将得到一个异常,您将能够捕获或至少会很快中止。

或者,如果您传递了一个右值,您可以通过强制返回一个临时值(而不是 ref)来尝试一个简单的修复:

class my_optional
{
public:
    bool has{ false };
    T value;

    const T get_or_default(const T&& def)
    {
        return get_or_default(static_cast<const T&>(def));
    }

    const T& get_or_default(const T& def)
    {
        return has ? value : def;
    }
};

另一种可能性是破解 Clang 编译器,要求它检测该方法是传递左值还是右值,因为对这些技术还不够习惯......

于 2017-02-20T10:51:25.327 回答