pvs-stdio ue4_PVS-Studio对牛顿游戏动力学的第二次检查

pvs-stdio ue4

Рисунок 1

Some time ago, somewhere on the Internet, I stumbled upon a physics engine called Newton Game Dynamics. Knowing that engine projects are usually big and complex, I decided to check its code with PVS-Studio for any interesting defects. I was especially enthusiastic about this one because my co-worker Andrey Karpov already checked it in 2014 and a second check would be a good opportunity to demonstrate our analyzer's evolution over the past six years. As of this writing, the latest version of Newton Game Dynamics is dated February 27, 2020, which means it has been actively developing for the past six years too. So, hopefully, this article will be interesting not only to us but to the engine's developers as well – and for them it's a chance to fix some bugs and improve their code.

前一段时间,在互联网上的某个地方,我偶然发现了一个名为Newton Game Dynamics的物理引擎。 知道引擎项目通常又大又复杂,因此我决定使用PVS-Studio检查其代码是否存在有趣的缺陷。 我对此特别感兴趣,因为我的同事安德烈·卡尔波夫(Andrey Karpov)已在2014年对其进行了检查,第二次检查将是一个很好的机会,以证明我们的分析仪在过去六年中的发展。 在撰写本文时,Newton Game Dynamics的最新版本发布于2020年2月27日,这意味着它在过去六年中也一直在积极开发。 因此,希望这篇文章不仅对我们而且对引擎的开发人员也很有趣-对于他们来说,这是修复一些错误并改进其代码的机会。

分析报告 (Analysis report)

In 2014, PVS-Studio issued:

2014年,PVS-Studio发布:

  • 48 first-level warnings;

    48个一级警告;
  • 79 second-level warnings;

    79次二级警告;
  • 261 third-level warnings.

    261个第三级警告。

In 2020, it issued:

2020年,它发布了:

  • 124 first-level warnings;

    124个一级警告;
  • 272 second-level warnings;

    272次二级警告;
  • 787 third-level warnings (some of them are pretty interesting too).

    787次三级警告(其中一些也很有趣)。

This time, there are many more interesting warnings than in Andrey's article, so let's check them out.

这次,比Andrey的文章中有更多有趣的警告,因此让我们检查一下。

诊断信息 (Diagnostic messages)

警告1 (Warning 1)

V519 The 'tmp[i][2]' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 468, 469. dgCollisionConvexHull.cpp 469

V519'tmp [i] [2]'变量连续两次被赋值。 也许这是一个错误。 检查行:468、469。dgCollisionConvexHull.cpp 469

bool dgCollisionConvexHull::Create (dgInt32 count,....)
{
  ....
  dgStack<dgVector> tmp(3 * count);
  for (dgInt32 i = 0; i < count; i ++) 
  {
    tmp[i][0] = dgFloat32 (buffer[i*3 + 0]);
    tmp[i][1] = dgFloat32 (buffer[i*3 + 1]);
    tmp[i][2] = dgFloat32 (buffer[i*3 + 2]);
    tmp[i][2] = dgFloat32 (0.0f);
  }
  ....
}

An element of the tmp[i][2] array is initialized twice in a row. Defects like that are usually a sign of misused copy-paste. This can be fixed by either removing the second initialization if it's not meant to be there or changing the index number to 3 – it all depends on the value of the count variable. Now, I'd like to show you another V519 warning absent in Andrey's article but recorded in our bug database:

tmp [i] [2]数组的元素连续初始化两次。 此类缺陷通常是滥用复制粘贴的迹象。 可以通过以下方法解决此问题:要么不删除第二个初始化,要么将索引号更改为3,这都取决于count变量的值。 现在,我想向您展示Andrey的文章中没有出现但记录在我们的bug数据库中的另一个V519警告:

V519 The 'damp' object is assigned values twice successively. Perhaps this is a mistake. physics dgbody.cpp 404

V519“阻尼”对象连续两次被赋值。 也许这是一个错误。 物理dgbody.cpp 404

void dgBody::AddBuoyancyForce (....)
{
  ....
  damp = (m_omega % m_omega) * dgFloat32 (10.0f) *
        fluidAngularViscousity; 
  damp = GetMax (GetMin ((m_omega % m_omega) * 
       dgFloat32 (1000.0f) * 
       fluidAngularViscousity, dgFloat32(0.25f)), 
       dgFloat32(2.0f));
  ....
}

