Как стать автором
Обновить

Комментарии 33

C первого раза голова чуть не взорвалась. Потом вроде бы все понял, спасибо за статью.
Иной раз нам и не с такими штуками приходится разбираться :-).Так что не поделиться знаниями просто нельзя.
Кстати, для более глубокого понимания, очень рекомендую «Дизайн и эволюция С++» Страуструпа. Там описан и механизм, и то, что к такому решению привело.
Ещё могу порекомендовать «C++ для настоящих программистов» (C++ for real programmers). Название книжки немного претензионное, но в ней хорошо описаны некоторые тонкости работы C++.
И вообще, стоит ли оно того?

Прочитал статью и решил, что, пожалуй, таки не стоит. Если уж сильно приспичит, лучше часть функционала на голых сях напишу, со всякой низкоуровневой магией.
Так, всё-же, в С++ нет методов, есть Member Functions, может подправите заголовок статьи?
А в чём разница?
НЛО прилетело и опубликовало эту надпись здесь
Так никто и не говорил, что так надо делать :). А Ваш пример действительно ужас, так как memset вызывается уже после вызова конструктора. memset'ом можно инициализировать объекты, выделенные, например, с помощью malloc. Но опять таки этого никогда не стоит делать. В статье была просто проведена аналогия между структурами в Си и классами С++.
НЛО прилетело и опубликовало эту надпись здесь
Это везде и всюду. И часто меня убеждают, что это вообще чуть-ли не хороший и правильный стиль :). Ведь memset это быстро и понятно.
НЛО прилетело и опубликовало эту надпись здесь
memset можно вызывать только в конструкторе, да и то только потому что конструктор знает все интимные детали класса. а снаружи — я с вами полностью согласен — нехорошо так делать
Как-то все-таки в голове не укладывается — в век C++14, лямбда- шаблоно -виртуально- и пр.-измов использовать memset и особенно memcpy ?!?
Очень часто используют. Я постоянно вижу. Другое дело, что часто это работает, так как все члены класса простые. Но есть и: www.viva64.com/en/examples/V598/
Кстати, у вас там не все примеры ошибочны. Пример с Qt можно вычеркивать — memset вызывается еще до вызова конструктора, это совершенно корректно.
Можно, но оставлю. memset до конструткора… Само по себе уже Бррр…
А что Бррр-то? POD-тип окажется забит нулями, как и планировалось, а у сложных типов потом корректно отработают конструкторы по умолчанию.

Для кода, который читает QML и преобразует его в объект — самое то.
Ok. Удалю из базы.
НЛО прилетело и опубликовало эту надпись здесь
memset'ом строго говоря можно заполнять только trivial типы, и если вам нужны нули, то того же эффекта лучше добиться с помощью value-initialization (с возможной поправкой на наличие padding'a между полями).
НЛО прилетело и опубликовало эту надпись здесь
Такая куча букв и слов, и всё для того, чтобы сказать: «дети, не стреляйте побайтово на не-POD-типах».

Даже без 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 вот такое:
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 на этот счёт есть отписка
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 — обратный, младший байт идет первым.
В статье была ошибка. Сейчас ее исправили.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий