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

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

Бесплатная лицензия PVS-Studio для специалистов Microsoft MVP
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

Мне интересно попробовать плагин на:
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Ваше сообщение отправлено.

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


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

>
>
>
Поиск неинициализированных членов класса

Поиск неинициализированных членов класса

27 Окт 2015

К нам достаточно давно обращались клиенты и потенциальные клиенты с просьбой реализовать диагностику для поиска неинициализированных членов класса. Мы долго сопротивлялись, осознавая сложность задачи, но всё-таки сдались. В результате мы создали диагностику V730. Диагностика получилась далеко не идеальной и предвидя множество писем о том, что что-то работает не так, я решил написать заметку о технической сложности этой задачи. Думаю, эта информация заранее даст пользователям PVS-Studio ответы на некоторые вопросы и просто будет интересна широкому кругу наших читателей.

0354_In_search_of_uninitialized_class_members_ru/image1.png

Говоря о поиске неинициализированных членов класса, человек представляет себе достаточно простые ситуации. Есть в классе скажем 3 члена. Два из них мы инициализировали, а один забыли. Ну что-то в этом духе:

class Vector
{
public:
  int x, y, z;
  Vector() { x = 0; y = 0; }
};

Эх, если бы мир был так прост и все использовали только такие классы. На практике даже человеку иногда бывает трудно сказать, содержит код ошибку или нет. В случае же анализатора, иногда эта задача вообще не решаема. Давайте посмотрим различные причины, почему анализатор может выдавать ложные предупреждения или наоборот не замечать ошибки.

Для начала отмечу, что члены классов могут инициализироваться разнообразнейшими способом. Все даже сложно упомнить. Не читайте дальше. Порассматривайте пока картинку и попробуйте перечислить, сколько способов инициализации членов вы знаете. Посчитали? Тогда продолжим.

0354_In_search_of_uninitialized_class_members_ru/image2.png

Рисунок 1. Единорог гадает, инициализирован член класса, или нет.

