Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
GBP
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
V725. A dangerous cast of 'this' to 'vo…
Сообщения PVS-Studio
Диагностики общего назначения (General Analysis, C++)
Диагностики общего назначения (General Analysis, C#)
Диагностики общего назначения (General Analysis, Java)
Диагностика микро-оптимизаций (C++)
Диагностика 64-битных ошибок (Viva64, C++)
Cтандарт MISRA
Стандарт AUTOSAR
Дополнительная информация
Оглавление

V725. A dangerous cast of 'this' to 'void*' type in the 'Base' class, as it is followed by a subsequent cast to 'Class' type.

30 июня 2015 г.

Анализатор обнаружил опасные приведения указателя "this" к типу "void*" и последующее обратное приведение "void*" к типу класса. Само по себе преобразование "this" к типу "void*" не является ошибкой, но в ряде случаев ошибочным является обратное преобразование - от "void*" к типу указателя на класс. В результате таких преобразований возможно получение некорректного указателя.

Описание диагностики достаточно большое и сложное, но, к сожалению, сделать его проще не получается. Просим внимательно прочитать его целиком.

Рассмотрим пример, где используется приведение "this" к "void*", а после - некорректное обратное приведение к типу класса:

class A
{
public:
  A() : firstPart(1){}
  void printFirstPart() { std::cout << firstPart << " "; }
private:
  int firstPart;
};

class B
{
public:
  B() : secondPart(2){}
  void* GetAddr() const { return (void*)this;  }
  void printSecondPart() { std::cout << secondPart << " "; }
private:
  int secondPart;
};

class C: public A, public B
{
public:
  C() : A(), B(), thirdPart(3){}
  void printThirdPart() { std::cout << thirdPart << " "; }
private:
  int thirdPart;
};
void func()
{
  C someObject;

  someObject.printFirstPart();
  someObject.printSecondPart();
  someObject.printThirdPart();

  void *pointerToObject = someObject.GetAddr();
  ....
  auto pointerC = static_cast<C*>(pointerToObject);

  pointerC->printFirstPart();
  pointerC->printSecondPart();
  pointerC->printThirdPart();
}

Можно было бы предположить, что вывод будет следующим:

1 2 3 1 2 3

Однако на самом деле на экране отобразится что-то типа:

1 2 3 2 3 -858993460

В итоге вывод для всех данных после преобразований является некорректным. Проблема кроется в том, что теперь указатель "pointerC" указывает не на начало объекта C, а на блок памяти, выделенной под объект B.

Кажется, что такая ошибка надуманна и допустить её невозможно. Однако ошибка очевидна только из-за того, что пример маленький и простой. В настоящих программах со сложными иерархиями классов можно легко запутаться. Особенно коварно то, что если функцию "GetAddr()" расположить в классе A, то всё работает, а если в классе B, то нет. Это может сбивать с толку. Давайте разберемся в ситуации подробнее.

Чтобы было проще понять, из-за чего возникла ошибка, необходимо знать, как конструируются и располагаются в памяти объекты классов, созданных в результате множественного наследования.

Схематичный пример этого изображён на рисунке 1.

V725_ru/image1.png

Рисунок 1 - Расположение в памяти объекта класса, полученного путём множественного наследования

Из рисунка 1 видно, что объект класса С (который и получен в результате множественного наследования) состоит из объектов классов A и B, плюс часть объекта C.

Указатель "this" содержит в себе адрес начала выделенного под объект блока памяти. На рисунке 2 изображены указатели "this" для всех трёх объектов.

V725_ru/image2.png

Рисунок 2 - Указатели "this", и блоки памяти

Так как объект класса C состоит из трёх частей, указатель "this" для него будет указывать не на блок памяти, который добавлен дополнительно к базовым классам, а на начало всего непрерывного блока памяти. То есть в данном случае, указатели "this" для классов A и C совпадут.

Указатель "this" для объекта класса B указывает на начало выделенного под него блока памяти, но при этом адрес начала этого участка памяти будет отличен от адреса начала участка памяти, выделенной под объект класса C.

Таким образом, при вызове метода "GetAddr()", мы получим адрес объекта B, и в результате обратного преобразования полученного указателя к типу "C*" будет получен некорректный указатель.

Т.е. если бы функция "GetAddr()" располагалась в классе A, то всё бы работало так, как и ожидал программист. Но если она расположена в B, то происходит сбой.

Во избежание подобных ошибок необходимо продумать, действительно ли стоит выполнять приведение "this" к "void*", и если всё же стоит - тщательно проверять иерархию наследования, а также дальнейшие операции обратного преобразования от "void*" к типу указателя на класс.

Дополнительные ссылки:

Данная диагностика классифицируется как:

Этот сайт использует куки и другие технологии, чтобы предоставить вам более персонализированный опыт. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Если вы не хотите, чтобы ваши данные обрабатывались, пожалуйста, покиньте данный сайт. Подробнее →
Принять