Actually, this bug didn't show up in the report. Neither did I find the AddBuoyancyForce function in the dgbody.cpp file. And that's just fine: while the ability to detect new bugs is a sign of our analyzer's evolution, the absence of earlier bugs in recent project versions is a sign of the project's own evolution.

实际上,此错误未显示在报告中。 我也没有在dgbody.cpp文件中找到AddBuoyancyForce函数。 这很好:虽然能够检测到新的错误是我们分析仪发展的一个标志,但在最新项目版本中没有较早的错误是该项目自身发展的标志。

有点离题的猜测 (A bit of off-topic speculation)

I'm not the one to judge if snippets below contain bugs or if their behavior fails the programmer's expectations, but they do look suspicious.

我不是要判断下面的代码片段是否包含错误或它们的行为是否超出了程序员的期望,但它们看起来确实很可疑。

This snippet triggered two warnings at once:

此代码段一次触发了两个警告:

V621 Consider inspecting the 'for' operator. It's possible that the loop will be executed incorrectly or won't be executed at all. MultiBodyCar.cpp 942

V621考虑检查“ for”操作员。 循环有可能执行不正确或根本不执行。 MultiBodyCar.cpp 942

V654 The condition 'i < count' of loop is always false. MultiBodyCar.cpp 942

V654循环的条件“ i <计数”始终为false。 MultiBodyCar.cpp 942

void MultibodyBodyCar(DemoEntityManager* const scene)
{
  ....
  int count = 10;
  count = 0;
  for (int i = 0; i < count; i++) 
  {
    for (int j = 0; j < count; j++) 
    {
      dMatrix offset(location);
      offset.m_posit += dVector (j * 5.0f + 4.0f, 0.0f, i * 5.0f, 0.0f);
      //manager->CreateSportCar(offset, viperModel.GetData());
      manager->CreateOffRoadCar(offset, monsterTruck.GetData());
    }
  }
  ....
}

This code might be used for debugging purposes – if so, turning off the loop is a normal trick. There were a few more cases like that:

该代码可能用于调试目的-如果是这样,关闭循环是一个正常的技巧。 还有更多类似的情况:

V519 The 'ret' variable is assigned values twice successively. Perhaps this is a mistake.Check lines: 325, 326. dString.cpp 326

V519“ ret”变量连续两次被赋值。 也许这是一个错误。检查行:325,326。dString.cpp 326

void dString::LoadFile (FILE* const file)
{
  ....
  size_t ret = fread(m_string, 1, size, file);
  ret = 0;
  ....
}

V519 The 'ret' variable is assigned values twice successively.Perhaps this is a mistake. Check lines: 1222, 1223. DemoEntityManager.cpp 1223

V519'ret'变量被连续两次赋值,也许这是一个错误。 检查行:1222、1223。DemoEntityManager.cpp 1223

void DemoEntityManager::DeserializeFile (....)
{
  ....
  size_t ret = fread(buffer, size, 1, (FILE*) serializeHandle);
  ret = 0;
  ....
}

V560 A part of conditional expression is always true: (count < 10). dMathDefines.h 726

V560条件表达式的一部分始终为true :(计数<10)。 dMathDefines.h 726

bool dCholeskyWithRegularizer(....)
{
  ....
  int count = 0;
  while (!pass && (count < 10))
  {
    ....
  }
  ....
}

V654 The condition 'ptr != edge' of loop is always false. dgPolyhedra.cpp 1571

V654循环的条件“ ptr!= edge”始终为false。 dgPolyhedra.cpp 1571

void dgPolyhedra::Triangulate (....)
{
  ....
  ptr = edge;
  ....
  while (ptr != edge);
  ....
}

V763 Parameter 'count' is always rewritten in function body before being used. ConvexCast.cpp 31

V763参数“ count”在使用前总是在功能体内被重写。 ConvexCast.cpp 31

StupidComplexOfConvexShapes (...., int count)
{
  count = 40;
  //count = 1;
  ....
}

V547 Expression 'axisCount' is always false. MultiBodyCar.cpp 650

V547表达式'axisCount'始终为false。 MultiBodyCar.cpp 650

void UpdateDriverInput(dVehicle* const vehicle, dFloat timestep) 
{
  ....
  int axisCount = scene->GetJoystickAxis(axis);
  axisCount = 0;
  if (axisCount)
  {
    ....
  }
  ....
}

