PVS-Studio заглянул в движок Red Dead Redemption — Bullet

    Picture 4

    В наши дни для, например, разработки игр уже нет нужды самостоятельно с нуля реализовывать физику объектов, так как для этого существует большое число библиотек. Bullet в свое время активно использовался во многих ААА играх, проектах виртуальной реальности, различных симуляциях и машинном обучении. Да и используется до сих пор, являясь, например, одним из движков Red Dead Redemption и Red Dead Redemption 2. Так что почему бы не проверить Bullet с помощью PVS-Studio, чтобы посмотреть, какие ошибки сможет выявить статический анализ в таком масштабном проекте, связанном с симуляцией физики.

    Эта библиотека свободно распространяется, так что все могут при желании использовать её и в своих проектах. Кроме Red Dead Redemption этот физический движок также используется и в киноиндустрии для создания спецэффектов. Например, он был задействован при съемках «Шерлока Холмса» Гая Ричи для расчета столкновений.

    Если вы впервые встречаетесь со статьей, где PVS-Studio проверяет проекты, то сделаю небольшое отступление. PVS-Studio — это статический анализатор кода, который помогает находить ошибки, недочеты и потенциальные уязвимости в исходном коде программ на С, C++, C#, Java. Статический анализ является своего рода автоматизированным процессом обзора кода.

    Для разогрева


    Пример 1:

    Начнем с забавной ошибки:

    V624 There is probably a misprint in '3.141592538' constant. Consider using the M_PI constant from <math.h>. PhysicsClientC_API.cpp 4109

    B3_SHARED_API void b3ComputeProjectionMatrixFOV(float fov, ....)
    {
      float yScale = 1.0 / tan((3.141592538 / 180.0) * fov / 2);
      ....
    }

    Небольшая опечатка в числе Пи (3,141592653...), пропущено число «6» на 7-ой позиции в дробной части.

    Picture 1
    Возможно, ошибка в десятимиллионной доле после запятой и не приведет к ощутимым последствиям, но все-таки стоит пользоваться уже существующими библиотечными константами без опечаток. Для числа Пи есть константа M_PI из заголовка math.h.

    Копипаста


    Пример 2:

    Иногда анализатор позволяет найти ошибку косвенным путем. Так, например, здесь в функцию передаются три родственных аргумента halfExtentsX, halfExtentsY, halfExtentsZ, но последний нигде в функции не используется. Можно заметить, что при вызове метода addVertex дважды используется переменная halfExtentsY. Так что возможно, это ошибка копипасты и здесь и должен использоваться забытый аргумент.

    V751 Parameter 'halfExtentsZ' is not used inside function body. TinyRenderer.cpp 375

    void TinyRenderObjectData::createCube(float halfExtentsX,
                                          float halfExtentsY,
                                          float halfExtentsZ,
                                          ....)
    {
      ....
      m_model->addVertex(halfExtentsX * cube_vertices_textured[i * 9],
                         halfExtentsY * cube_vertices_textured[i * 9 + 1],
                         halfExtentsY * cube_vertices_textured[i * 9 + 2],
                         cube_vertices_textured[i * 9 + 4],
                         ....);
      ....
    }

    Пример 3:

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

    Picture 11

    Видите эту длииииинную строчку?

    Picture 12

    Крайне странно, что программист решил записать такое длинное условие в одну строку. А вот то, что в неё скорее всего закралась ошибка, совсем не удивительно.

    Анализатор выдал на эту строку следующие предупреждения.

    V501 There are identical sub-expressions 'rotmat.Column1().Norm() < 1.0001' to the left and to the right of the '&&' operator. LinearR4.cpp 351

    V501 There are identical sub-expressions '0.9999 < rotmat.Column1().Norm()' to the left and to the right of the '&&' operator. LinearR4.cpp 351

    Если мы выпишем все в наглядном «табличном» виде, то станет видно, что к Column1 применяются одни и те же проверки. Из последних двух сравнений видно, что есть Column1 и Column2. Скорее всего третье и четвёртое сравнение должны были проверять значение именно Column2.

       Column1().Norm() < 1.0001 && 0.9999 < Column1().Norm()
    && Column1().Norm() < 1.0001 && 0.9999 < Column1().Norm()
    &&(Column1() ^ Column2()) < 0.001 && (Column1() ^ Column2()) > -0.001
    

    В таком виде одинаковые сравнения становятся куда более заметными.

    Пример 4:

    Ошибка аналогичного вида:

    V501 There are identical sub-expressions 'cs.m_fJacCoeffInv[0] == 0' to the left and to the right of the '&&' operator. b3CpuRigidBodyPipeline.cpp 169

    float m_fJacCoeffInv[2];      
    static inline void b3SolveFriction(b3ContactConstraint4& cs, ....)
    {
      if (cs.m_fJacCoeffInv[0] == 0 && cs.m_fJacCoeffInv[0] == 0)
      {
        return;
      }
      ....
    }

    В этом случае дважды проверяется один и тот же элемент массива. Скорее всего условие должно было выглядеть так: cs.m_fJacCoeffInv[0] == 0 && cs.m_fJacCoeffInv[1] == 0. Это классический пример ошибки копипасты.

    Пример 5:

    Еще был обнаружен такой недочет:

    V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 79, 112. main.cpp 79

    int main(int argc, char* argv[])
    {
      ....
      while (serviceResult > 0)
      {
        serviceResult = enet_host_service(client, &event, 0);
        if (serviceResult > 0)
        {
          ....
        }
        else if (serviceResult > 0)
        {
          puts("Error with servicing the client");
          exit(EXIT_FAILURE);
        }
        ....
      }
      ....
    }

    Функция enet_host_service, результат которой присваивается serviceResult, возвращает единицу в случае удачного завершения и -1 в случае неудачи. Скорее всего ветка else if как раз и должна была среагировать на отрицательное значение serviceResult, но условие проверки было продублировано. Скорее всего это также ошибка копипасты.

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

    V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 151, 190. PhysicsClientUDP.cpp 151

    За пределами дозволенного: выход за границы массива


    Пример 6:

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

    Здесь в условии цикла переменная dofIndex ограничивается сверху значением 128, а dof значением 4 включительно. Но m_desiredState также содержит только 128 элементов. В результате индекс [dofIndex+dof] может привести к выходу за границы массива.

    V557 Array overrun is possible. The value of 'dofIndex + dof' index could reach 130. PhysicsClientC_API.cpp 968

    #define MAX_DEGREE_OF_FREEDOM 128 
    double m_desiredState[MAX_DEGREE_OF_FREEDOM];
    
    B3_SHARED_API int b3JointControl(int dofIndex,
                                     double* forces,
                                     int dofCount, ....)
    {
      ....
      if (   (dofIndex >= 0)
          && (dofIndex < MAX_DEGREE_OF_FREEDOM )
          && dofCount >= 0
          && dofCount <= 4)
      {
        for (int dof = 0; dof < dofCount; dof++)
        {
          command->m_sendState.m_desiredState[dofIndex+dof] = forces[dof];
          ....
        }
      }
      ....
    }

    Пример 7:

    Схожая ошибка, но теперь к ней приводит суммирование не при индексировании массива, а в условии. Если имя файла будет максимально длинным, то терминальный ноль будет записан за границей массива (Off-by-one Error). Естественно, переменная len лишь в исключительных случаях будет равна MAX_FILENAME_LENGTH, но это не устраняет ошибку, а просто делает её проявление редким.

    V557 Array overrun is possible. The value of 'len' index could reach 1024. PhysicsClientC_API.cpp 5223

    #define MAX_FILENAME_LENGTH MAX_URDF_FILENAME_LENGTH 1024
    struct b3Profile
    {
      char m_name[MAX_FILENAME_LENGTH];
      int m_durationInMicroSeconds;
    };
    
    int len = strlen(name);
    if (len >= 0 && len < (MAX_FILENAME_LENGTH + 1))
    {
      command->m_type = CMD_PROFILE_TIMING;
      strcpy(command->m_profile.m_name, name);
      command->m_profile.m_name[len] = 0;
    }

    Один раз отмерь — семь раз отрежь


    Пример 8:

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

    V807 Decreased performance. Consider creating a pointer to avoid using the 'm_app->m_renderer->getActiveCamera()' expression repeatedly. InverseKinematicsExample.cpp 315

    virtual void resetCamera()
    {
      ....
      if (....)
      {
        m_app->m_renderer->getActiveCamera()->setCameraDistance(dist);
        m_app->m_renderer->getActiveCamera()->setCameraPitch(pitch);
        m_app->m_renderer->getActiveCamera()->setCameraYaw(yaw);
        m_app->m_renderer->getActiveCamera()->setCameraPosition(....);
      }
    }

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

    Пример 9:

    V810 Decreased performance. The 'btCos(euler_out.pitch)' function was called several times with identical arguments. The result should possibly be saved to a temporary variable, which then could be used while calling the 'btAtan2' function. btMatrix3x3.h 576

    V810 Decreased performance. The 'btCos(euler_out2.pitch)' function was called several times with identical arguments. The result should possibly be saved to a temporary variable, which then could be used while calling the 'btAtan2' function. btMatrix3x3.h 578

    void getEulerZYX(....) const
    {
      ....
      if (....)
      {
        ....
      }
      else
      {
        ....
        euler_out.roll  = btAtan2(m_el[2].y() / btCos(euler_out.pitch),
                                  m_el[2].z() / btCos(euler_out.pitch));
        euler_out2.roll = btAtan2(m_el[2].y() / btCos(euler_out2.pitch),
                                  m_el[2].z() / btCos(euler_out2.pitch));
        euler_out.yaw  =  btAtan2(m_el[1].x() / btCos(euler_out.pitch),
                                  m_el[0].x() / btCos(euler_out.pitch));
        euler_out2.yaw =  btAtan2(m_el[1].x() / btCos(euler_out2.pitch),
                                  m_el[0].x() / btCos(euler_out2.pitch));
    
      }
      ....
    }
    

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

    Утечка


    Пример 10:

    В проекте было обнаружено множество ошибок следующего вида:

    V773 Visibility scope of the 'importer' pointer was exited without releasing the memory. A memory leak is possible. SerializeSetup.cpp 94

    void SerializeSetup::initPhysics()
    {
      ....
      btBulletWorldImporter* importer = new btBulletWorldImporter(m_dynamicsWorld);
      ....
     
      fclose(file);
    
      m_guiHelper->autogenerateGraphicsObjects(m_dynamicsWorld);
    }

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

    Тут свои законы


    Пример 11:

    Следующая ошибка появляется в коде из-за того, что правила С++ не всегда совпадают с математическими правилами или «здравым смыслом». Заметите сами, где в небольшом отрывке кода содержится ошибка?

    btAlignedObjectArray<btFractureBody*> m_fractureBodies;
    
    void btFractureDynamicsWorld::fractureCallback()
    {
      for (int i = 0; i < numManifolds; i++)
      {
        ....
        int f0 = m_fractureBodies.findLinearSearch(....);
        int f1 = m_fractureBodies.findLinearSearch(....);
    
        if (f0 == f1 == m_fractureBodies.size())
          continue;
        ....
      }
    ....
    }

    Анализатор выдает следующее предупреждение:

    V709 Suspicious comparison found: 'f0 == f1 == m_fractureBodies.size()'. Remember that 'a == b == c' is not equal to 'a == b && b == c'. btFractureDynamicsWorld.cpp 483

    Казалось бы, условие проверяет, что f0 равно f1 и равно количеству элементов в m_fractureBodies. Похоже, что это сравнение должно было проверить, находятся ли f0 и f1 в конце массива m_fractureBodies, поскольку они содержат найденную методом findLinearSearch() позицию объекта. Однако на самом деле это выражение превращается в проверку равны ли f0 и f1, а затем в проверку равно ли m_fractureBodies.size() результату f0 == f1. В итоге третий операнд здесь сравнивается с 0 или 1.

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

    Пример 12:

    При работе со строками зачастую лучше использовать возможности, предоставляемые классом string. Так, для следующих двух случаев лучше заменить strlen(MyStr.c_str()) и val = "" на MyStr.length() и val.clear() соответственно.

    V806 Decreased performance. The expression of strlen(MyStr.c_str()) kind can be rewritten as MyStr.length(). RobotLoggingUtil.cpp 213

    FILE* createMinitaurLogFile(const char* fileName,
                                std::string& structTypes,
                                ....)
    {
      FILE* f = fopen(fileName, "wb");
      if (f)
      {
        ....
        fwrite(structTypes.c_str(), strlen(structTypes.c_str()), 1, f);
        ....
      }
      ....
    }
    

    V815 Decreased performance. Consider replacing the expression 'val = ""' with 'val.clear()'. b3CommandLineArgs.h 40

    void addArgs(int argc, char **argv)
    {
      ....
      std::string val;
      ....
      val = "";
      ....
    }

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

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

    Ошибки, найденные до нас


    Было интересно в духе недавней статьи "Ошибки, которые не находит статический анализ кода, потому, что он не используется" попробовать найти баги или недочеты, которые уже были исправлены, но которые был бы способен обнаружить статический анализатор.
    Picture 2

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

    Пример 13:

    char m_deviceExtensions[B3_MAX_STRING_LENGTH];
    
    void b3OpenCLUtils_printDeviceInfo(cl_device_id device)
    {
      b3OpenCLDeviceInfo info;
      b3OpenCLUtils::getDeviceInfo(device, &info);
      ....
      if (info.m_deviceExtensions != 0)
      {
        ....
      }
    }

    Комментарий к правке говорит о том, что необходимо было проверить массив на то, что он не пустой, но вместо этого производилась бессмысленная проверка указателя, которая всегда возвращала true. Об этом и говорит предупреждение PVS-Studio на исходный вид проверки:

    V600 Consider inspecting the condition. The 'info.m_deviceExtensions' pointer is always not equal to NULL. b3OpenCLUtils.cpp 551

    Пример 14:

    Сможете сходу найти, в чем проблема в следующей функции?

    inline void Matrix4x4::SetIdentity()
    {
      m12 = m13 = m14 = m21 = m23 = m24 = m13 = m23 = m41 = m42 = m43 = 0.0;
      m11 = m22 = m33 = m44 = 1.0;
    }

    Анализатор выдает следующие предупреждения:

    V570 The same value is assigned twice to the 'm23' variable. LinearR4.h 627

    V570 The same value is assigned twice to the 'm13' variable. LinearR4.h 627

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

    m12 = m13 = m14 =
    m21 = m23 = m24 =
    m31 = m32 = m34 =
    m41 = m42 = m43 = 0.0;
    

    Пример 15:

    Следующая ошибка в одном из условий функции btSoftBody::addAeroForceToNode() приводила к явному багу. Согласно комментарию в пулл реквесте, силы применялись к объектам с неверной стороны.

    struct eAeroModel
    {
      enum _
      {
        V_Point,             
        V_TwoSided,
        ....
        END
      };
    };
    
    void btSoftBody::addAeroForceToNode(....)
    {
      ....
      if (....)
      {
        if (btSoftBody::eAeroModel::V_TwoSided)
        {
          ....
        }
        ....
      }
    ....
    }

    Эту ошибку PVS-Studio также мог бы найти и выдать следующее предупреждение:

    V768 The enumeration constant 'V_TwoSided' is used as a variable of a Boolean-type. btSoftBody.cpp 542

    Исправленная проверка выглядит следующим образом:

    if (m_cfg.aeromodel == btSoftBody::eAeroModel::V_TwoSided)
    {
      ....
    }

    Вместо эквивалентности свойства объекта одному из перечислителей проверялся сам перечислитель V_TwoSided.

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

    Заключение


    Picture 6

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

    Если хотите всегда быть в курсе новостей и событий нашей команды, подписывайтесь на наши соц. сети: Инстаграм, Твиттер, Вконтакте, Телеграм.



    Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: PVS-Studio Looked into the Red Dead Redemption's Bullet Engine
    PVS-Studio
    553,59
    Static Code Analysis for C, C++, C# and Java
    Поделиться публикацией

    Комментарии 32

      +1
      Интересно, улучшается ли работа проектов после исправления таких замечаний? Со стороны кажется, будто эти предупреждения никак не мешают работе проекта
        +5

        Об этом и говорит раздел «Ошибки, найденные до нас». Одна из приведенных ошибок приводила к неверному применению физических сил к объекту. А предупреждение, казалось бы, не сильно страшнее прочих выглядит.

          +3
          Конечно улучшается. На качество проекта нужно смотреть не только со стороны конечного пользователя. Внутри команды разработки происходит много других процессов. И хорошо, если ошибки выявляются автоматически на раннем этапе. Тогда разработчики могут делать новые фичи, а не патчить старый код.
          +7
          Читая ваши статьи у меня волосы перестают быть мягкими. я не понимаю как такие ошибки не были выявлены?
            +2
            исходя из собственного опыта использования PVS-Studio — значимые ошибки правятся и так — у них видимый эффект. Анализатор находит скрытые ошибки — либо в коде который (почти) не используется, либо эффект практически не виден (как с Пи выше).
            Редко бывает, что реальный баг отлавливается. Как-то так.
              +1
              Просто примечание. Многие уязвимости, не что иное как ошибки в коде, эффект от существования которых (в стандартном режиме использования) практически не виден. :)
                +1
                Было бы интересно не только посмотреть на ошибки, найденные анализатором, но и на изменения в игре после их исправления.

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

                Ребята из PVS, может появится такая возможность и подходящий проект? Думаю многим было бы интересно взглянуть на такое.
                  +1
                  Если бы это были такие очевидные ошибки их бы исправили и без анализатора.
                  А так, это либо неиспользуемый/deprecated код. Либо сложно повторяемые ошибки.
                  Соответственно если их не удается повторить, то как заснять эффект до и после?
                    +1
                    Боюсь, что исправление ошибки в 7-м знаке пи вообще не приведет к видимым изменениям. Я сначала даже подумал, что такое значение могло быть выбрано специально — тонкости оптимизации какой-нибудь, проблемы округления — мало ли. Но масса примеров с вычислением одного и того же быстро вернули меня на землю :)
                      +1

                      Ничего интересного. Для жены писал программу, на которой она считала кандидатскую — в этой программе был расчёт достаточно хитрой разностной схемы с кучей переменных: диффуры в частных производных, а именно Навье-Стокс и проч. Так вот ошибки проявлялись либо на разных границах-переходах, либо начинали массово заражать модель NaNами, потому что x+NaN = NaN, а NaN чаще всего получается из +-Inf. Стоит отметить, что там бы я от такой ошибки огрёб именно полную сетку NaN.
                      Применительно к графике и физике игр — это будут примерно такие варианты:


                      1. Ничего заметного (потому что не успело накопиться)
                      2. Тупо вылет если ошибка повлияла на логику, а не вычисления.
                      3. Странные артефакты на граничных случаях
                      4. Блинки кадрами с мусором (если проблема только в расчёте изображения)
                      5. Вылет из-за арифметики, если пошло заражаться NaN или подобный эффект

                      Какой-то видимый, но не фатальный эффект в подобных расчётах возникает очень редко.


                      (PS: кандидатская была сделана честно, я только облегчал рутину вычислений и визуализаций)

                  0
                  а примеры 8 и 9 — компилятор соптимизировать не может?
                  Убедиться, что GetActiveCamera() без побочных эффектов…
                    +1
                    Возможно и сможет. Но в любом случае, зачем писать такую простыню кода?
                      0
                      ну на самом деле, если руками проводить микрооптимизации — код становится «грязнее». вынесение общих подвыражний, переиспользование повторно вычисляемых переменных — можно делать руками, но обычно компилятор достаточно умный, что бы сделать это самому. поэтому V807 одна из наименее мною используемых подсказок. в 99% случаев она не помогает.
                        +2

                        Вот, кстати, нифига не "микро". Там код m_app->m_renderer->getActiveCamera() — то есть кроме переходов по указателям еще и вызов функции. Оно, конечно, теоретически может эта функция недетерминированная и выдаёт каждый раз разный результат, но тогда мне страшно за мозг программиста, которому с этим жить. А если нет, то это может стоить очень дорого.
                        Я в своё время (лет 10-12 назад) работу с выборками ADO через COM нехитрыми приёмами выноса подобных выражений ускорял в несколько раз.
                        А недетерминированные выражения в T-SQL выносил из запросов наружу менее года назад — фильтр по недетерминированному выражению (например, несколько сравнений с getdate() в запросе) может генерировать очень плохие планы запросов.

                    +2
                    Небольшая опечатка в числе Пи
                    Пожалуй самое впечатляющее в PVS. Правила на разыменование указателей, UB, идентичные ветви then/else — это всё понятно. Но вот научить понимать, что вот эта опечатка — вероятно pi… o_O

                    Какие ещё константы знаем? e? Постоянная планка? Число Авогадро? Заряд электрона? ;-)
                      +2
                      На тему опечаток в константах будет нечто новенькое в моём следующем обзоре. Ещё не выкатили диагностику в релиз, а уже нашли ошибку в очень популярном проекте.
                      0
                      Сейчас анализатор знает про: M_E, M_LOG2E, M_LOG10E, M_LOG10E, M_LN2, M_LN2, M_LN10, M_PI, M_PI_2, M_PI_4, M_PI_4, M_1_PI, M_1_PI, M_2_PI, M_2_PI, M_2_SQRTPI, M_SQRT2, M_SQRT1_2, M_SQRT1_2.
                        0

                        Повторение M_PI_4, M_PI_4, M_1_PI, M_1_PI – это опечатка или пасхалка?)

                          0
                          С точки зрения статьи, это опечатка. Уже собираясь домой (время комментария 17:56) я поспешил и скопировал лишние. Но на самом деле всё хитрее. С точки зрения анализатора существует «две ипостаси» этих констант. M_PI_4 может быть записано в коде как 0.785398163397448309616, так и .785398163397448309616. И при анализе схожести констант отдельно рассматривается как вариант с 0, так и без 0.
                            +1
                            Учтите только что всех этих констант, удивительным образом, нет ни в стандарте C, ни в стандарте C++. Так что в переносимом проекте это может быть не очень хорошая рекомендация.

                            Вместо этого, вот прям вот-совсем-недавно в C++20 появился заголовочный файл <math> (в девичестве — <number>)
                      +3
                      Для числа Пи есть константа M_PI из заголовка math.h.
                      M_PI содержит 20 символов после запятой. В цитате кода 9 символов (пусть и с неточностью). Интересно, такая разница дает различие в производительности? Ведь этот участок кода может вызываться огромное количество раз.

                      Давно читаю ваши публикации. Давно мечтаю о PVS-Studio для PHP. Эх… Нет, я слышал о статических анализаторах кода для PHP. Но их авторы таких красивых и мотивирующих постов не пишут.
                        +2
                        M_PI содержит 20 символов после запятой. В цитате кода 9 символов (пусть и с неточностью). Интересно, такая разница дает различие в производительности? Ведь этот участок кода может вызываться огромное количество раз.

                        Разве 9 и 20 символов не будут скомпилированы в один тип данных и соответственно в инструкцию/данные одного размера?


                        Или Вы задумываетесь о скорости обработки чисел разной длины (но одного типа) на FPU?

                          +1
                          Потестил. Все верно. Спасибо за ликбез!
                        0
                        Всё еще жду проверку Firefox.
                          0
                          Хм. Ну через 5 лет можно и перепроверить)
                          0
                          А может ли PVS скормить код из UE4 с их миллиардом зависимостей и макросов? У движка всегда были проблемы с физикой (особенно сетевая часть).
                          0

                          Надеюсь, что какая-нибудь из этих ошибок поможет исправить "завязание в текстурах", когда едешь на телеге и врезаешься в гору (нет, как мне кажется).

                            –2
                            Небольшая опечатка в числе Пи (3,141592653...)

                            Занудство, конечно, но в этой фразе тоже есть неточность или неоднозначность трактовки многоточия. Обычно принято последнюю написанную цифру округлять с учетом последующей, а дальше идут ...5359879..., т.е. последнюю цифру точнее было бы написать, как "4". Но если многоточие трактовать именно как сокращение строки цифр, то всё нормально :)

                              0
                              Про ошибку в Matrix4x4 — я удивился, что стиль кода не соответствует остальной библиотеке. Так и оказалось, класс Matrix4x4 лежит в директории
                              examples/ThirdPartyLibs/
                              Я так понял, эта библиотека используется только для примеров.
                              Собственно, вопрос к авторам статьи — вы как-то фильтруете подобные случаи?
                              Формально это все относится к коду движка, я не спорю! Но вот крошечная сноска, мол «а вот эта замечательная ошибка из каталога Thirdparty» м.б. была бы в тему (всего лишь мнение. Может и не прав).

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

                              Самое читаемое