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开始重用这个内存,这是合法的行为。
问题是:如何在编译时检测这样的悬空引用或者在运行时使用像清理程序这样的东西?没有咳嗽消毒剂可以检测到它们。
UPD。请不要回答:“使用std::optional
”。仔细阅读:问题与此无关
UPD2。请不要回答:“你的代码设计很糟糕”。仔细阅读:问题不是代码设计。
答案 0 :(得分:23)
您可以通过添加额外的重载来检测此特定API的滥用:
const T& get_or_default(T&& rvalue) = delete;
如果赋予get_or_default
的参数是真正的右值,则会选择它,因此编译将失败。
至于在运行时检测此类错误,请尝试使用Clang的AddressSanitizer,并启用use-after-return(ASAN_OPTIONS=detect_stack_use_after_return=1
)和/或use-after-scope(-fsanitize-address-use-after-scope
)检测。
答案 1 :(得分: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)来尝试一个简单的修复,如果您传递了一个右值:
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编译器要求它检测该方法是通过左值还是左值,我对这些技术已经不够了......
答案 2 :(得分:3)
您可以试用lvalue_ref
库中的Explicit包装器。它可以在一个声明中阻止对临时的不必要的绑定,例如:
const T& get_or_default(lvalue_ref<const T> def)
{
return has ? value : def.get();
}