Любое использование vptr выходит за рамки стандарта.
Конечно, использование memcpy
здесь имеет UB
Ответы, указывающие на то, что любое использование memcpy
или другое манипулирование байтами не-POD, то есть любого объекта с vptr, имеет неопределенное поведение, строго технически правильны, но не отвечают на вопрос. Вопрос основан на существовании vptr (указателя vtable), который даже не предусмотрен стандартом: конечно, ответ будет включать факты, выходящие за рамки стандарта, и счет за результат не будет гарантирован стандарт!
Стандартный текст не имеет отношения к vptr
Проблема не в том, что вам не разрешено манипулировать vptr; представление о том, что стандарту разрешено манипулировать чем-либо, что даже не описано в стандартном тексте, абсурдно. Конечно, нестандартный способ изменения vptr будет существовать, и это не относится к делу.
Vptr кодирует тип полиморфного объекта
Проблема здесь не в том, что стандарт говорит о vptr, проблема в том, что представляет vptr, а в том, что стандарт говорит об этом: vptr представляет динамический тип объекта. Когда результат операции зависит от динамического типа, компилятор сгенерирует код для использования vptr.
[Примечание относительно MI: я говорю «vptr» (как будто это единственный vptr), но когда задействовано MI (множественное наследование), объекты могут иметь более одного vptr, каждый из которых представляет собой полный объект, рассматриваемый как определенный полиморфный базовый класс. тип. (Полиморфный класс - это класс с хотя бы одной виртуальной функцией.)]
[Примечание относительно виртуальных баз: я упоминаю только vptr, но некоторые компиляторы вставляют другие указатели для представления аспектов динамического типа, таких как расположение виртуальных базовых подобъектов, а некоторые другие компиляторы используют vptr для этой цели. То, что верно для vptr, верно и для других внутренних указателей.]
Итак, конкретное значение vptr соответствует динамическому типу: это тип самого производного объекта.
Изменение динамического типа объекта за время его существования
Во время конструирования динамический тип меняется, поэтому вызовы виртуальных функций изнутри конструктора могут быть «неожиданными». Некоторые люди говорят, что правила вызова виртуальных функций во время построения особые, но это абсолютно не так: вызывается последний переопределитель; это переопределение - это класс, соответствующий наиболее производному объекту, который был создан, а в конструкторе C::C(arg-list)
это всегда тип класса C
.
Во время разрушения динамический тип меняется в обратном порядке. Вызов виртуальной функции из внутренних деструкторов подчиняется тем же правилам.
Что это значит, когда что-то остается неопределенным
Вы можете выполнять манипуляции низкого уровня, не санкционированные стандартом. То, что поведение не определено явно в стандарте C ++, не означает, что оно не описано где-либо еще. Тот факт, что результат манипуляции явно описан и имеет UB (неопределенное поведение) в стандарте C ++, не означает, что ваша реализация не может его определить.
Вы также можете использовать свои знания о том, как работают компиляторы: если используется строгая раздельная компиляция, то есть когда компилятор не может получить информацию из отдельно скомпилированного кода, каждая отдельно скомпилированная функция является «черным ящиком». Вы можете использовать этот факт: компилятор должен будет предположить, что все, что может сделать отдельно скомпилированная функция, будет выполнено. Даже внутри заданной функции вы можете использовать директиву asm
для получения тех же эффектов: директива asm
без ограничений может делать все, что разрешено в C ++. Эффект - это директива «забудьте то, что вы знаете из анализа кода в этот момент».
Стандарт описывает, что может изменить динамический тип, и ничего не разрешено изменять его, кроме построения / разрушения, поэтому только «внешняя» функция (черный ящик) в противном случае может выполнять построение / разрушение, может изменить динамический тип.
Вызов конструкторов для существующего объекта не допускается, за исключением его восстановления с тем же типом (и с ограничениями) см. [basic.life] / 8:
Если после того, как время жизни объекта закончилось и до того, как хранилище, которое занимал объект, будет повторно использовано или освобождено, новый объект создается в том месте хранения, которое занимал исходный объект, указатель, указывающий на исходный объект, ссылка, которая ссылается на исходный объект, или имя исходного объекта будет автоматически ссылаться на новый объект и, как только время жизни нового объекта начнется, может использоваться для управления новым объектом, если:
(8.1) хранилище для нового объекта точно перекрывает место хранения, которое занимал исходный объект, и
(8.2) новый объект имеет тот же тип, что и исходный объект (игнорируя cv-квалификаторы верхнего уровня), и
(8.3) тип исходного объекта не квалифицируется как константа, и, если это тип класса, не содержит каких-либо нестатических членов данных, тип которых квалифицирован как константа, или ссылочный тип, и
(8.4) исходный объект был наиболее производным объектом ([intro.object]) типа T, а новый объект - наиболее производным объектом типа T (то есть они не являются подобъектами базового класса).
Это означает, что единственный случай, когда вы можете вызвать конструктор (с размещением new) и по-прежнему использовать те же выражения, которые использовались для обозначения объектов (его имя, указатели на него и т. Д.), - это те, в которых динамический тип не изменится, так что vptr все равно останется прежним.
Другими словами, , если вы хотите перезаписать vptr, используя уловки низкого уровня, вы можете; но только если вы напишете то же значение.
Другими словами, не пытайтесь взломать vptr.
person
curiousguy
schedule
28.01.2017
&b_memcpy == &b_ref
- person fghj   schedule 12.12.2016