【问题标题】:Call static function on deleted object在已删除对象上调用静态函数
【发布时间】:2014-12-09 01:19:54
【问题描述】:

我开始学习 C++,但我不明白这是内存泄漏还是某种巫术?!

我有一些“单例”类(只是为了演示):

#include <iostream>
using namespace std;

class S {
    private: S() {
        cout << "S::S" << endl;
    }

    public: static S* instance() {
        static S* i;
        if(i == NULL) {
            cout << "Create S" << endl;
            i = new S;
        }
        return i;
    }

    public: virtual ~S() {
        cout << "S::~S" << endl;
    }

    public: void a() {
        cout << "S::a" << endl;
    }
};

int main() {
    // call some method
    S::instance()->a();
    // delete pointer to instance (no reason, because I'm curious)
    delete S::instance();
    // call method again
    S::instance()->a();

    return 0;
}

这些输出是:

Create S
S::S
S::a
S::~S
S::a

所以我的问题是:为什么在调用析构函数后,静态变量中仍然有类 S 的工作副本?

更新:感谢您的回答。我意识到我的错误。请原谅我的打扰。

【问题讨论】:

  • 你没有工作副本,你有一个 dangling 指针并且真的触发了未定义的行为..(顺便说一句 - 你不需要 public: 之前 每个方法这不是java..
  • (顺便说一句 - 对不赞成的选民来说 - 给初学者投票有点苛刻 - 特别是没有任何关于原因的评论!)
  • I don't understand this is memory leak or some kind of voodoo magic 底线是这样的——不像大多数其他语言,在 C++ 中,做“坏事”并不意味着代码会崩溃,获取堆栈跟踪,弹出一个消息框说“你做了一件坏事”等等。因此,您得到的答案不仅适用于您的问题,还适用于任何碰巧编译没有错误但似乎运行正常的“疯狂”C++ 代码。

标签: c++ delete-operator static-functions


【解决方案1】:

与其他答案一样,您的代码中有未定义的行为,在像您这样的简单代码中,您可能会看到您的代码实际上可以正常工作。在大多数代码库中,代码要复杂得多,这样的错误会导致:

bash: line 7: 7324 Segmentation fault (core dumped) ./a.out

但这仍然可能发生在代码的其他部分,与存在错误的部分无关,也不会立即发生。

我已经稍微修改了您的代码,以允许这样的输出。如您所见,此代码中有更多分配,这很可能是导致此段错误的原因,我的意思是,向量可能正在重用 S::ss 释放内存的一部分,导致它在 S:: 中使用时崩溃转储一种。

http://coliru.stacked-crooked.com/a/c1be7a83275ae847

#include <iostream>
#include <vector>
#include <string>
using namespace std;

class S {
    std::string ss;
    private: S() {
        ss = "ub ub ub ub ub ub ub ub ";
        cout << "S::S" << endl;
    }

    public: static S* instance() {
        static S* i;
        if(i == NULL) {
            cout << "Create S" << endl;
            i = new S;
        }
        return i;
    }

    public: virtual ~S() {
        cout << "S::~S" << endl;
    }

    public: void a() {
        cout << "S::a" << ss << endl;
    }
};

int main() {
        S::instance()->a();
        // delete pointer to instance (no reason, because I'm curious)
        delete S::instance();    
    for ( int n = 0; n < 100; ++n ){
        std::vector<char> avec(n);
        // call some method
        S::instance()->a();
    }

    return 0;
}

【讨论】:

    【解决方案2】:

    现在第二次调用实例是有风险的:

    S::instance()->a();
    

    它会调用:

    public: static S* instance() {
        static S* i;
        if(i == NULL) {
                cout << "Create S" << endl;
            i = new S;
        }
        return i;
    }
    

    但 i 不是 NULL(尽管删除了对象,但指向它的指针无效),所以它返回一个指向曾经是 S 的内存的指针。然后你有未定义的行为,旧对象可能存在,它可能不存在,或者可能发生更糟糕的事情。

    结论要使其正常工作,请确保在删除对象时始终将指针设置为 NULL。例如检查这个答案:How to delete Singleton pointer?

    【讨论】:

    • 我更喜欢你的回答。你抓住了我错过的东西,这可能是 OP 真正想知道的:为什么 instance() 没有导致重建。
    • 好的。我明白了(我永远不会在现实生活中使用这样的代码)。但是为什么它不在程序结束时调用析构函数(我认为应该)?!
    • 你的意思是在'S::a'之后?因为它是一个指针,你应该自己清除它。您的操作系统或 IDE 只是释放程序使用的内存,无需调用析构函数。
    【解决方案3】:

    因为使用已销毁的对象会导致未定义的行为。任何事情都可能发生,包括它的工作......直到它决定不这样做。

    这是语言的正确答案。这通常导致这种未定义行为的原因是因为成员函数一旦变成机器代码就只是常规函数。如果您从未真正使用过您的班级成员,那么它不太可能爆炸。

    同样,未定义的行为。不要期望它会起作用。

    【讨论】:

      【解决方案4】:

      您没有“工作实例”,您有未定义的行为(您在对象被销毁后访问它)。碰巧因为a() 不以任何方式访问对象的数据成员,它似乎工作正常。但是从技术上讲,您正在取消引用一个悬空指针,任何事情都可能发生,包括段错误。

      虽然您的情况略有不同(您有一个删除后访问,而不是指向堆栈的悬空指针),但您可能希望阅读此 excellent answer 上的过时指针。


      关于格式化的附注:虽然您的代码布局当然是合法的,但它对于 C++ 开发人员来说却是陌生的。以下是更自然的格式:

      class S {
          S() {
              cout << "S::S" << endl;
          }
      
      public:
          static S* instance() {
              static S* i;
              if(i == NULL) {
                  cout << "Create S" << endl;
                  i = new S;
              }
              return i;
          }
      
          virtual ~S() {
              cout << "S::~S" << endl;
          }
      
          void a() {
              cout << "S::a" << endl;
          }
      };
      

      【讨论】:

        猜你喜欢
        • 2018-12-16
        • 2018-07-28
        • 1970-01-01
        • 1970-01-01
        • 2013-09-30
        • 1970-01-01
        • 2021-12-28
        • 2015-01-10
        • 1970-01-01
        相关资源
        最近更新 更多