Many of you might argue that changes like that to publicly available code should be, at the very least, commented. Well, I'm with you on this one. I believe that certain features that are fine for a pet project shouldn't be allowed in a project intended for use by many people. But the choice is still up to the authors.

你们中许多人可能会争辩说,至少应该评论对公开代码所做的更改。 好吧,我和你在一起。 我认为,在供许多人使用的项目中,不允许使用某些适合宠物项目的功能。 但是选择仍然取决于作者。

警告2 (Warning 2)

V769 The 'result' pointer in the 'result + i' expression equals nullptr. The resulting value is senseless and it should not be used. win32_monitor.c 286

V769“结果+ i”表达式中的“结果”指针等于nullptr。 结果值是没有意义的,不应使用。 win32_monitor.c 286

GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
{
  GLFWvidmode* result = NULL;
  ....
  for (i = 0;  i < *count;  i++)
    {
    if (_glfwCompareVideoModes(result + i, &mode) == 0)
      break;
    }
}

The problem here is that result doesn't change once it's initialized. The resulting pointer is pointless; you can't use it.

这里的问题是,初始化后结果不会改变。 结果指针是没有意义的。 你不能使用它。

警告3、4、5 (Warnings 3, 4, 5)

V778 Two similar code fragments were found. Perhaps, this is a typo and 'm_colorChannel' variable should be used instead of 'm_binormalChannel'. dgMeshEffect1.cpp 1887

V778找到两个相似的代码片段。 也许这是一个错字,应该使用“ m_colorChannel”变量而不是“ m_binormalChannel”。 dgMeshEffect1.cpp 1887

void dgMeshEffect::EndBuildFace ()
{
  ....
  if (m_attrib.m_binormalChannel.m_count) <=
  {
    attibutes.m_binormalChannel.
      PushBack(m_attrib.m_binormalChannel[m_constructionIndex + i]);
  }
  if (m_attrib.m_binormalChannel.m_count) <= 
  {
    attibutes.m_colorChannel.
      PushBack(m_attrib.m_colorChannel[m_constructionIndex + i]);
  }
}

The second condition seems to be a clone of the first one and was meant to look like this:

第二个条件似乎是第一个条件的克隆,并且应如下所示:

if (m_attrib.m_colorChannel.m_count) <= 
{
  attibutes.m_colorChannel.
  PushBack(m_attrib.m_colorChannel[m_constructionIndex + i]);
}

Here's another very similar bug:

这是另一个非常类似的错误:

V524 It is odd that the body of 'EnabledAxis1' function is fully equivalent to the body of 'EnabledAxis0' function. dCustomDoubleHingeActuator.cpp 88

V524'EnabledAxis1'函数的主体与'EnabledAxis0'函数的主体完全等效是很奇怪的。 dCustomDoubleHingeActuator.cpp 88

void dCustomDoubleHingeActuator::EnabledAxis0(bool state)
{
  m_axis0Enable = state;  <=
}
void dCustomDoubleHingeActuator::EnabledAxis1(bool state)
{
  m_axis0Enable = state;  <=
}

This one should be fixed as follows:

此问题应固定如下:

void dCustomDoubleHingeActuator::EnabledAxis1(bool state)
{
  m_axis1Enable = state;
}

Another copy-paste error:

另一个复制粘贴错误:

V525 The code contains the collection of similar blocks. Check items 'm_x', 'm_y', 'm_y' in lines 73, 74, 75. dWoodFracture.cpp 73

V525该代码包含相似块的集合。 在第73、74、75行中检查项目'm_x','m_y','m_y'。dWoodFracture.cpp 73

WoodVoronoidEffect(....)
{
  ....
  for (int i = 0; i < count; i ++) 
  {
    dFloat x = dGaussianRandom(size.m_x * 0.1f);
    dFloat y = dGaussianRandom(size.m_y * 0.1f);  <=
    dFloat z = dGaussianRandom(size.m_y * 0.1f);  <=
  ....
  }
  ....
}

I guess the z variable should be initialized as follows:

我猜应该将z变量初始化如下:

dFloat z = dGaussianRandom(size.m_z * 0.1f);

警告6、7 (Warnings 6, 7)

