在对象的生存期内,是否存在任何可能发生变化的情况?

时间:2019-08-15 20:24:07

标签: c++ language-lawyer

我一直假设一个对象在相同的内存位置开始和结束其生存期,但是最近我遇到了需要确定的情况。具体来说,我正在从标准中寻找一种保证,即无论编译器执行何种优化,构造对象的地址都将与其调用的析构函数相同,并且其析构函数确实是,除非程序终止,否则一定要从该位置调用它。

我一直认为这些东西是理所当然的,但是仔细检查后我找不到保证,而且在复制和移动省略周围存在一些语言,我不确定该如何解释。我希望这里的一些熟悉标准的人可以将我引向章节和经文。

3 个答案:

答案 0 :(得分:15)

您要查找的内容在[intro.object]/1

中定义
  

[...]对象在其构造周期([class.cdtor])的整个生命周期内以及在其破坏期间([class.cdtor])占据一个存储区域。

这意味着地址只要您可以访问就无法更改。

答案 1 :(得分:1)

  

具体地说,我正在从标准中寻找一种保证,即无论编译器执行何种优化,构造对象的地址都与从其调用析构函数的对象相同...

     

并且确实保证可以从该位置调用其析构函数,除非程序终止。

该标准保证自动变量和static变量都可以,只要它们不会对对象造成不良影响即可。但是,它也不保证从免费存储区分配的对象。

即使对于自动变量,狡猾的程序员也可以通过指针操纵来颠覆意图,并通过指针显式调用析构函数。

此外,当基类没有delete的析构函数时,virtual-基类指针的调用将调用错误的析构函数。这将是编程错误,而不是意图颠覆的结果。

示例:

struct Base
{
   int b;
};

struct Derived : virtual Base
{
   float d;
};


int main()
{
   {
      Derived d1; // Not a problem.
   }

   {
      Derived d1;
      Derived* ptr = &d1;
      delete ptr;    // Bad. The programmer subverts the program.
                     // Must not use delete.
   }

   {
      Derived* d2 = new Derived; // The destructor does not get called automatically.
   }

   {
      Derived* d2 = new Derived;
      delete d2;   // OK. The proper destructor gets called.
   }

   {
      Derived* d2 = new Derived;
      Base* ptr = d2;
      delete ptr;  // Programmer error. The wrong destructor gets called.
   }
}

答案 2 :(得分:-1)

如内森·奥利弗({@ 3}}所述:

  

[...]对象在其构造周期([class.cdtor])的整个生命周期内以及在其破坏期间([class.cdtor])占据一个存储区域。

编译器尊重这一点,并且在某些对象(类似于您描述的对象)中,它必须为真。考虑std::mutex。互斥锁不能被复制或移动,其原因是,互斥锁必须在其生命周期内保持在内存中的同一位置才能正常工作。

那么复制/移动省略如何工作?

复制/移动省略通过在需要移动的地方创建对象而起作用。就这么简单。

我们可以自己看到这种行为:

#include <iostream>

struct Foo {
    Foo() {
        std::cout << "I am at " << (void*)this << '\n';
    }
    // Delete copy and move, to ensure it cannot be moved
    Foo(const Foo&) = delete;
    Foo(Foo&&) = delete;
};

Foo getFoo() {
    return Foo(); 
}

int main() {

    Foo* ptr = new Foo(getFoo()); 

    std::cout << "Foo ptr is at " << (void*)ptr << '\n';
    delete ptr; 
}

此代码输出:

I am at 0x201ee70
Foo ptr is at 0x201ee70

并且我们看到Foo在其生命周期内始终位于同一位置,甚至没有被复制或移动,即使是在动态分配的内存中创建的。

编译器如何知道在哪里创建对象?

如果一个函数返回的类型不能被普通地复制,则该函数采用一个隐式参数,该参数表示应该构造返回值的内存地址。