Прокачай свои обзоры кода
Нам в поддержку написал пользователь о странном ложном срабатывании анализатора PVS-Studio. Сейчас станет понятно, почему этот случай заслуживает отдельной маленькой статьи и насколько у программистов может быть замылен взгляд.


Пользователи время от времени присылают нам различные фрагменты C++ кода, на которые анализатор PVS-Studio, по их мнению, выдал странные/ложные предупреждения. После чего мы дорабатываем диагностики или выписываем идеи по доработкам на потом. В общем, идёт обыкновенная работа по поддержке пользователей.


Однако в этот раз программист, который получил письмо от пользователя, прибежал возбуждённый ко мне с идеей, что про этот случай стоит что-то написать в блог. Мы не можем назвать пользователя или показать его код. Поэтому вот переписанный сокращённый вариант:


char *SomeGlobalPointer {};

void foo()
{
  SomeGlobalPointer = new char[1024];
}

int main()
{
  foo();

  if (!SomeGlobalPointer)
  {
    delete[] SomeGlobalPointer;
  }

  return 0;
}

Программист со стороны пользователя жалуется на "ложное" срабатывание PVS-Studio:


V575 The null pointer is passed into 'operator delete[]'. Inspect the argument.

Какой же это нулевой указатель? Вон же он явно инициализируется в функции.

Это интересный пример, когда код выглядит настолько простым, что человек не видит ошибку. Код для освобождения памяти скучен, и в него сложно вчитаться на обзоре кода. Я уже описывал подобный эффект, например, в статье "Зло живёт в функциях сравнения".


На самом деле перед нами неприметная опечатка в условии:


if (!SomeGlobalPointer)
{
  delete[] SomeGlobalPointer;
}

Оператор delete[] вызывается только в том случае, если указатель нулевой. В результате возникает утечка памяти. Естественно, должно быть наоборот:


if (SomeGlobalPointer)
{
  delete[] SomeGlobalPointer;
}

На самом деле проверка вообще не нужна. Оператор delete[] корректно обрабатывает нулевые указатели. Код можно упростить:


char *SomeGlobalPointer {};

void foo()
{
  SomeGlobalPointer = new char[1024];
}

int main()
{
  foo();
  delete[] SomeGlobalPointer;
  return 0;
}

Поскольку мы заговорили о рефакторинге, то сюда ещё просится умный указатель (std::unique_ptr или std::shared_ptr в зависимости от ситуации). Конечно, код вообще выглядит странным, но не забывайте, что в статье приведён синтетический вариант. Впрочем, мы отвлеклись.


Согласитесь, это красивая опечатка. Она настолько проста, что её не заметили. Более того, даже когда анализатор выдал предупреждение на этот код, программист не увидел проблему. Вместо этого написал нам письмо о том, что анализатор, видимо запутался из-за использования глобально переменной и выдал ложное срабатывание.


Если бы не PVS-Studio, в коде так и осталась бы эта ошибка, приводящая к утечке памяти.


Любите статический анализ кода! Прокачайте процесс обзора кода, используя дополнительно анализатор кода!