Like any other big C or C++ project, Newton Game Dynamics failed to steer clear of unsafe pointer handling bugs. These are typically hard to find and debug and they cause programs to crash – that is, they are highly dangerous and unpredictable. Luckily, many of them are easily detected by our analyzer. It doesn't seem to be a very original idea that writing a check for a pointer and moving on with a light heart is way better than wasting time trying to reproduce the bug, tracing the problem spot, and debugging it, does it? Anyway, here are some of the warnings of this type:

像任何其他大型C或C ++项目一样,Newton Game Dynamics未能避免不安全的指针处理错误。 这些通常很难找到和调试,并且会导致程序崩溃–也就是说,它们非常危险且不可预测。 幸运的是,我们的分析仪可以很容易地检测出其中许多。 这似乎不是一个非常原始的想法,写指针检查并轻松地前进比浪费时间尝试重现该错误,跟踪问题点并对其进行调试好吗? 无论如何,这是一些此类警告:

V522 There might be dereferencing of a potential null pointer 'face'. dgContactSolver.cpp 351

V522可能会取消引用潜在的空指针“面”。 dgContactSolver.cpp 351

DG_INLINE dgMinkFace* dgContactSolver::AddFace(dgInt32 v0,dgInt32 v1,
                                               dgInt32 v2)
{
  dgMinkFace* const face = NewFace();
  face->m_mark = 0; 
  ....
}

The implementation of the NewFace function isn't big, so I'll include it in full:

NewFace函数的实现并不大,因此我将其全部包含在内:

DG_INLINE dgMinkFace* dgContactSolver::NewFace()
{
  dgMinkFace* face = (dgMinkFace*)m_freeFace;
  if (m_freeFace) 
  {
    m_freeFace = m_freeFace->m_next;
  } else 
  {
    face = &m_facePool[m_faceIndex];
    m_faceIndex++;
    if (m_faceIndex >= DG_CONVEX_MINK_MAX_FACES) 
    {
      return NULL;
    }
  }
#ifdef _DEBUG
    memset(face, 0, sizeof (dgMinkFace));
#endif
  return face;
}

In one of its exit points, the NewFace function returns NULL, which will in its turn lead to null pointer dereferencing with undefined behavior as a result.

在其退出点之一中, NewFace函数返回NULL ,这又将导致空指针取消引用,结果是未定义的行为。

Here's a similar case of null pointer dereferencing, but more dangerous:

这是空指针解引用的类似情况,但更危险:

V522 There might be dereferencing of a potential null pointer 'perimeter'. dgPolyhedra.cpp 2541

V522可能会取消引用潜在的空指针“周长”。 dgPolyhedra.cpp 2541

bool dgPolyhedra::PolygonizeFace(....)
{
  ....
  dgEdge* const perimeter = flatFace.AddHalfEdge
                           (edge1->m_next->m_incidentVertex,
                            edge1->m_incidentVertex);
  perimeter->m_twin = edge1;
  ....
}

Here's the implementation of AddHalfEdge:

这是AddHalfEdge的实现:

dgEdge* dgPolyhedra::AddHalfEdge (dgInt32 v0, dgInt32 v1)
{
  if (v0 != v1) 
  {
    dgPairKey pairKey (v0, v1);
    dgEdge tmpEdge (v0, -1);
    dgTreeNode* node = Insert (tmpEdge, pairKey.GetVal()); 
    return node ? &node->GetInfo() : NULL;
  } else 
  {
    return NULL;
  }
}

This time, NULL is returned at two exit points out of three.

这次,在三个出口中的两个出口处返回NULL

In total, the analyzer issued 48 V522 warnings. They are similar for the most part, so I don't see any point in discussing more here.

分析仪总共发出了48条V522警告。 它们在大多数情况下是相似的,因此我在这里没有更多讨论。

警告8 (Warning 8)

V668 There is no sense in testing the 'pBits' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. TargaToOpenGl.cpp 166

V668没有意义将'pBits'指针针对null进行测试,因为使用'new'运算符分配了内存。 如果内存分配错误,将生成异常。 166.第166章

char* const pBits = new char [width * height * 4];
if(pBits == NULL) 
{
  fclose(pFile);
  return 0;
}

The value of the pointer returned by the new operator is compared with zero. This usually means that you'll get unexpected behavior if memory allocation fails. When the new operator fails to allocate the required storage, a std::bad_alloc() exception should be thrown, as prescribed by the C++ standard. In this particular case, it means the condition will never execute, which is obviously different from the behavior the programmer counted on. They wanted the program to close the file in the case of memory allocation failure. But the program won't do that and will instead end up with a resource leak.

