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

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

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

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

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

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

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


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

>
>
>
Вы все еще кипятите и сравниваете this …

Вы все еще кипятите и сравниваете this с нулем?

12 Дек 2013

Давным-давно в далекой-далекой галактике широко использовалась библиотека MFC, в которой у ряда классов были методы, сравнивающие this с нулем.

Статью написал сотрудник компании ABBYY Дмитрий Мещеряков, впервые опубликована: "Блог компании ABBYY. Вы все еще кипятите и сравниваете this с нулем?" Публикуется здесь с разрешения правообладателя.

0226_This_ru/image1.png

Примерно так:

class CWindow {
    HWND handle;
    HWND GetSafeHandle() const
    {
         return this == 0 ? 0 : handle;
    }
};

"Это же не имеет смысла" - возразит читатель. Еще как "имеет": этот код "позволяет" вызывать метод GetSafeHandle() через нулевой указатель CWindow*. Такой прием время от времени используется в разных проектах. Рассмотрим, почему на самом деле это плохая идея.

Нужно начать с того, что, согласно Стандарту C++ (следует из 5.2.5/3 стандарта ISO/IEC 14882:2003(E)), вызов любого нестатического метода любого класса через нулевой указатель приводит к неопределенному поведению. Тем не менее, в ряде реализаций вот такой код вполне может работать:

class Class {
public:
    void DontAccessMembers()
    {
        ::Sleep(0);
    }
};

int main()
{
    Class* object = 0;
    object->DontAccessMembers();
}

Это происходит благодаря тому, что во время работы метода нет попыток получить доступ к членам класса, а для вызова метода не используется позднее связывание. Компилятор знает, какой именно метод какого именно класса нужно вызвать, и просто добавляет вызов этого метода. При этом this передается как параметр. Эффект тот же, как если бы метод был статическим:

class Class {
public:
    static void DontAccessMembers(Class* currentObject)
    {
        ::Sleep(0);
    }
};

int main()
{
    Class* object = 0;
    Class::DontAccessMembers(object);
}

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

Но мы же знаем, что наш метод никогда не будет вызываться виртуально, правда? И вообще этот код уже сколько-то там лет работает.

Проблема в том, что компилятор может использовать неопределенное поведение для оптимизации. Вот например:

int divideBy = ...;
whatever = 3 / divideBy;
if( divideBy == 0 ) {
    // THIS IS IMPOSSIBLE
}

В коде выше выполняется целочисленное деление на divideBy. Целочисленное деление на ноль приводит к неопределенному поведению (обычно к аварийному завершению программы). Значит, можно считать, что переменная divideBy не равна нулю, и на этапе компиляции исключить проверку и соответствующим образом оптимизировать код.

Точно так же компилятор может оптимизировать и код, сравнивающий this с нулем. В соответствии со Стандартом, this не может быть нулевым, соответственно, проверки и соответствующие ветви кода можно исключить, а это существенно повлияет на код, зависящий от сравнения this с нулем. Компилятор имеет полное право "сломать" (на самом деле — доломать) код CWindow::GetSafeHandle() и сгенерировать машинный код, в котором сравнения нет, а всегда считывается поле класса.

Пока даже самые новые версии распространенных компиляторов (можно проверить с помощью сервиса GCC Explorer) не выполняют таких оптимизаций, так что пока "все работает", правда же?

Во-первых, НЕНАВИСТЬ вы будете очень недовольны, когда после перехода на другой компилятор или другую версию того же компилятора вы потратите немало времени, чтобы обнаружить, что о, теперь такая оптимизация есть. Поэтому код выше является непереносимым.

Во-вторых,

class FirstBase {
    int firstBaseData;
};

class SecondBase {
public:
    void Method()
    {
        if( this == 0 ) {
            printf( "this == 0");
        } else {
            printf( "this != 0 (value: %p)", this );
        }
    }
};

class Composed1 : public FirstBase, public SecondBase {
};

int main()
{
    Composed1* object = 0;
    object->Method();
}

НУ НАДО ЖЕ, при компиляции на Visual C++ 9 указатель this на входе в метод равен 0x00000004, потому что изначально нулевой указатель корректируется так, чтобы указывать на начало подобъекта соответствующего класса.

А если поменять порядок следования базовых классов

class Composed2 : public SecondBase, public FirstBase {
};
    
int main()
{
    Composed2* object = 0;
    object->Method();
}

при тех же самых условиях this будет нулевым, потому что начало подобъекта совпадает с началом объекта, в который он включен. Получается замечательный класс, метод которого работает только при условии "правильного" использования этого класса в составных объектах. Счастливой отладки, премия Дарвина давно не была так близко.

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

SecondBase* object = reinterpret_cast<Composed1*>( rand() );
object->Method();

на Visual C++ 9 дает такой машинный код:

SecondBase* object = reinterpret_cast<Composed1*>( rand() );
010C1000  call        dword ptr [__imp__rand (10C209Ch)] 
010C1006  test        eax,eax
010C1008  je          wmain+0Fh (10C100Fh) 
010C100A  add         eax,4 
object->Method();
010C100D  jne         wmain+20h (10C1020h) 
010C100F  push        offset string "this == 0" (10C20F4h) 
010C1014  call        dword ptr [__imp__printf (10C20A4h)] 
010C101A  add         esp,4

В этом машинном коде вторая инструкция - это сравнение указателя на объект с нулем, при равенстве указателя нулю управление не проходит через инструкцию add eax,4, которая сдвигает указатель. Здесь неявное преобразование реализовано с проверкой, хотя тоже можно было воспользоваться последующим вызовом метода через указатель и считать указатель ненулевым.

В первом случае (вызов метода класса подобъекта прямо через указатель на объект класса) равенство приводимого указателя нулю тоже соответствует неопределенному поведению, и здесь проверка не добавляется. Если при чтении абзаца про оптимизацию кода с вызовом метода и последующей проверкой указателя на ноль, вы подумали, что это бред и фантастика, - напрасно, вот выше описан случай, в котором такая оптимизация по сути уже применена.

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

Популярные статьи по теме
Как и почему статические анализаторы борются с ложными срабатываниями

Дата: 20 Мар 2017

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

В своей предыдущей статье я писал, что мне не нравится подход, при котором статические анализаторы кода оцениваются с помощью синтетических тестов. В статье приводился пример, воспринимаемый анализат…
PVS-Studio для Java

Дата: 17 Янв 2019

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

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

Дата: 31 Май 2014

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

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

Дата: 16 Окт 2017

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

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

Дата: 30 Янв 2019

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

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

Дата: 19 Май 2017

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

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

Дата: 22 Дек 2018

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

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

Дата: 14 Апр 2016

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

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

Дата: 31 Июл 2017

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

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

Дата: 21 Ноя 2018

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

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

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

Следующие комментарии

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