Некоторые другие статьи о том, как статический анализ компенсирует нашу человеческую невнимательность:


  1. В очередной раз анализатор PVS-Studio оказался внимательнее человека.
  2. Как PVS-Studio оказался внимательнее, чем три с половиной программиста.
  3. Один день из жизни разработчика PVS-Studio, или отладка диагностики, которая оказалась внимательнее трёх программистов.
  4. Конкурс внимательности: PVS-Studio vs Хакер.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Simple, yet easy-to-miss errors in code.

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


  1. Apoheliy
    12.09.2023 12:57

    На самом деле проверка вообще не нужна

    Конечно не нужна! Ведь new не может выдать nullptr.

    А поставить обработчик исключений желательно.


    1. CaptainFlint
      12.09.2023 12:57
      +6

      Ведь new не может выдать nullptr.
      Как минимум раньше (не проверял, как сейчас) в настройках студийного компилятора была опция, включающая возврат NULL из new вместо кидания исключения. Так что я бы не был так уверен в невозможности нулевого указателя. Стандарт стандартом, а реализации реализациями.


    1. Andrey2008 Автор
      12.09.2023 12:57

      В данном случае согласен. В более общем, указатель может инициализироваться при одних условиях и оставаться нулевым в других. Программисты знают про это и пишут лишние проверки перед delete (и перед free в C). Я имел в иду, что такие проверки не нужны, так как deletefree) корректно обработают нулевой указатель.

      Да, еще есть new(std::nothrow), но он тут ни при чём.


    1. agmt
      12.09.2023 12:57
      +1

      А ещё и исключение скорее всего не кинет (во многих системах /proc/sys/vm/overcommit_memory = 0), а при обращении OOM пришлёт SIGKILL.

      Лечение: поставить в "2" - тогда будешь получать исключение, но gcc перестанет работать.


      1. a-tk
        12.09.2023 12:57
        +1

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


      1. Andrey2008 Автор
        12.09.2023 12:57

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

        P.S. Раньше подобное приходилось про malloc писать, теперь new.... эх...


        1. agmt
          12.09.2023 12:57

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

          А что даже если исключения не будет, доверять тому, что процесс не упадёт - нельзя. Надо обращаться к платформо-зависимым штукам.


    1. voldemar_d
      12.09.2023 12:57

      del


  1. viordash
    12.09.2023 12:57
    +10

    немного про стили в условиях. Для себя давно уже определил что ! или просто выражение применять только к булевым, те это условие я бы писал как if (SomeGlobalPointer == nullptr).

    В таком варианте при прочтении кода лучше видны не стыковки.


    1. vasan
      12.09.2023 12:57
      +4

      Кстати хорошая практика использовать явное приведения аргумента условия к булеву типу. Те, кто начинал программировать на Object Pascal (Delphi, Lazarus итп), стараются хотя бы поначалу делать именно так, поскольку в Паскале передавать в if в качестве аргумента не булевы типы считается ошибкой.


      1. Helltraitor
        12.09.2023 12:57
        +1

        Rust идет теп же путем, к слову


    1. Malizia
      12.09.2023 12:57
      +4

      Ну тогда сразу if (nullptr == SomeGlobalPointer) чтобы исключить ошибочное присваивание if (SomeGlobalPointer = nullptr).


      1. viordash
        12.09.2023 12:57
        +5

        о нее, это не мое:) я читатель кода, и когда читаю что если 0 равен... , и тут меня клинит, ноль может быть равен только нулю.

        Мне кажется уже давно эта проблема присваиваний в условии ловится анализаторами.


        1. ReadOnlySadUser
          12.09.2023 12:57
          +2

          А я наоборот люблю Yoda style. Именно за то, что обычно то, какая переменная будет проверяться, чаще всего понятно из контекста, и на деле я хочу глазами сразу увидеть значение, которое ожидается в условии. Так просто удобнее читать)


        1. datacompboy
          12.09.2023 12:57
          +1

          читать такое надо "Если нулю равна переменная SomeGlobalPointer"..


          1. technic93
            12.09.2023 12:57

            Все равно йода


        1. Helltraitor
          12.09.2023 12:57

          Думаю, стоит считать конструкции a = b ошибкой компиляции в любом месте, если каким либо образом не сказано иначе (хотя, что должно возвращать операция присваивания - для меня загадка)


          1. Gromilo
            12.09.2023 12:57

            public static void Main() {
              int a, b, c, d;
              a = b = c = d = 1;
                    
              Console.WriteLine($"{a} {b} {c} {d}");
            }
            //1 1 1 1

            Возвращает само значение. Код на шарпах, но помнится на плюсах можно так же делать.

            Можно ещё в условии присваивание и сравнение сразу делать, типа `if((b = a) > 10)`. Но я такое на рефакторинге не пропустил бы.


    1. Tujh
      12.09.2023 12:57
      +5

      я бы писал как if (SomeGlobalPointer == nullptr)

      Между прочим, этим вы нарушаете С++ Guidelines

      https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-if


      1. Andrey2008 Автор
        12.09.2023 12:57
        +5

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

        Вот, например, как узнать что хотели проверить? Указатель или значение по указателю?

        int *p = foo();
        if (p) .... // невозможно сказать, верно написано или нет
        if (*p) .... // невозможно сказать, верно написано или нет
        if (p != nullptr) .... // скорее всего всё хорошо, хотели проверить именно указатель
        if (*p != nullptr) .... // ошибка компиляции

        Или вот такой реальный пример из проекта Mozilla Firefox:

        const char *token = str.get();
        if (token == '\0') {
          return NS_ERROR_DOM_SYNTAX_ERR; // nothing between commas
        }
        

        Если-бы человек был фанатом сокращения текста, он бы написал:

        if (!token)

        И тогда анализатор PVS-Studio не смог-бы найти ошибку. А за счёт дополнительной информации, что сравнивают не с абстрактным нулём, а с нулевым литералом '\0', анализатор способен предположить, что здесь опечатка:

        V528 It is odd that pointer to 'char' type is compared with the '\0' value. Probably meant: *token == '\0'. svgnumberlist.cpp 96

        В общем, забыли разыменовать указатель. Правильно:

        if (*token == '\0')


        1. slamon
          12.09.2023 12:57
          -2

          if (p) .... // невозможно сказать, верно написано или нет
          if (*p) .... // невозможно сказать, верно написано или нет

          Серьезно? Быть может вам стоит попрактиковаться в программировании?

          Если-бы человек был фанатом сокращения текста, он бы написал:

          Никто бы так не написал. Человек четко понимает, что работает с текстом, и отчетливо осознает что именно хочет проверить


          1. datacompboy
            12.09.2023 12:57
            +1

            К сожалению, if(p) vs if (*p) встречаются многократно при долгосрочной жизни кода. Требования меняются, набивки объектов меняются, передачи копией, ссылкой, указателем, тушкой, чучелком -- меняются друг на друга. И да, вот такие implicit проверки становятся минами замедленного действия, всплывающими когда их не ждёшь.


            1. technic93
              12.09.2023 12:57

              По хорошему не должно быть чтобы и p и *p имели такую семантику. Это означает что у вас в коде есть что то вроде optional<optional<T>> p


              1. datacompboy
                12.09.2023 12:57

                Почему стразу optional?

                if (p) / if (*p)

                одинаково успешно компилируются и для для int / int*, int[], int**.


        1. Tujh
          12.09.2023 12:57
          +1

          По крайней мере в контексте сравнения с nullptr.

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

          if (p) .... // невозможно сказать, верно написано или нет
          if (*p) .... // невозможно сказать, верно написано или нет

          Не очень корректный пример, потому что не видно, что потом делают с указателем.

          По второй строке частично соглашусь, потому что правильным (относительно абстрактного примера) было бы написать:

          if(p && *p) ...

          а теперь перепишите эту же проверку с использованием избыточности

          if((p != nullptr) && (*p != '\0')) ...

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

          Или вот такой реальный пример из проекта Mozilla Firefox:

          А тут стоит дочитать рекомендацию до конца (я же привёл только часть)

          тут не integer, но и char может принимать далеко не два значения, так что рекомендация прямо говорит, что в вашем примере с Mozilla Firefox она не применима.

          И, конечно, всегда нужно руководствоваться здравым смыслом.


        1. technic93
          12.09.2023 12:57

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

          killAllHumans() // не понятно возможно хотели loveAllHumans()
          
          loveAllHumansWithMyHartIRellyMeanIt() // вот так лучше
          


      1. enree
        12.09.2023 12:57
        +4

        1. oficsu
          12.09.2023 12:57

          У него достаточно взаимопротиворечивых проверок, так что апелляция к нему сомнительна


          1. AnthonyMikh
            12.09.2023 12:57

            Это апелляция к тому, что не стоит использовать C++ /s


      1. ReadOnlySadUser
        12.09.2023 12:57

        Мне нравится Reason. "eliminates some opportunities for mistakes". В то же самое время порождает opprotunities для других ошибок :)

        В общем, из всего правила, я согласен только с тем, что не стоит сравнивать указатели с нулём. Сранивать с nullptr более чем имеет смысл, если рассуждать со стороны моего личного опыта. Просто на практике ни разу не встретил ошибок, связанных с явным сравнением, но с десяток раз уже натыкался на баги, связанные именно с желанием сократить код.


    1. oficsu
      12.09.2023 12:57
      +2

      В таком варианте при прочтении кода лучше видны не стыковки.

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

      if (SomeGlobalPointer == nullptr)
      {
        delete[] SomeGlobalPointer;
      }

      ...никаких несостыковок вы тут не заметите. И явность вам никак вообще не поможет. Потому что проблема не в отсутствии явности, а в том, что мозг крайне плохо обрабатывает в условиях инварианты, завязанные на инверсию

      Лично мне же такой код ещё и усложняет чтение. Ведь для кода в голове обычно строится некоторая ментальная модель. И она строится не на конструкциях языка. В моём, примере, мозг поступает иначе — определяет зависимости между телом ветки и содержимым условия. Это необходимая для понимания сути кода низкоуровневая часть ментального процесса. И независимо от того, способны ли вы её пронаблюдать в своём мозгу сознательно, она на некотором уровне работы мозга отрабатывает

      И если в исходном условии зависимость лишь одна — SomeGlobalPointer, возможных исходов в ментальной модели тоже два — либо у нас SomeGlobalPointer, либо у нас не SomeGlobalPointer. Добавив же == nullptr, мы привнесли в ментальную модель ещё как минимум одну сущность. И не важно, что это неизменная часть условия

      В нашей ментальной модели теперь сущностей, от которых зависит исполнение тела, стало в два раза больше — две. Также и возможное число исходов (где-то в фоне считаемое мозгом) увеличилось до четырёх — SomeGlobalPointer, не SomeGlobalPointer, nullptr, не nullptr

      Это лишь увеличит когнитивную нагрузку при том же семантическом наполнении кода. Вы будете уставать чуть быстрее. Уставая же, будете совершать новые ошибки. Решали одну конкретную частную проблему — добавили новых неочевидным для себя образом


  1. Xeldos
    12.09.2023 12:57
    +5

    Мне нравилась в Perl конструкция unless. Казалось бы, сахарный сахар, но сразу видно, где "если да", а где "если нет". Очень семантично, я считаю.


  1. alan008
    12.09.2023 12:57
    +1

    @Andrey2008

    Немного не по теме статьи, но каково ваше ощущение (по прошествии пары лет) от востребованности анализаторов для C# и Java , а также анализатора MISRA и CVE?

    Или всё-таки C++ был и остаётся основным направлением работы PVS Studio?

    Можно в л.с., если это sensitive информация.

    Просто интересуюсь темой статического анализа, но мы пишем на Delphi и у нас свой самописный анализатор под наши задачи.


    1. Andrey2008 Автор
      12.09.2023 12:57
      +2

      Интерес к C# и Java растёт, но медленно. Думаю, мы сами в этом виноваты, так как команде не хватает сил более активно заниматься их продвижением. Про Java, например, вообще давно статей не писали :(. На тему C++ у нас куда больше разнообразного материала и активностей. В том числе больших и сильных, таких как мини-книга про вредные C++ советы.

      MISRA - интерес есть, но у малого количества пользователей. Хорошо, что сделали, но существенно на жизнь это не повлияло. По запросам пользователей скоро будем дорабатывать это направление и делать ещё некоторые диагностики.

      CVE - как я понимаю, имеется в виду SCA (software composition analysis). Пока не понятно.

      C++ остаётся основным направлением? - Да. Оно самое старое, поэтому там больше всего клиентов и поддержки. И с продвижением у нас в этом ловчее получается.


      1. alan008
        12.09.2023 12:57
        +1

        Спасибо, Андрей!
        Большую и хорошую работу делаете! Насчет CVE — вчера вечером был немного не в себе и сам не до конца разобрался, что я имел в виду. У меня в памяти застряла вот эта статья:
        https://habr.com/ru/companies/pvs-studio/articles/418143/
        И я считал (возможно, ошибочно), что PVS-Studio как-то особенно сигнализирует именно об уязвимостях из списка CVE.
        Насчет SCA — тоже интересная тема, не знал о ней ранее.


        Насчет сравнения языков — C++ всё-таки лидер по стрелянию себе по ногам, возможно поэтому C# и Java менее востребованы, в них изначально меньше косячат + многое выявляется компилятором. Возникнет ли надежная замена языку С++ — это вопрос. Все пророчили Rust, но там местами запутанность конструкций не меньшего уровня, чем в С++, хоть и safe.
        Теоретически, мог бы взлететь анализатор для Python или PHP, но это опять же новые направления, требующие еще и еще ресурсов.


  1. SquareRootOfZero
    12.09.2023 12:57
    +6

    Казалось бы, поставь брекпойнт да проверь, выполняется ли деле delete[] и чему при этом равен указатель. Прежде чем уличать в ложном срабатывании ажно само PVS-Studio! Признайтесь, вы этот пример сами выдумали? :)


    Опять же, какое-то сбивающее с толку предупреждение:


    V575 The null pointer is passed into 'operator delete[]'. Inspect the argument.

    При том что, как сами пишете, оператор delete[] корректно обрабатывает нулевые указатели. Что оно сказать-то хочет? Что указатель возможно нулевой или что указатель обязательно нулевой?


    1. mentin
      12.09.2023 12:57
      +8

      +1 про сбивающее предупреждение. Было бы там The pointer passed into ... is always null, его бы думаю прекрасно понимали.


    1. Andrey2008 Автор
      12.09.2023 12:57
      +1

      Признайтесь, вы этот пример сами выдумали? :)

      Нет. Такой простой случай сложно придумать :) Правильнее будет сказать, я его "услышал". Хотя можно без кавычек. Это в прямом смысле.

      Слышу вдруг коллега в соседней секции: "Ааааа!" и прочие эмоции. Заинтригован, подхожу. Что такое? Он рассказывает и показывает. Я понимаю, что это стоит маленькой заметки :)

      Про V575.

      Сообщение говорит, что в оператор delete всегда передаётся нулевой указатель. Если бы он не всегда был нулевым, то и предупреждения не будет. Это ведь нормально. А вот передача явного nullptr говорит и какой-то ошибке или избыточном коде. Возможно, опечатались и не ту переменную используют. Не очень понятно, что именно сбивает с толку. на мой взгляд вполне понятное предупреждение. Хотя, конечно, у меня может быть взгляд замылен :).


  1. boldape
    12.09.2023 12:57

    Если бы не PVS-Studio, в коде так и осталась бы эта ошибка, приводящая к утечке памяти.

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


    1. SquareRootOfZero
      12.09.2023 12:57
      +2

      Так они своим тестовым кодом воспроизводят не (возможную) утечку памяти, а якобы ложное срабатывание статического анализатора кода. Будет ли там реально утечка памяти, статический анализатор вам не определит.


    1. Andrey2008 Автор
      12.09.2023 12:57
      +2

      Что вы хотите от синтетического примера? :) Его смысл показать, что предупреждение показалось ложным, а оно не ложное.

      Естественно, в реальном приложении, всё это где-то глубоко в потрохах. И утечка памяти самая настоящая. Естественно, реальная программа не заканчивает сразу после этого работу, как в моём примере :).


  1. SquareRootOfZero
    12.09.2023 12:57

    del


  1. Gromilo
    12.09.2023 12:57
    +5

    Пишу на C# и у нас на проекте принято соглашение не использовать восклицательный знак, т.к. его не видно. Пишем такую вот, на первый взгляд, странную конструкцию:

    if(project.IsEnabled == false)
    {
      //Что-то делаем
    }

    Сначала непривычно, но быстро привыкаешь. Ищешь отрицание в конце, а не в начале.

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

    Не страна.город.мэрия.зачем.так.много.вложенности.удалена //удалена что? а! не удалена

    vs

    страна.город.мэрия.зачем.так.много.вложенности.удалена отрицаем удаление // теперь не читается как предложение, зато понятно о каком свойстве идёт речь.


    1. SergeyMax
      12.09.2023 12:57
      +2

      Признайтесь, у вас KPI по количеству кода?)


      1. Gromilo
        12.09.2023 12:57
        +1

        К счастью, нет (:

        Я вот что ещё понял: мы обычно сравниваем одни значения с другими и только булы особенные: с ними либо ничего не делают, либо отрицают (C#). Т.е. если увидел булевую переменную в выражении, будь любезен сделай глазами влево, чтоб узнать есть там восклицательный знак или нет (и ты его точно не пропустил).

        Если использовать сравнение через `== false` вместо отрицания, то ответ на вопрос, было отрицание или нет, всегда будет находится справа от переменной. Лично мне так оказалось удобнее.


        1. a-tk
          12.09.2023 12:57

          Как вам повезло, что Вы не застали в естественном ареале обитания такую дичь, как операторы true и false в дополнение к приведению к bool для пользовательских типов.

          Hidden text

          The true operator returns the bool value true to indicate that its operand is definitely true. The false operator returns the bool value true to indicate that its operand is definitely false. The true and false operators aren't guaranteed to complement each other. That is, both the true and false operator might return the bool value false for the same operand. If a type defines one of the two operators, it must also define another operator.


      1. a-tk
        12.09.2023 12:57
        +6

        if (project.IsEnabled.ToString().Length > 4)

        :)


        1. datacompboy
          12.09.2023 12:57
          +1

          привет "true/false" vs "ложь/истина"?


    1. slamon
      12.09.2023 12:57
      +1

      Пишем такую вот, на первый взгляд, странную конструкцию:

      Она странная вовсе не на первый взгляд. Обычный признак джуниоров, ещё не привыкших к чтению кода


      1. Gromilo
        12.09.2023 12:57

        Мне казалось, что это не общеупотребительная практика. Есть ссылки на массовое применение?


    1. a-tk
      12.09.2023 12:57

      Попробуйте провести эксперимент с коллегами с pattern matching-ом вида

      if (project is {IsEnabled: false})


      1. Gromilo
        12.09.2023 12:57

        И появляется дополнительная проверка на то, что проект не null. Возникает вопрос: это специально или побочный эффект?

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


        1. a-tk
          12.09.2023 12:57

          Ну, если у вас там и так нету NRE и JIT в этом уверен, то вы за это не заплатите.

          В данном случае семантика не меняется


          1. Gromilo
            12.09.2023 12:57

            Согласен, JIT убирает проверку, если точно уверен, например, если экземпляр создали через new. Но чаще значение мы получаем из функции или из параметра и для таки переменных проверка всё равно будет, хоть переменная и не nullable.

            Для параметра видно 2 джампа на L0028.

            Но это всё экономия на спичках


            1. a-tk
              12.09.2023 12:57

              А если посмотреть использование этого метода, то видно, как инлайнер отрабатывает


              1. Gromilo
                12.09.2023 12:57

                Да, инлайнер крутой, жаль с объектами из бд и запросов так не может


    1. igormich88
      12.09.2023 12:57

      Примерно из таких соображений в Котлине к коллекциям прикрутили метод расширение isNotEmpty в дополнение к стандартному isEmpty, чтобы вероятность ошибиться была меньше.


      1. a-tk
        12.09.2023 12:57

        ... а в Perl есть оператор unless


    1. dopusteam
      12.09.2023 12:57

      Не страна.город.мэрия.зачем.так.много.вложенности.удалена //удалена что? а! не удалена

      Если у вас такое большое обращение к вложенном свойству, то оно так или иначе читается тяжело.


  1. khe404
    12.09.2023 12:57
    +2

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


    Тут люди делились грамотными методами написания кода, которые позволяют таких ошибок избежать, мне всегда был близок следующий подход:


      bool PointerIsDefined = (bool) SomeGlobalPointer;
      if(PointerIsDefined){
        delete [] SomeGlobalPointer;
      }

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


    И конечно здорово, что среда разработки уже умеет находить подобные проблемы.


    1. voldemar_d
      12.09.2023 12:57

      Наверное, лучше const bool, или даже так:

      const bool PointerIsDefined = (nullptr != SomeGlobalPointer);

      А то остается вероятность, что значение переменной PointerIsDefined где-то по коду могут изменить. Но еще остается возможность, что bool не изменится, а сам указатель изменится. ИМХО, нужно как следует подумать, разнося одну сущность в две разных переменных. Особенно, если они куда-то еще передаются.


      1. khe404
        12.09.2023 12:57
        -1

        Согласен с вами const в данном случае уместен и скорее всего поможет оптимизатору принять правильное решение. Может быть даже constexpr.


        Что касается сравнения с nullptr это обсуждали выше в комментариях.


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


        1. voldemar_d
          12.09.2023 12:57

          Раздвоение сущностей опасно и в другом смысле. Иногда, в результате каких-нибудь ошибок, мусорится память. И в переменную bool PointerIsDefined может попасть значение, не соответствующее текущему состоянию указателя. Понимаю, что ситуация маловероятная, но и незаряженное ружье иногда стреляет.


          1. khe404
            12.09.2023 12:57

            Ну, с такой же вероятностью у вас будет мусор в SomeGlobalPointer ...


  1. Travisw
    12.09.2023 12:57
    -1

    Эти ваши безопасные джавы мигрировали с одной бд на другую и вылетали и набили дублетов в новую базу, потому что разрабы не рассчитали память ОЗУ потребляемую и был java out of memory