new运算符返回的指针值与零进行比较。 这通常意味着,如果内存分配失败,您将得到意外的行为。 当新的经营者不分配所需的存储,一个std :: bad_alloc的()异常应被抛出,由C ++标准来规定。 在这种特殊情况下,这意味着条件将永远不会执行,这显然与程序员所依赖的行为不同。 他们希望程序在内存分配失败的情况下关闭文件。 但是该程序不会这样做,而最终会导致资源泄漏。

警告9、10、11 (Warnings 9, 10, 11)

V764 Possible incorrect order of arguments passed to 'CreateWheel' function: 'height' and 'radius'. StandardJoints.cpp 791

V764传递给'CreateWheel'函数的参数的可能错误顺序:'height'和'radius'。 第791章

V764 Possible incorrect order of arguments passed to 'CreateWheel' function: 'height' and 'radius'. StandardJoints.cpp 833

V764传递给'CreateWheel'函数的参数的可能错误顺序:'height'和'radius'。 StandardJoints.cpp 833

V764 Possible incorrect order of arguments passed to 'CreateWheel' function: 'height' and 'radius'. StandardJoints.cpp 884

V764传递给'CreateWheel'函数的参数的可能错误顺序:'height'和'radius'。 StandardJoints.cpp 884

These are the calls to the function:

这些是对该函数的调用:

NewtonBody* const wheel = CreateWheel (scene, origin, height, radius);

And this is its declaration:

这是它的声明:

static NewtonBody* CreateWheel (DemoEntityManager* const scene,
  const dVector& location, dFloat radius, dFloat height)

This diagnostic detects function calls with presumably swapped arguments.

该诊断程序检测带有大概交换参数的函数调用。

警告12、13 (Warnings 12, 13)

The analyzer issued warnings on two similar methods of different names:

分析器针对两种不同名称的类似方法发出警告:

V621 Consider inspecting the 'for' operator. It's possible that the loop will be executed incorrectly or won't be executed at all. dgCollisionUserMesh.cpp 161

V621考虑检查“ for”操作员。 循环有可能执行不正确或根本不执行。 dgCollisionUserMesh.cpp 161

V621 Consider inspecting the 'for' operator. It's possible that the loop will be executed incorrectly or won't be executed at all. dgCollisionUserMesh.cpp 236

V621考虑检查“ for”操作员。 循环有可能执行不正确或根本不执行。 dgCollisionUserMesh.cpp 236

void dgCollisionUserMesh::GetCollidingFacesContinue
    (dgPolygonMeshDesc* const data) const
{
  ....
  data->m_faceCount = 0; <=
  data->m_userData = m_userData;
  data->m_separationDistance = dgFloat32(0.0f);
  m_collideCallback(&data->m_p0, NULL);
  dgInt32 faceCount0 = 0;
  dgInt32 faceIndexCount0 = 0;
  dgInt32 faceIndexCount1 = 0;
  dgInt32 stride = data->m_vertexStrideInBytes / sizeof(dgFloat32);
  dgFloat32* const vertex = data->m_vertex;
  dgInt32* const address = data->m_meshData.m_globalFaceIndexStart;
  dgFloat32* const hitDistance = data->m_meshData.m_globalHitDistance;
  const dgInt32* const srcIndices = data->m_faceVertexIndex;
  dgInt32* const dstIndices = data->m_globalFaceVertexIndex;
  dgInt32* const faceIndexCountArray = data->m_faceIndexCount;
  for (dgInt32 i = 0; (i < data->m_faceCount)&&
       (faceIndexCount0 < (DG_MAX_COLLIDING_INDICES - 32));
       i++)
  {
    ....
  }
  ....
}
void dgCollisionUserMesh::GetCollidingFacesDescrete
    (dgPolygonMeshDesc* const data) const
{
  ....
  data->m_faceCount = 0; <=  
  data->m_userData = m_userData;
  data->m_separationDistance = dgFloat32(0.0f);
  m_collideCallback(&data->m_p0, NULL);
  dgInt32 faceCount0 = 0;
  dgInt32 faceIndexCount0 = 0;
  dgInt32 faceIndexCount1 = 0;
  dgInt32 stride = data->m_vertexStrideInBytes / sizeof(dgFloat32);
  dgFloat32* const vertex = data->m_vertex;
  dgInt32* const address = data->m_meshData.m_globalFaceIndexStart;
  dgFloat32* const hitDistance = data->m_meshData.m_globalHitDistance;
  const dgInt32* const srcIndices = data->m_faceVertexIndex;
  dgInt32* const dstIndices = data->m_globalFaceVertexIndex;
  dgInt32* const faceIndexCountArray = data->m_faceIndexCount;
  for (dgInt32 i = 0; (i < data->m_faceCount)&&
       (faceIndexCount0 < (DG_MAX_COLLIDING_INDICES - 32));
       i++)
  {
    ....
  }
  ....
}

