2014-09-04 2 views
4
#include <cstdlib> 
struct B { 
    virtual void f(); 
    void mutate(); 
    virtual ~B(); 
}; 
struct D1 : B { void f(); }; 
struct D2 : B { void f(); }; 
void B::mutate() { 
    new (this) D2; // reuses storage — ends the lifetime of *this 
    f(); // undefined behavior - WHY???? 
    ... = this; // OK, this points to valid memory 
} 

Мне нужно объяснить, почему f() invokation имеет UB? new (this) D2; повторно использует память, но также вызывает конструктор для D2 и с момента запуска нового объекта. В этом случае f() равно this -> f(). То есть мы просто вызываем f() Функция-член D2. Кто знает, почему это UB?Повторное использование времени начала хранения нового объекта?

+0

Размещение-новое предполагается использовать на большинстве производных классов и заменять их объектами того же типа. Это еще одна причина, по которой у вас есть UB, потому что вы не только заменяете подобъект базового класса, но и заменяете его объектом другого типа. – 0x499602D2

+0

@ 0x499602D2 18.6.1.3 Стандарта определяет поведение такого размещения - новое, но ничего не говорит о предполагаемом использовании ** на большинстве производных классов и создании объектов того же типа. ** –

+4

3.8 «Если после срок службы объекта закончился [...], новый объект создается в месте хранения, в котором был загружен исходный объект, [...] имя исходного объекта [...] будет автоматически ссылаться на новый объект [...] и может быть используется для управления новым объектом [...], если: исходный объект был наиболее производным объектом (1.8) типа T, а новый объект является наиболее производным объектом типа T (т. е. они не являются подобъектами базового класса) ». – 0x499602D2

ответ

1

Стандартной показывает этот пример § 3.8 67 N3690:

struct C { 
    int i; 
    void f(); 
    const C& operator=(const C&); 
}; 

const C& C::operator=(const C& other) { 
    if (this != &other) { 
    this->~C(); // lifetime of *this ends 
    new (this) C(other); // new object of type C created 
    f(); // well-defined 
    } 
    return *this; 
} 

C c1; 
C c2; 
c1 = c2; // well-defined 
c1.f(); // well-defined; c1 refers to a new object of type C 

Обратите внимание, что этот пример прекращения жизни объекта перед созданием нового объекта на месте (сравните свой код, который не вызывает деструктор).

Но даже если вы сделали, стандарт также говорит:

Если после жизни объекта закончилась и перед хранением которого объект занят повторно используется или отпущена, новый объект является созданный в месте хранения, в котором был загружен исходный объект, указатель , указывающий на исходный объект, ссылка, которая ссылалась на исходный объект , или имя исходного объекта автоматически ссылаются на новый объект и, время жизни нового объекта , его можно использовать для управления n РЭБ объект, если:

- хранилище для нового объекта точно перекрывает место хранения которого исходный объект занимаемого, и - новый объект имеет же типа, что и исходный объект (не обращая внимания на высший уровень CV-классификаторы) и

- тип исходного объекта не Const квалификации, и, если тип класса, не содержит какой-либо нестатический элемент данных, типа которого Const-квалифицированной или ссылки тип и

- первоначальный объект был самым производным объектом (1.8) of тип T и новый объект является наиболее производным объектом типа T (то есть они не являются подобъектами базового класса).

обратите внимание на слова «и», все условия должны быть выполнены.

Поскольку вы не выполняете все условия (вы производный объект расположенной в пространство памяти объекта базового класса), то есть неопределенного поведение при ссылках на вещи с явным или неявным использованием этого указатель.

В зависимости от реализации компилятора это может или, возможно, в настоящее время взрывать, так как виртуальный объект базового класса сохраняет некоторое пространство для виртуальных таблиц, на месте построения объекта производного типа, который переопределяет некоторые из виртуальных функций означают, vtable может быть разным, поставить вопросы выравнивания и другие низкоуровневые внутренние элементы, и вы получите, что простого sizeof недостаточно, чтобы определить, правильный ли ваш код или нет.

+0

Вы говорите, что он не прекращает жизнь старого объекта? Стандарт говорит, что он есть. –

+0

Он не разрушает объект, который может быть ужасно плохим. Я укажу это ради правильности, спасибо –

1

Эта конструкция очень интересна:

  • Размещение новый не гарантирует вызов деструктора объекта. Таким образом, этот код не будет надлежащим образом гарантировать конец жизни объекта.

  • В принципе вы должны вызвать деструктор перед повторным использованием объекта. Но тогда вы продолжите выполнение функции-члена объекта, который мертв. В соответствии со стандартным разделом.9.3.1/2 Если для объекта, не относящегося к типу X, или типа, полученного из X, вызывается нестатическая функция-член класса X, поведение не определено.

  • Если вы явно не удаляете свой объект, как и в своем коде, вы затем воссоздаете новый объект (построив второй B без разрушения первого, а затем D2 от вершины этого нового B).

Когда создание вашего нового объекта завершено, личность вашего текущего объекта фактически изменилась во время выполнения функции. Вы не можете быть уверены, что указатель на виртуальную функцию, которая будет вызвана, была прочитана до вашего размещения - нового (таким образом, старый указатель на D1 :: f) или после (таким образом, D2 :: f).

Кстати, именно по этой причине существуют некоторые ограничения на то, что вы можете или не можете сделать в объединении, где одно и то же место памяти используется для разных активных объектов (см. Пункт 9.5/2 и в стандартной позиции 9,5/4).

+1

Размещение нового не очистит старый объект, но он убьет его. Срок жизни объекта заканчивается, если его память повторно используется. –

+0

Да, правда: после перезаписи старый объект больше не будет показывать знак жизни! Однако * «после того, как срок жизни объекта закончился, и до того, как хранилище, которое объект занят, повторно используется» *, настоятельно рекомендуют в принципе, что конец жизни должен произойти до повторного использования, а пункт 9.5/4 демонстрирует это в другом контексте. Вот почему я сформулировал * «не будет ** правильно ** гарантировать» *. Например, если объект будет использовать интеллектуальные указатели, они будут грубо перезаписаны, и их счетчик ссылок будет неправильным: объект-объект может быть убит, но он все равно может преследовать код! – Christophe

+0

То, что «* после того, как срок жизни объекта закончился, и до того, как хранилище, которое объект занят, повторно используется *» - это причудливый способ сказать *, пока деструктор запущен, если вы решите его запустить *. Строка, которая «* после окончания срока службы объекта закончилась и до хранения, в котором объект занят, повторно используется или выпущена, создает новый объект в месте хранения, который был занят исходным объектом *», - это знак того, что память физически не уходит. Вы можете «освободить» его с помощью 'free()', но ваш libc все еще владеет им и вернет его вам через 'malloc()'. –

Смежные вопросы