Comments 33
Иной раз нам и не с такими штуками приходится разбираться :-).Так что не поделиться знаниями просто нельзя.
Кстати, для более глубокого понимания, очень рекомендую «Дизайн и эволюция С++» Страуструпа. Там описан и механизм, и то, что к такому решению привело.
И вообще, стоит ли оно того?
Прочитал статью и решил, что, пожалуй, таки не стоит. Если уж сильно приспичит, лучше часть функционала на голых сях напишу, со всякой низкоуровневой магией.
Так, всё-же, в С++ нет методов, есть Member Functions, может подправите заголовок статьи?
Так никто и не говорил, что так надо делать :). А Ваш пример действительно ужас, так как memset вызывается уже после вызова конструктора. memset'ом можно инициализировать объекты, выделенные, например, с помощью malloc. Но опять таки этого никогда не стоит делать. В статье была просто проведена аналогия между структурами в Си и классами С++.
delete
поспешил, немного не о том написал…
поспешил, немного не о том написал…
Это везде и всюду. И часто меня убеждают, что это вообще чуть-ли не хороший и правильный стиль :). Ведь memset это быстро и понятно.
memset можно вызывать только в конструкторе, да и то только потому что конструктор знает все интимные детали класса. а снаружи — я с вами полностью согласен — нехорошо так делать
Как-то все-таки в голове не укладывается — в век C++14, лямбда- шаблоно -виртуально- и пр.-измов использовать memset и особенно memcpy ?!?
Очень часто используют. Я постоянно вижу. Другое дело, что часто это работает, так как все члены класса простые. Но есть и: www.viva64.com/en/examples/V598/
Кстати, у вас там не все примеры ошибочны. Пример с Qt можно вычеркивать —
memset
вызывается еще до вызова конструктора, это совершенно корректно.Близко к этой теме: смешивание высокоуровневого и низкоуровневого — htrd.su/wiki/zhurnal/2013/09/18/zabavnyj_bag, так сказать, случай из личной практики.
Такая куча букв и слов, и всё для того, чтобы сказать: «дети, не стреляйте побайтово на не-POD-типах».
Даже без novtable может случиться беда: во время копирования-срезки произойдёт подмена vptr на адрес чужой vtable.
Даже без novtable может случиться беда: во время копирования-срезки произойдёт подмена vptr на адрес чужой vtable.
class A {
A& operator=(A const& src) { memcpy(this, &src, sizeof(A)); return *this; }
virtual void foo() {}
};
class B : public A {
~B() {}
virtual void foo() {}
void bar() { foo(); }
};
class C : public A {
~C() {}
virtual void foo() { buz(); }
void buz() {}
};
int main() {
A a; B b; C c;
b = a; // теперь b думает, что он A (и стал ограниченно трудоспособен)
a = c; // теперь a думает, что он C (и может подёргать за несуществующие члены)
(A&)b = c; // теперь b думает, что он C
}
Это понятно. Началось всё с того, что пишут люди и просят устранить ложные срабатывания в анализаторе, так как они делают memcpy()/memset() для классов c __declspec(novtable). Мол ничего в них испортить нельзя. Это статья, так сказать, ответ им.
Так здесь ложных срабатываний вообще 0%.
Потому что если полиморфный класс без vtable, он заведомо абстрактный по своей сути, и будет унаследован — со всеми вытекающими отсюда следствиями.
Другое дело, что __declspec(novtable) не заставляет компилятор даже варнинг кинуть при попытке создать экземпляр такого класса — как это было бы, если бы у него были объявлены чисто виртуальные функции. (Я считаю, это дефект компилятора).
Это даёт пользователям ложную уверенность.
Кстати, ловит ли PVS Studio вот такое:
В этом коде a.foo() и a.buz() — это UB, причём мы даже не попадём в обработчик-ловушку PVFC (указателями на который заполнены недостающие позиции таблиц виртуальных функций абстрактных классов).
Потому что если полиморфный класс без vtable, он заведомо абстрактный по своей сути, и будет унаследован — со всеми вытекающими отсюда следствиями.
Другое дело, что __declspec(novtable) не заставляет компилятор даже варнинг кинуть при попытке создать экземпляр такого класса — как это было бы, если бы у него были объявлены чисто виртуальные функции. (Я считаю, это дефект компилятора).
Это даёт пользователям ложную уверенность.
Кстати, ловит ли PVS Studio вот такое:
struct __declspec(novtable) A
{
virtual void foo() { printf("%p A::foo\n", this); }
void bar() { A::foo(); }
void buz() { foo(); }
};
int main()
{
A a; // ошибка, на самом деле, вот здесь. "фурсенко разрешило"
a.foo(); // компилятор ***схитрил и оптимизировал*** динамический вызов на прямой
a.bar(); // компилятору явно приказали использовать прямой вызов
a.buz(); // гарантированный pure virtual function call, только ***недиагностируемый***!
}
В этом коде a.foo() и a.buz() — это UB, причём мы даже не попадём в обработчик-ловушку PVFC (указателями на который заполнены недостающие позиции таблиц виртуальных функций абстрактных классов).
Так здесь ложных срабатываний вообще 0%.Или я Вас не понял, или Вы меня. Имеет место следующая ситуация. Люди пишут в поддержку и говорят:
Анализатор PVS-Studio врёт, поправьте. У меня класс __declspec(novtable) и именно поэтому я его копирую/обнуляю, используя memcpy()/memset(). Это безопасно.
Мы даже им в начале поверили (не один ведь человек пиcал про это). Но прежде чем что-то править, мы разбираемся с ситуацией. И выясняется, что не всё так просто. И что можно испортить указатель. Про это и статья. Впредь она будет служить ответом на такие письма.
Кстати, ловит ли PVS Studio вот такое.Нет. Быть может добавим.
Если б я был султан… я бы отвечал таким отважным: «пофиг на novtable, вы копируете/обнуляете vptr суперкласса — в пункт назначения может попасть неожиданный мусор».
В принципе, здоровое использование бинарно копируемых абстрактных классов — это когда типы источника и приёмника суть один и тот же класс-наследник. В этом случае паника на memcpy — это ложное срабатывание.
Но, чтобы диагностировать ложность, надо выполнить покрытие кода и убедиться, что в конкретном месте типы действительно совпадают.
А код в статье связан с созданием объектов абстрактного класса, что само по себе UB (хотя, по-хорошему, должно было быть ill-formed). К сожалению, именно это не диагностируется компилятором.
В MSDN на этот счёт есть отписка
В принципе, здоровое использование бинарно копируемых абстрактных классов — это когда типы источника и приёмника суть один и тот же класс-наследник. В этом случае паника на memcpy — это ложное срабатывание.
Но, чтобы диагностировать ложность, надо выполнить покрытие кода и убедиться, что в конкретном месте типы действительно совпадают.
А код в статье связан с созданием объектов абстрактного класса, что само по себе UB (хотя, по-хорошему, должно было быть ill-formed). К сожалению, именно это не диагностируется компилятором.
В MSDN на этот счёт есть отписка
If you attempt to instantiate a class marked with novtable and then access a class member, you will receive an access violation (AV).
Вы как-то странно развернули байты в памяти: меняется порядок байтов, порядок битов внутри байтов не меняется.
Ничего странного. Байт — это минимальная единица адресации памяти, поэтому порядок битов в нем зависит исключительно от того, каким алгоритмом мы его на эти биты раскладываем. Вопрос, как «на самом деле» эти биты хранятся в регистрах процессора и в ОЗУ, бессмысленный — мы все равно не можем работать с ними на этом уровне.
А вот порядок байтов в слове (а также двойном слове и четверном слове) зависит от архитектуры процессора напрямую. И этот порядок на x86 и x64 — обратный, младший байт идет первым.
А вот порядок байтов в слове (а также двойном слове и четверном слове) зависит от архитектуры процессора напрямую. И этот порядок на x86 и x64 — обратный, младший байт идет первым.
Sign up to leave a comment.
Таблица виртуальных методов и техника безопасности