按值传递可调用对象,将其分配给指针成员

vil*_*apx 6 c++

我们收到了来自分包商的代码,该代码主要执行以下操作:

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变量或其他内容),此代码的"已定义"是否有任何差异?

Lig*_*ica 7

m_callable = &callable;
Run Code Online (Sandbox Code Playgroud)

我是否更正此代码是未定义的行为?

是的,这是胡说八道,因为你给的原因.

但代码有效

是的,那就是UB发生的事情......

和Valgrind报告没有内存错误

...尤其是当您正在操作的内存仍属于您的进程时.Valgrind没有在这里发现任何东西; 它不验证C++范围,只有"物理" 存储器访问.并且该程序不会崩溃,因为没有什么机会可以破坏以前占用的内存c.

"物理",我指的是操作系统及其内存管理,而不是C++的抽象概念.它实际上可能是虚拟内存或其他什么.


Nat*_*ica 7

是的,这是未定义的行为.在构造函数的右大括号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不正确.