我们收到了来自分包商的代码,该代码主要执行以下操作:
class Callable
{
public:
void operator()(int x)
{
printf("x = %d\n", x);
}
};
template<typename T>
class UsesTheCallable
{
public:
UsesTheCallable(T callable) :
m_callable(NULL)
{
m_callable = &callable;
}
~UsesTheCallable() {}
void call() { (*m_callable)(5); }
private:
T* m_callable;
};
Run Code Online (Sandbox Code Playgroud)
这让我觉得是未定义的代码......他们将Tby值传递给UsesTheCallable构造函数然后将m_callable成员分配给参数的地址,这应该超出构造函数末尾的范围,所以我随时调用UsesTheCallable::call(),在对不再存在的物体采取行动.
所以我尝试了这个主要方法:
int main(int, char**)
{
UsesTheCallable<Callable>* u = NULL;
{
Callable c;
u = new UsesTheCallable<Callable>(c);
}
u->call();
delete u;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我确保Callable在调用之前对象超出了范围UsesTheCallable::call(),所以我应该在内存上调用我当时并不拥有的函数.但代码有效,Valgrind报告没有内存错误,即使我将一些成员数据放入Callable类中并使该operator()函数对该成员数据起作用.
我是否更正此代码是未定义的行为?根据是否Callable有成员数据(例如私有int变量或其他内容),此代码的"已定义"是否有任何差异?
Run Code Online (Sandbox Code Playgroud)m_callable = &callable;我是否更正此代码是未定义的行为?
是的,这是胡说八道,因为你给的原因.
但代码有效
是的,那就是UB发生的事情......
和Valgrind报告没有内存错误
...尤其是当您正在操作的内存仍属于您的进程时.Valgrind没有在这里发现任何东西; 它不验证C++范围,只有"物理" †存储器访问.并且该程序不会崩溃,因为没有什么机会可以破坏以前占用的内存c.
† "物理",我指的是操作系统及其内存管理,而不是C++的抽象概念.它实际上可能是虚拟内存或其他什么.
是的,这是未定义的行为.在构造函数的右大括号callable被破坏并且你有一个悬空指针之后.
您没有看到负面影响的原因是您在超出范围后确实没有使用该实例.函数调用操作符是无状态的,因此它不会尝试访问它不再拥有的内存.
如果我们向callable添加一些状态就像
class Callable
{
int foo;
public:
Callable (int foo = 20) : foo(foo) {}
void operator()(int x)
{
printf("x = %d\n", x*foo);
}
};
Run Code Online (Sandbox Code Playgroud)
然后我们使用
int main()
{
UsesTheCallable<Callable>* u = NULL;
{
Callable c(50);
u = new UsesTheCallable<Callable>(c);
}
u->call();
delete u;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
然后你可以看到这种不良行为.在那次运行中,它输出x = 772773112不正确.