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

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

Бесплатная лицензия 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.

Популярные статьи по теме
Зло живёт в функциях сравнения

Дата: 19 Май 2017

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

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

Дата: 22 Окт 2018

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

PVS-Studio, как и другие статические анализаторы кода, часто выдаёт ложные срабатывания. Но не стоит спешить считать странные срабатывания ложными. Это короткая история о том, как PVS-Studio вновь ок…
PVS-Studio ROI

Дата: 30 Янв 2019

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

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

Дата: 14 Апр 2016

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

Вы угадали, ответ - "42". Здесь приводится 42 рекомендации по программированию, которые помогут избежать множества ошибок, сэкономить время и нервы. Автором рекомендаций выступает Андрей Карпов - тех…
PVS-Studio для Java

Дата: 17 Янв 2019

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

В седьмой версии статического анализатора PVS-Studio мы добавили поддержку языка Java. Пришло время немного рассказать, как мы начинали делать поддержку языка Java, что у нас получилось и какие дальн…
Эффект последней строки

Дата: 31 Май 2014

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

Я изучил множество ошибок, возникающих в результате копирования кода. И утверждаю, что чаще всего ошибки допускают в последнем фрагменте однотипного кода. Ранее я не встречал в книгах описания этого …
Бесплатный PVS-Studio для тех, кто развивает открытые проекты

Дата: 22 Дек 2018

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

В канун празднования нового 2019 года команда PVS-Studio решила сделать приятный подарок всем контрибьюторам open-source проектов, хостящихся на GitHub, GitLab или Bitbucket. Им предоставляется возмо…
Характеристики анализатора PVS-Studio на примере EFL Core Libraries, 10-15% ложных срабатываний

Дата: 31 Июл 2017

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

После большой статьи про проверку операционной системы Tizen мне было задано много вопросов о проценте ложных срабатываний и о плотности ошибок (сколько ошибок PVS-Studio выявляет на 1000 строк кода)…
Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей

Дата: 21 Ноя 2018

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

Краткое описание технологий, используемых в инструменте PVS-Studio, которые позволяют эффективно обнаруживать большое количество паттернов ошибок и потенциальных уязвимостей. Статья описывает реализа…
Любите статический анализ кода!

Дата: 16 Окт 2017

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

Я в шоке от возможностей статического анализа кода, хотя сам участвую в разработке инструмента PVS-Studio. На днях я был искренне удивлён тому, что анализатор оказался умнее и внимательнее меня.

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

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