The problem spot is the i < data->m_faceCount part of the condition.Since data->m_faceCount is assigned the value 0, this loop won't execute even once. I guess the programmer forgot to reinitialize the m_faceCount field and simply cloned the method's body.

问题点是条件的i <data-> m_faceCount部分。由于data-> m_faceCount被赋值为0,因此该循环甚至不会执行一次。 我猜程序员忘记了重新初始化m_faceCount字段,而只是克隆了方法的主体。

警告14、15 (Warnings 14, 15)

The analyzer issued two warnings on two similar adjacent lines:

分析仪在两条相似的相邻线上发出了两个警告:

V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. dgSkeletonContainer.cpp 1341

V630'_alloca'函数用于为对象数组分配内存,这些对象数组是包含构造函数的类。 dgSkeletonContainer.cpp 1341

V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. dgSkeletonContainer.cpp 1342

V630'_alloca'函数用于为对象数组分配内存,这些对象数组是包含构造函数的类。 dgSkeletonContainer.cpp 1342

#define alloca _alloca
....
#define dAlloca(type,size) (type*) alloca ((size) * sizeof (type))
....
dgSpatialMatrix::dgSpatialMatrix();
dgSpatialMatrix::dgSpatialMatrix(dgFloat32 val);
....
dgSpatialMatrix* const bodyMassArray = dgAlloca(dgSpatialMatrix,
                                                m_nodeCount);
dgSpatialMatrix* const jointMassArray = dgAlloca(dgSpatialMatrix,
                                                 m_nodeCount);

The problem with this code is that the allocated memory block is being handled as if it were an array of objects that have a constructor or destructor. But when memory is allocated the way it's done here, the constructor won't be called. Neither will the destructor be called when freeing the memory. This code is very suspicious. The program may end up handling uninitialized variables and running into other troubles. Another problem with this approach is that, unlike with the malloc/free technique, you won't get an explicit error message if you try to have more memory allocated than the machine could provide. Instead, you'll get a segmentation error when trying to access that memory. A few more messages of this type:

此代码的问题在于,分配的内存块的处理方式就像是具有构造函数或析构函数的对象数组一样。 但是,如果按照此处的方式分配内存,则不会调用构造函数。 释放内存时也不会调用析构函数。 此代码非常可疑。 该程序可能最终会处理未初始化的变量并遇到其他麻烦。 这种方法的另一个问题是,与使用malloc / free技术不同,如果尝试分配的内存比计算机可以提供的更多,则不会收到明确的错误消息。 相反,尝试访问该内存时会出现分段错误。 此类型的更多消息:

  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. dVehicleSolver.cpp 498

    V630'_alloca'函数用于为对象数组分配内存,这些对象数组是包含构造函数的类。 dVehicleSolver.cpp 498
  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. dVehicleSolver.cpp 499

    V630'_alloca'函数用于为对象数组分配内存,这些对象数组是包含构造函数的类。 dVehicleSolver.cpp 499
  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. dVehicleSolver.cpp 1144

    V630'_alloca'函数用于为对象数组分配内存,这些对象数组是包含构造函数的类。 dVehicleSolver.cpp 1144
  • About 10 more warnings like that.

    大约还有10条这样的警告。

结论 (Conclusion)

As usual, PVS-Studio didn't let us down and found a few interesting bugs. And that means it's doing great and helps make the world a better place. If you want to try PVS-Studio on your own project, you can get it here.

和往常一样,PVS-Studio并没有让我们失望,而是发现了一些有趣的错误。 这意味着它做得很好,有助于使世界变得更美好。 如果您想在自己的项目上尝试PVS-Studio,可以在这里获得。

翻译自: https://habr.com/en/company/pvs-studio/blog/498176/

pvs-stdio ue4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值