О некоторых способах инициализации:

  • Просто присвоить члену класса значение: A() { x = 1; }.
  • Использовать список инициализации: A() : x(1) {}
  • Использовать доступ через this: A(int x) { this->x = x; }
  • Использовать доступ через "::" : A(int x) { A::x = x; }
  • Использовать инициализацию в духе C++11: class A { int x = 1; int y { 2 }; .... };
  • Инициализировать поле с помощью функций типа memset(): A() { memset(&x, 0, sizeof(x); }.
  • Инициализировать с помощью memset() сразу все поля класса (да, да, так делают): A() { memset(this, 0, sizeof(*this)); }
  • Использовать делегирующий конструктор (С++11): A() : A(10, 20) {}
  • Использовать специальную функцию инициализации: A() { Init(); }
  • Члены класса могут сами инициализировать себя: class A { std::string m_s; .... };
  • Члены класса могут быть статическими.
  • Можно инициализировать класс явно вызывая другой конструктор: A() { this->A(0); }
  • Можно вызвать другой конструктор, используя placement new (программисты такие выдумщики): A() { new (this) A(1,2); }
  • Инициализировать члены можно косвенно через указатель: A() { int *p = &x; *p = 1; }
  • И через ссылку: A() { int &r = x; r = 1; }
  • Можно инициализировать члены, если они являются классами, вызывая у них функции: A() { member.Init(1, 2); }
  • Можно "постепенно" инициализировать члены, являющиеся структурами: A() { m_point.x = 0; m_point.y = 1; }
  • Есть и другие способы.

Как видите, надо учитывать очень много способов того, как инициализируются члены класса. Думаю, мы знаем ещё далеко не про все. А ведь эти разнообразные ситуации надо предвидеть и учитывать!

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

Однако даже если научиться распознавать все-все способы инициализации классов, этого все равно будет мало. Отсутствие инициализации какого-то члена не всегда является ошибкой. Классический случай - реализация разновидности какого-то контейнера. Нередко можно встретить подобный код:

class MyVector
{
  size_t m_count;
  float *m_array;
public:
  MyVector() : m_count(0) { }
  ....
};

Переменная m_array не инициализирована, но это не имеет значения. Вначале класс хранит 0 элементов, и поэтому для массива не выделена память. Соответственно, указатель m_array не инициализирован. Он будет инициализирован позже, когда в контейнер добавят хотя бы один элемент.

Код корректен, но анализатор выдаст ложное предупреждение, что расстроит программиста. Но как быть с такими случаями непонятно.

Возможно, для надежности следовало инициализировать m_array значением nullptr. Однако обсуждение стиля программирования выходит за рамки заметки. Важно, что на практике, если в конструкторе инициализируются не все члены, это ещё ничего не значит. Код может совершенно корректно работать и не инициализировать что-то до поры до времени вполне разумно. Здесь я показал очень упрощенный пример. Бывают гораздо более сложные ситуации.

А теперь о двойственности мира. Посмотрите на абстрактный код:

class X
{
  ....
  char x[n];
  X() { x[0] = 0; }
  ....
};

Ошибка, что в классе X инициализируется только 1 элемент? Дать ответ невозможно. Все зависит от того, что представляет из себя класс X. И понять это анализатор не может. Для этого нужен человек.

Если это какой-то класс строки, то ошибки нет:

class MyString
{
  ....
  char m_str[100];
  MyString() { m_str[0] = 0; }
  ....
};

В начало строки записывается терминальный ноль. Тем самым программист указывает, что строка пустая. Остальные элементы массива можно не инициализировать. Код корректен.

Если это класс для работы с цветом, то имеет место ошибка:

class Color
{
  ....
  char m_rgba[4];
  Color() { m_rgba[0] = 0; }
  ....
};

Здесь инициализирован только один элемент массива, а следовало инициализировать все элементы. В данном случае, кстати, анализатор посчитает, что класс полноценно инициализирован и не выдаст предупреждения (false negative). Приходится отдавать предпочтение подходу "промолчать", иначе анализатор будет генерировать слишком много шума.

Видите, как все непросто и неоднозначно? Очень сложно говорить, когда есть ошибка, а когда нет. Пришлось реализовать в анализаторе множество эмпирических проверок, которые пытаются угадать, ошибочен код или нет. Естественно, эмпирический подход будет давать сбой, и мы хотим заранее попросить за это прощения. Однако надеюсь, теперь вы понимаете, что поиск неинициализированных членов класса является сложной задачей и будете снисходительны к PVS-Studio.

Популярные статьи по теме
Holy C++

Дата: 23 Ноя 2022

Автор: Гость

В этой статье постараюсь затронуть все вещи, которые можно без зазрения совести выкинуть из С++, не потеряв ничего (кроме боли), уменьшить стандарт, нагрузку на создателей компиляторов, студентов, из…
Продление жизни временных значений в С++: рецепты и подводные камни

Дата: 01 Ноя 2022

Автор: Гость

Прочитав эту статью, вы узнаете следующее: способы, которыми можно продлить время жизни временного объекта в С++; рекомендации и подводные камни этого механизма, с которыми может столкнуться С++ прог…
Как мы баг в PVS-Studio искали или 278 Гигабайтов логов

Дата: 28 Окт 2022

Автор: Григорий Семенчев, Сергей Ларин, Филипп Хандельянц

Предлагаем вашему вниманию интересную историю о поиске бага внутри анализатора PVS-Studio. Да, мы тоже допускаем ошибки, но мы готовы засучить рукава и залезть в самую глубину "кроличьей норы".
0, 1, 2, Фредди забрал Blender

Дата: 26 Окт 2022

Автор: Андрей Карпов

Эта статья могла бы получить название "Как PVS-Studio защищает от поспешных правок кода, пример N7". Однако так именовать статьи становится скучновато. Поэтому сейчас вы узнаете, причём здесь Фредди …
Примеры ошибок, которые может обнаружить PVS-Studio в коде LLVM 15.0

Дата: 25 Окт 2022

Автор: Андрей Карпов

Компиляторы развиваются и выдают всё больше предупреждений. Остаются ли преимущества от использования статических анализаторов кода, таких как PVS-Studio? Да, так как анализаторы тоже развиваются. Пе…

Комментарии (0)

Следующие комментарии
Unicorn with delicious cookie
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо