Улучшим качество кода!
Я написал маленькую электронную книгу в которой рассматриваю вопросы как сделать код лучше. Книга ориентирована на Си/Си++ программистов, но будет интересна и разработчикам, использующих другие языки. Формат книги не подходит для моего любимого Хабра, но мне интересно получить обратную связь и обсудить мысли, изложенные в статье. Поэтому я решил разместить здесь только анонс, а с самой статьей можно познакомиться здесь. И приглашаю в комментарии для обсуждения.

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

Вот 42 темы, которые я рассматриваю:
  1. Не берите на себя работу компилятора
  2. Больше 0, это не 1
  3. Один раз скопируй, несколько раз проверь
  4. Бойтесь оператора ?: и заключайте его в круглые скобки
  5. Используйте доступные инструменты для проверки кода
  6. Проверьте все места, где указатель явно приводится к целочисленным типам
  7. Не вызывайте функцию alloca() внутри циклов
  8. Помните, что исключение в деструкторе — это опасно
  9. Используйте для обозначения терминального нуля литерал '\0'
  10. Старайтесь «не мельчить» при использовании #ifdef
  11. Не жадничайте на строчках кода
  12. Занимаясь Copy-Paste, сосредоточьтесь в конце работы
  13. Выравнивайте однотипный код «таблицей»
  14. Помните: не всегда достаточно компилятора и хорошего стиля кодирования
  15. Если есть возможность, начинайте использовать enum class.
  16. «Смотрите как я могу» — недопустимо в программировании
  17. Используйте специализированные функции для затирания в памяти приватных данных
  18. Знания, полученные при работе с одним языком, не всегда применимы к другому языку
  19. Как правильно вызвать один конструктор из другого
  20. Проверки о достижении конца файла (EOF) может быть недостаточно
  21. Проверяйте признак достижения конца файла (EOF) правильно
  22. Не используйте #pragma warning(default: X)
  23. Вычисляйте длину строкового литерала автоматически
  24. Идентификаторы override и final должны стать вашими новыми друзьями
  25. Больше не сравнивайте 'this' с nullptr
  26. Коварный VARIANT_BOOL
  27. Коварные BSTR строки
  28. Не делайте макрос там, где можно сделать обыкновенную функцию
  29. Используйте для итераторов префиксный оператор инкремента (++i) вместо постфиксного (i++)
  30. Visual C++ и функция wprintf()
  31. В C и C++ массивы не передаются по значению
  32. Бойтесь printf
  33. Никогда не разыменовывайте нулевые указатели
  34. Undefined behavior ближе, чем вы думаете
  35. Добавляя в enum новую константу, не забываем поправить операторы switch
  36. Если на вашем компьютере происходят магические события, проверьте память
  37. Бойтесь оператора continue внутри do {… } while(...)
  38. С сегодняшнего дня используйте nullptr вместо NULL
  39. Почему некорректный код иногда работает
  40. Внедрите статический анализ кода
  41. Сопротивляйтесь добавлению в проект новых библиотек
  42. Не давайте функциям название «empty»

Итак, ссылка на статью: http://www.viva64.com/ru/b/0391/

На английском языке: http://www.viva64.com/en/b/0391/

Я предвижу, что может нечаянно приключиться Хаброэффект, поэтому хочу заранее подстраховаться. Поэтому выкладываю на Яндекс Диск в формате PDF:

Русский: https://yadi.sk/i/LKkWupFjr5WzR

Английский: https://yadi.sk/i/pBZqebxsr5Wyg

P.S. Я специально не размещаю текст книги на других сайтах. Достаточно много людей сообщает о недочётах в тексте, и, если текст будет опубликован в нескольких местах, я замучаюсь вносить исправления. Когда текст стабилизируется, я размещу его ещё где-то в разных форматах. Пока же прошу отнестись с пониманием. И ещё просьба. О замеченных ляпах прошу писать мне на почту (karpov [@] viva64.com), а не в комментариях.

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


  1. iSage
    15.04.2016 15:45
    +19

    Как мило. «Предвижу. Заранее выкладываю». Экий вы предвидец. Особенно после ЛОР-эффекта и просьбы выложить в пдф.


  1. daiver19
    15.04.2016 15:50
    +4

    «Добавляя в enum новую константу, не забываем поправить операторы switch» — просто не надо добавлять default в switch для enum (ваши же советы о том, что не надо выполнять работу за компилятор и экономить на строках.


  1. m1n7
    15.04.2016 16:17
    -2

    Пункт 13, самый конец.
    , «KW_IF»
    , «KW_IGNOREPAT»
    , «KW_INCLUDES»
    , «KW_JUMP»
    , «KW_MACRO»
    , «KW_PATTERN»
    Очевидно, в первой строке запятая не нужна. На этом фоне довольно иронично смотрится «Теперь очень легко заметить недостающую запятую»


    1. Andrey2008
      15.04.2016 16:18

      Обратите в статье внимание на многоточие. Это середина списка.


    1. gorodnev
      17.04.2016 19:47

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


  1. zikher
    15.04.2016 16:50

    Добавляя в enum новую константу, не забываем поправить операторы switch

    Если enum представляет собой последовательные номера (или не сильно разрежённые) и начинается с 0, а количество элементов достаточно большое, то проще сделать таблицу функций. Ошибка будет легко находиться на этапе компиляции, если последним элементом enum-а сделать MYENUM_MAX и сделать эту константу размером таблицы функций.
    И выглядит такой код зачастую привлекательнее, чем гигантский switch.


    1. SKolotienko
      15.04.2016 23:33

      Но если есть гигантский switch и хочется иметь compile-time проверку, то можно также добавить static_assert на равенство MYENUM_MAX == x, где x — то количество элементов в перечислении, которое было в момент написание switch-а.


      1. vagran
        16.04.2016 09:34
        +1

        Если нет default, хороший компилятор и так ругнётся на недостающие значения.


    1. ZyXI
      17.04.2016 02:14

      Какая ошибка компиляции?


      typedef void (*MyEnumHandler)(void);
      MyEnumHandler example_handler;
      const MyEnumHandler table[10] = {
          &example_handler,
      };

      является вполне правильным кодом. «Лишние» элементы будут просто нулями. Вот switch даст ошибку компиляции (при -Werror). А таблица нет, она даст ошибку только если из enum удалили значение и MYENUM_MAX стало меньше длины таблицы.


      Плюс нужно либо синхронизировать порядок между таблицей и enum, что неудобно и не даёт группировать обработчики в таблице, что можно делать со switch. Либо нужно использовать [kMyEnumFoo] = &example_handler. Такой синтаксис также нужен, чтобы получить ошибку, если элемент перечисления был удалён, но зато вставили новый (switch тоже даёт так сделать). Но это C99. А в C89 и ниже switch имеет ещё больше преимуществ перед таблицей, а не одну только ошибку компиляции при добавлении нового элемента в перечисление.


      1. zikher
        18.04.2016 09:28

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

        Со switch-ем есть ещё две проблемы: забытый break и зачастую расползающиеся простыни кода, который явно просится выделиться в функции, но его никто не рефакторит.


  1. nikkadim
    15.04.2016 17:05

    Можете объяснить п.29?


    1. Andrey2008
      15.04.2016 17:05

      Попробую. Что именно следует пояснить?


      1. dyadyaSerezha
        15.04.2016 18:06
        +1

        Не поверите, но п.29


        1. khim
          15.04.2016 20:22

          А чего тут пояснять? "++i" просто проще…

          "++i" обозначает, что переменную можно увеличить — и сразу использовать. «i++» обозначает, что нужно где-то «прикопать» предыдущее значение. Что может быть дорого. В случае если компилятор «видит» все определения он, конечно, справится с оптимизацией и лишнюю работу изведёт, но в любом случае — зачем его заставлять делать это, если можно этого и не делать? Ещё и человек может запутаться во всём этом…


        1. tsostik
          15.04.2016 20:53

          Стандартным объяснением для такого совета является примерно следующий:
          ++i меняет итератор, после чего возвращает ссылку на обновленный итератор.
          i++ сохраняет копию итератора, меняет итератор, возвращает сохраненный. Итого имеем на один конструктор копирования больше.
          Впрочем, мне встречалось и противоположное утверждение о том, что постинкремент предпочтительнее, правда относилось оно к интегральным типам. Обоснования, увы, не помню, но выглядело оно не лишенным логики.


          1. vladon
            16.04.2016 00:57
            -1

            потому что в цикле в случае с i++ сперва идет чтение из регистра, а затем его увеличение, и процессор умеет такое делать параллельно

            в случае с ++i сперва нужно увеличить, а затем считать, что распараллелить невозможно

            это касается тех типов, которые влезают в регистр


            1. khim
              16.04.2016 02:25
              +7

              Это называется «слышал звон, да не знаю — где он».

              Чтение/запись и вычисления на большинстве процессоров делаются совершенно отдельными блоками, которые, уж конечно, могут всё распараллелить. Или не могут — если процессор совсем старый.

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

              Разница была в PDP-11. Вот там — да, «i++» и «--i» выгоднее, чем «++i» и «i--». Но это ни с каким «параллельным исполнением» не связано — просто так система команд устроена, что в ней есть постинкремент и предекремент, а постдекремента и преинкремента нету.

              Такая особенность, в свою очередь, тоже имеет объяснение: так как на PDP-11 все регистры — регистры общего назначения (влючая PC и SP) и доступ к ним всем осуществляется по одному шаблону, то нужен «перекос», чтобы нормально мог работать стек, а так как immediate'ов тоже нет, то нужно, чтобы у команды читающей по адресу PC происходил именно постинкремент, а не преинкремент. Так и получилось что в наборе способов обращения к памяти получился вот такой странный «перекос»: один бит экономии на опкоде, однако.

              Оттуда всё и пошло. Но, чёрт побери, PDP-11 перестала выпускаться ещё до того, как большинство читателей Хабра на свет появились! Уже ARM в 1987м имел как LDMIB/STMIB (load/store memory increment before), так и LDMIA/STMIA (load/store memory increment after) — и в обоих случаях регистр увеличивается в том же такте, когда и используется!

              Может уже стоит забыть про «дела давно минувших дней»?


              1. vladon
                16.04.2016 07:35

                Вот нашел видео, откуда я это взял, и там речь явно не про pdp-11:

                https://youtu.be/vrfYLlR8X8k

                примерно 48:10


                1. khim
                  16.04.2016 12:22
                  +1

                  Вот что бывает, когда водители такси начинают смотреть передачи, где профессиональные гонщики обсуждают как лучше проходить поворот в заносе на льду :-)

                  Там речь шла об очень специфическом случае: 64-битный процессор, 32-битный индекс (который может получить экономию, если у вас их много), вы хотите их смешать (однократно) и обратиться по этому индексу в память.

                  Вот в этом, очень специфическом, случае действительно иногда, очень редко, вы можете получить выигрыш! Из-за использования 32-битного индекса у вас появляется дополнительная операция между увеличением i и его использованием: обрезание (если процессор не имеет 32-битных операций) или «расширение» (если имеет).

                  И вот тут действительно может получиться проблемка. А может и не получиться. Причём в реальной жизни — обычно не получается. Дело в том, что если индекс у вас «просто int», то он не может переполниться (неопределённое поведение) и, в большинстве случаев, за счёт этого компилятор сможет вернуть «утерянный параллелизм».

                  Вот uinsigned int — тут да, тут у компилятора определённо проблемы возникнут.

                  Никакого отношения к итераторам этот пример не имеет. Вообще. А про использование индексов и указателей — рекомендую послушать ровно предыдущую минуту того же выступления.


                1. hellamps
                  16.04.2016 14:44

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

                  По идее, еще быстрее должно работать что-то типа *a++ =…

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


                  1. khim
                    16.04.2016 16:12

                    В видео ситуация немножко другая, там идет обращение к элементу массива по инкрементируюмуся указателю, и если постинкремент позволяет сначала использовать смещение для доступа к памяти, а потом увеличивать указатель(пока dma работает), то сначала инкремент заставляет сначала ждать инкремент, а потом запускать фетч из памяти.
                    Какой ещё DMA? Процессоры DMA не используют. Зато они активно используют prefetch, переименование регистров и спекулятивное исполнение…

                    Но в реальной жизни, думаю, компилятор из всех вариантов сделает одно и то же.
                    Если сможет. Как я уже написал выше — у него может не быть такой возможности. Я пробовал сделать так, чтобы «не получилось» — но примеры получаются весьма хитро закрученными, а выигрыш — мизерным. То есть легко сделать так, чтобы компилятору пришлось породить лишний «mov», но это мало: в современных процессорах mov «ничего не стоит», то есть нужно ещё сделать так, чтобы эта лишняя команда «напрягла декодер» и так далее. Это очень сложно сделать. То есть если вы хотите выиграть «самый последний такт», то… может тогда уже стоит на ассемблер посмотреть и отойти от C/C++?

                    Я думаю та строка в презентации ради «вау-фактора» появилась. Типа: «вы все знаете, что нужно делать A, а не B — а догадываетесь ли вы, что иногда B — лучше?»

                    Догадываемся. Если бы всё программирование сводилось к набору простых правил, то на рынке не было бы такой острой нехватки программистов и они получали бы гораздо меньше. Но это не значит, что если вы придумали как сделать так, чтобы правило которое правильно срабатывает 1000 случаях из 1000, в одном случае из миллиона всё-таки «сломалось», то об этом нужно сразу на лекции рассказывать. Может «плохо подействовать на неокрепшие умы». Вероятность столкнуться с тем, что для итератора it++ вызовет замедление на порядок (а то и на два) превышает вероятность того, что код с a[i++] окажется хоть где-то быстрее, чем код с a[++i] — и это ещё если забыть что во многих случаях переписав этот код с использованием SSE/AVX/etc вы часто сможете получить экономию не в 0.05%, а в 50% ;-)


                    1. hellamps
                      16.04.2016 16:17

                      Да, про DMA я сморозил, конечно.

                      Про итераторы полностью согласен.

                      В целом, эта магия постфикса идет с этих типовых функций типа *strdest++ = *strsrc++, которые транслировались в movs, думаю так.


        1. Andrey2008
          15.04.2016 20:54
          -1

          За меня уже всё ответили. :)


    1. EvgeniyRyzhkov
      15.04.2016 17:06
      +11

      Лев Николаевич, а все-таки что вы хотели сказать в «Войне и мире»?


    1. IvaYan
      15.04.2016 17:18
      +2

      Если использовать i++ то сначала будет создана копия i, сдвинута на 1 позицию и присвоена обратно i. Если использовано ++i, то копии не создается.


      1. vladon
        16.04.2016 01:01
        -1

        только применяйте это только к сложным объектам, с обычными указателями это будет пессимизацией
        ну и насчет создания копии можно уже поспорить, вроде бы clang уже умеет распознавать и оптимизировать подобное


        1. khim
          16.04.2016 02:40
          +2

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

          ну и насчет создания копии можно уже поспорить, вроде бы clang уже умеет распознавать и оптимизировать подобное
          Вы либо штаны наденьте, либо крестик снимите… Если код доступен, то любой компилятор вам всё, что угодно отлично прооптимизирует и разницы между i++ и ++i не будет вообще. А если нет — то нет… собственно достаточно взглянуть на прототипы: преикремент может вернуть ссылку на итератор, а вот постинкремент — вынужден оный итератор копировать.

          Так что правило простое — и именно такое, как написано: используйте для итераторов префиксный оператор инкремента (++i) вместо постфиксного (i++) причём даже тогда, когда эти итераторы — простые указатели или числа. Точка. Конец. Без исключений.

          С процессорами и компиляторами где бы это простое правило давало сбой вам, скорее всего, встретится не удастся, а более сложное правило (используйте постинкремент, но предекремент!), которое когда-то, много лет назад имело смысл — настолько криво и неестественно, что, честное слово… оно того не стоит.


    1. myrov_vlad
      15.04.2016 17:21

      В случае постфиксного инкремента создается временный объект, используется, и только после увеличивается значение самой переменной, при работе с префиксным инкрементом временный объект не создается и переменная увеличивается сразу, что позволяет сэкономить немного ресурсов в случае комплексных переменных (например, итераторов).
      Но при работе с переменными простых типов (например, обычных целочисленных) следует пользоваться все равно постфиксным инкрементом (т.к. отсутствует зависимость данных, подробнее habrahabr.ru/company/mailru/blog/279449/#comment_8809081 )


      1. Einherjar
        15.04.2016 17:27
        -3

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


        1. Andrey2008
          15.04.2016 17:30
          +1

          1. Einherjar
            15.04.2016 17:34

            По вашим же ссылкам в Release разницы как раз никакой. А в дебаге… ну что то мне подсказывает что это мало где будет критичным боттлнеком.


            1. Andrey2008
              15.04.2016 17:41

              Скорость работы Debug версии не менее важна, чем Release. Да, конечному пользователю всё равно. А вот программисту нет.


              1. vladon
                16.04.2016 01:02
                -1

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


                1. Andrey2008
                  16.04.2016 09:37

                  Бенчмарк — действительно смысла не имеет. Незачем сравнивать скорость работы отладочных версий.

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


            1. Whiteha
              15.04.2016 19:34
              +4

              Что-то мне подсказывает что везде где не надо писать i++ это просто неосознанная привычка оставшаяся с первых проб в программировании. Да в релизе это не скажется, но в коде это показывает неосознанность автора и действительно замедляет циклы в и без того медленном дебаге, в котором все разработчики проводят под 95% времени.


              1. vladon
                16.04.2016 01:03
                -2

                ваши дебажные шаги все равно медленнее любого цикла


                1. mihaild
                  16.04.2016 01:13

                  Что такое «дебажные шаги»?
                  (ну и хотите я вам напишу код, который в 100 раз ускоряется что в дебаге что в релизе при замене постинкремента преинкрементом?)


                  1. vladon
                    16.04.2016 01:16
                    -1

                    из скорости дебажной версии вообще не следует ничего о скорости релизной

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

                    шаги в смысле шаги пошаговой отладки


                    1. mihaild
                      16.04.2016 05:26
                      +1

                      >из скорости дебажной версии вообще не следует ничего о скорости релизной
                      правда
                      >более того, нет никакого практического смысла бенчмаркить и оптимизировать скорость работы дебажной версии
                      неправда
                      >шаги в смысле шаги пошаговой отладки
                      >ваши дебажные шаги все равно медленнее любого цикла
                      Сформулируйте точнее: что именно медленнее чего? (какие отрезки времени больше каких?)


            1. mihaild
              15.04.2016 19:51
              +2

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


              1. vladon
                16.04.2016 01:03

                современные компиляторы умеют оптимизировать и не такое


                1. mihaild
                  16.04.2016 01:12
                  +3

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


    1. niya3
      15.04.2016 22:42
      +1

      На эту тему высказались многие из великих: Мейерс (6. «Различайте префиксную форму операторов инкремента и декремента»), Саттер(6. «Временные объекты» )+Александреску(9. «Не пессимизируйте преждевременно», 28. «Предпочитайте канонический вид ++ и --, и вызов префиксных операторов»), Страуструп+Cline( isocpp.org/wiki/faq/operator-overloading#increment-pre-post-speed ).


      1. vladon
        16.04.2016 01:08
        -2

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


        1. khim
          16.04.2016 02:58
          +2

          Давайте сведём всё, что вы тут понаписали вместе, идёт?

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

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


        1. niya3
          16.04.2016 07:45
          +2

          Оптимизировать можно только после профилировщика, я помню. Но при этом:

          Передача параметров по ссылке (рекомендация 25), использование префиксной формы операторов ++ и -- (рекомендация 28) или подобных идиом, которые при работе должны естественным образом "стекать с кончиков ваших пальцев", преждевременной оптимизацией не являются. Это всего лишь устранение преждевременной пессимизации (рекомендация 9)."


      1. Fil
        16.04.2016 10:14

        + Дьюхэрст «Скользкие места C++» (87. «Проблемы инкремента и декремента»)


  1. alexxx_b
    15.04.2016 17:06
    -2

    По первому пункту так и просится цикл, но как автор кода, так и ваш анализатор это не видят.


    1. Andrey2008
      15.04.2016 17:08
      +1

      А причём здесь анализатор? Анализатор ищет ошибки. И кое-что на тему неэффективного кода. Но здесь код как раз руками оптимизированный (развернутый), хоть и неправильный.
      Ошибку анализатор нашел. Больше ему здесь сказать нечего.


  1. Vladusch
    15.04.2016 17:17

    Т.к. Read-and-Comment, то могу только тут: Ссылка на англ. версию PDF на Я.Диске содержит русскую версию


    1. Andrey2008
      15.04.2016 17:17

      Уже исправил.


  1. heleo
    15.04.2016 17:20

    А что мешало разместить книгу на Хабре, даже в виде набора статей, как основном источнике и тут же править по замечаниям в каментах? Как мне кажется, разбиение текста на малые блоки с обсуждением каждого по отдельности более эффективно, нежели сборка всего в одну кучу и отделение обрабатываемого ресурса от источника основного фидбэка.


    1. Andrey2008
      15.04.2016 17:20
      +1

      Когда материал побит на части, их сложно продвигать (рекламировать). У меня на этот материал большие планы в плане рекламы PVS-Studio. И мне хочется, чтобы всё было в одном месте.

      Считайте это моими причудами. Я вот так захотел сделать и сделал. :)


      1. heleo
        15.04.2016 18:25

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


        1. Andrey2008
          15.04.2016 20:58

          Мне нужны продажи. Способ достижения цели — интересный материал, в котором присутствует информация о PVS-Studio. Продвигать такой материал, знаю по опыту проще, когда он не побит на части. Иначе все равно приходится делать страницу «агригатор». Только хуже получается. Вот как-то так.


    1. AndrewSu
      16.04.2016 14:45
      +2

      На самом деле большая часть книги уже есть на Хабре в виде статей


  1. Krypt
    15.04.2016 17:38
    +5

    > 11. Не жадничайте на строчках кода

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


    1. alexws54tk
      15.04.2016 21:16

      Только не говорите этим IOCCC (http://www.ioccc.org/) ребятам.


      1. Krypt
        15.04.2016 21:56
        +2

        В принципе, пока они не коммитят код в мой проект, я ничего не имею против :)


  1. Adamantium
    15.04.2016 19:21

    Интересная подборка получилась, как и почти всегда Ваши статьи, спасибо.

    Не знаю, где ещё спросить, но ответа не нашёл, а может просто плохо искал… Умеет ли ваш анализатор проверять и файлы с «почти си» кодом, в частности *.cu файлы в проектах, использующих CUDA? И если нет, то планируется ли это добавить в дальнейших релизах?


    1. Andrey2008
      15.04.2016 21:00

      Быть может что-то и проверится, но никакой гарантии дать не могу. Поддержка CUDA не планируется. Однако, вдруг Вы сможете сделать предложение, от которого мы не сможем отказаться… Но это уже стоит обсуждать в почте. :)


  1. an9eldust
    15.04.2016 20:23

    PDF-версии очень недостает оглавления.


    1. x_sourer
      15.04.2016 22:05

      Поддерживаю по поводу активного оглавления в PDF.


      1. Andrey2008
        15.04.2016 22:11

        1. x_sourer
          16.04.2016 10:20
          +1

          Да, именно так.
          Спасибо!


  1. VBKesha
    15.04.2016 23:46
    +2

    Спасибо, узнал много интересного.


  1. ashcan
    16.04.2016 00:13

    На 23 странице функция IsInterestinError вызывается без аргумента. Это не опечатка?


    1. Andrey2008
      16.04.2016 00:32

      Спс. Поправлю. Но лучше на почту.


  1. AndAm
    16.04.2016 00:48

    C 4 пунктом не согласен. При грамотном использовании очень удобный оператор. Например для быстрой вставки по условию.


    1. alexws54tk
      16.04.2016 11:04

      С чем именно в 4 пункте вы не согласны?

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


  1. sunman
    16.04.2016 11:12
    -1

    Спасибо за труд, узнал много нового.

    Но вот это место, похоже, само требует статического анализатора:
    «if (headerM != 0) Адрес массива проверяется на равенство 0. Сравнение не имеет смысла, так как результат всегда true.»

    Конечно же имелось в виду «проверяется на НЕравенство 0»


  1. Mercury13
    16.04.2016 12:06

    Насчёт DllMain. У меня на определённой машине LoO наглухо зависал при загрузке. Если выбить и запустить по новой — порядок. Не по этой ли причине?


  1. herr_kaizer
    16.04.2016 14:45

    [blockquote]Бойтесь printf[/blockqoute]
    А чего в ней страшного?



    1. khim
      16.04.2016 16:26

      Если короткое — то строка формата. Очень много подводных камней, в том числе потенциальных дыр в безопасности.

      К сожалению у C++ stream'ов есть другая проблема — отсутствие строки формата. В результате — куча проблем с локализацией (в действительности программы, использующие потоки почти невозможно локализовывать).

      У boost::format — всё вроде хорошо, обе проблемы более-менее решены, но… это нестандарт.

      В результате на практике, если нет возможности использовать boost, то приходится использовать printf и потоки: printf — для надписей, которые должен видеть пользователь (и которые кто-то когда-то может решить локализовать), а потоки — для всего остальное (в частности для логов).

      Так и живём…


      1. 4144
        17.04.2016 08:36

        Для форматной строки лучше всегда использовать константную строку.
        Тогда компилятор выдаст предупреждение, если указаны неверные параметры. По крайней мере gcc такое умеет.


        1. khim
          17.04.2016 17:22

          Если у вас можно использовать константную строку, то и iostream отлично справится.

          Я говорю о чём-нибудь типа

          printf(_("There are %d file(s) in directory \"%s\"!"), num, dir)
          понятно, что в идеале вам нужно грамотно обработать множественные числа и прочее, но даже с таким «расхлябанным» кодом переводчик может выкрутится и написать что-нибудь типа
          "В каталоге \"%2$s\" файлов: %1$d!"
          А в случае с iostream'ом форматной строки нет и отдельные «ошмётки», которые достанутся переводчику почти невозможно перевести так, чтобы результат не выглядет ужасно.

          Но если в примере выше переводчик просто переставит местами %s и %d — то программа и упасть может и даже уязвимость получить!

          От всего этого спасает boost::format, но… нестандарт, увы.


          1. 4144
            17.04.2016 18:29

            Для корректных переводов надо использовать сервисы, которые не дают такое делать.
            Или какую-ли утилиту. Не уверен что gettext может такое проверить, но там не случайно есть атрибуты c-format / no-c-format


  1. yamatoko
    16.04.2016 18:00
    -4

    а зачем нам на английском языке, да еще на ломанном?


    1. Andrey2008
      16.04.2016 18:02
      +1

      Основные продажи PVS-Studio — США и Европа.


      1. vanxant
        20.04.2016 10:31
        -4

        Так а на ломанном то зачем? Отдайте нативному спикеру на вычитку, это копейки стоит.


        1. EvgeniyRyzhkov
          20.04.2016 10:38
          +3

          Теоретик.


        1. khim
          20.04.2016 11:31
          +3

          От «нативного спикера» без IT-образования толку будет мало, скорее вред. А вычитывать всё, как с книгами делается, через несколько кругов редактирования прогонять — так статья через два года выйдет.


  1. Mixim333
    17.04.2016 17:49

    Хоть использую не C/C++, а C#, но для образовательных целей решил почитать.

    Насчет постфиксных и префиксных операторов уже пару раз читал, но объективно, мне проще в цикле for инкрементировать переменную i с помощью i++, чем ++i, т.к. читается легче. Но применение цикла for стал очень ограничивать, если мне не нужно изменять иттерируемые объекты, то использую foreach.

    «Есть масса упрямых программистов, которая не хочет видеть ничего опасного в сдвигах отрицательных чисел, переполнении знаковых чисел, сравнивании this c нулём и так далее.» — на дух не переношу сдвиги, всегда думал, что это со мной «что-то не так», а оказывается Вы их тоже не рекомендуете.

    «Самое простое, но не очень удачное решение, это делать везде „default:“, который будет уведомлять об ошибке.» — C# здесь более строгий, если «default» нет и вхождение значения не найдено, то он выкинет исключение. На институтской скамье, после Pascal и C++ думал, что генерация исключения в таком случае — это дурость C#, пока в один прекрасный момент уже на работе эта «дурость» ни сохранила мне пару миллионов нервных клеток.

    «И упаси вас боже, обвинять компилятор, что он неправильно собирает вашу программу. Такое конечно бывает, но крайне редко.» — а с вот этим не соглашусь. Писал в VS2012 код, который должен был обрабатывать CSV-файлы немалых объемов (сейчас этот объем составляет около 200Гб в сутки), использовать рефлексию(которую заменил на LINQ): запустил в Debug-режиме, подсунул файл «1.csv» — все отработало ровно так, как я и ожидал; откомпилировал приложение в Release, разместил на сервере, оно утром скачало тот же самый «1.csv», начало разбирать, произошло исключение, которое я залогировал; собрал приложение снова в Debug, подсунул скаченный файл, на котором произошла ошибка — все успешно разобралось; плюнул, случайно собрал приложение в Release, запустил на своей машине и оно упало ровно на том месте, на котором упало на сервере; запустил в Debug — снова все хорошо; понял, что проблема именно в разных версиях сборки; потом с другими целями поменял версию Framework'а, отвлекли, случайно поставил версию на Release, подсунул тот же самый файл и все отработало успешно. Так досконально и не понял, с чем был связан баг — то ли с Framework'ом, то ли еще с чем.

    Да, спасибо за статью, было интересно почитать


    1. vlivyur
      18.04.2016 10:09
      +1

      В C# префиксный и постфиксный операторы одинаковы по времени и количеству операций.


    1. khim
      18.04.2016 13:49

      «Есть масса упрямых программистов, которая не хочет видеть ничего опасного в сдвигах отрицательных чисел, переполнении знаковых чисел, сравнивании this c нулём и так далее.» — на дух не переношу сдвиги, всегда думал, что это со мной «что-то не так», а оказывается Вы их тоже не рекомендуете.
      Вопрос не в сдвигах. Грамотно применённые сдвиги — очень даже полезны. И опасны сдвиги не отрицательных чисел, конечно, а сдвиги на отрицательную величину.

      Так досконально и не понял, с чем был связан баг — то ли с Framework'ом, то ли еще с чем.
      Если вы досконально не понимаете с чем столкнулись, то о чём спорить-то? С вероятностью 99% в вашей программе до сих пор сидит ошибка, просто «ворошение кода» вокруг неё скрыло её из глаз.

      Я сталкиваюсь с багами разных компиляторов и стандартных библиотек регулярно — то есть примерно обнаруживаю один-два-три бага в год. Сколько за это время я сажаю (и исправляю) багов у себя — сказать сложно, но речь явно идёт о сотнях, если не о тысячах.

      Так что совет такой: если уж совсем всё работает «странно», то можно и баг компилятора заподозрить, они реально существуют, они не миф — но перед этим всё-таки лучше поискать ошибку у себя потщательнее ибо ошибка с вероятностью 99% — всё-таки где-то у вас в коде…


      1. Andrey2008
        18.04.2016 14:17

        Поправка.

        Опасны сдвиги не отрицательных чисел, конечно, а сдвиги на отрицательную величину.

        Нельзя осуществлять сдвиг отрицательных чисел. Это неопределённо поведение. http://www.viva64.com/ru/b/0142/


        1. khim
          18.04.2016 19:08

          Прочитал стандарт. Слава богу то, что требуется (ASR) они оставили в покое. И то хлеб. А сдвиг влево… тут не очень даже и понятно, что, собственно, должно получиться, так что да, тут вы правы.


    1. Andrey2008
      18.04.2016 14:19
      +1

      «И упаси вас боже, обвинять компилятор, что он неправильно собирает вашу программу. Такое конечно бывает, но крайне редко.» — а с вот этим не соглашусь. Писал в VS2012 код, который должен был обрабатывать CSV-файлы немалых объемов (сейчас этот объем составляет около 200Гб в сутки), использовать рефлексию(которую заменил на LINQ): запустил в Debug-режиме, подсунул файл «1.csv» — все отработало ровно так, как я и ожидал; откомпилировал приложение в Release, разместил на сервере, оно утром скачало тот же самый «1.csv», начало разбирать, произошло исключение, которое я залогировал; собрал приложение снова в Debug, подсунул скаченный файл, на котором произошла ошибка — все успешно разобралось; плюнул, случайно собрал приложение в Release, запустил на своей машине и оно упало ровно на том месте, на котором упало на сервере; запустил в Debug — снова все хорошо; понял, что проблема именно в разных версиях сборки; потом с другими целями поменял версию Framework'а, отвлекли, случайно поставил версию на Release, подсунул тот же самый файл и все отработало успешно. Так досконально и не понял, с чем был связан баг — то ли с Framework'ом, то ли еще с чем.

      Не понял в чём дело == виноват компилятор.

      Кхм… Что-то не очень доказательство. :)


  1. Hawker
    17.04.2016 18:36

    Рекомендация 31:
    Программисты иногда забывают, что в C/C++ нельзя передать в функцию массив по значению. На самом деле, в качестве аргумента передается указатель на массив. Числа в квадратных скобках ничего не значат, они всего лишь служат, своего рода подсказкой программисту, массив какого размера предполагается передать.

    Для многомерных массивов это не так.
    В С/С++ многомерные массивы хранятся последовательно, для вычисления адреса элемента компилятору нужно знать все размерности, кроме первой. Ее-то и можно проигнорировать.

    void foo(int x[][]) // ошибка компиляции
    void foo(int x[][3]) // ok


  1. ainoneko
    18.04.2016 08:18

    В 17-м пункте в первом из вариантов корректного кода (
    memset_s(x, sizeof(apr_uint32_t), 0, sizeof(APR_MD4_DIGESTSIZE));
    ) мне кажется подозрительным «sizeof».


    1. Andrey2008
      18.04.2016 08:23

      Да, фигня написана. Поправлю. Спасибо.


  1. fareloz
    18.04.2016 17:17

    38. С сегодняшнего дня используйте nullptr вместо NULL

    У меня всегда возникает проблема с функциями WinApi. Уместно ли туда передавать nullptr? Я всегда считал, что нужно использоваться средства библиотеки при работе с этой библиотекой. И когда появился nullptr каждый раз возникает трудный выбор что передать. Особенно когда есть пример из MSDN с NULL вместо аргумента-указателя


    1. khim
      18.04.2016 19:14
      +1

      Пример из MSDN с вероятностью 90% был написан до того, как nullptr вообще появился. Какой может быть «трудный выбор» — мне, если честно, неясно.

      Правило, на самом деле, простое: NULL — не нужно использовать. Никогда. Вообще. Точка.

      Если ОЧЕНЬ НАДО поддерживать старые компиляторы — компилируйте с опцией -Dnullptr=NULL или аналогичной.

      Либо библиотека принимает nullptr — и тогда нужно туда передать nullptr, либо не принимает — тогда нужно связаться с разработчиком и выяснить, с какого перепугу у него написано в документации, что куда-то нужно передавать NULL, хотя там явно требуется int. В 100 случаев из 100 это ошибка. Причём в 90% — в программе (и её нужно исправить), в 10% — в документации (и её нужно исправить тоже).


  1. webkumo
    20.04.2016 02:31

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

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