Документация по анализатору кода PVS-Studio (одним файлом)

Вы можете загрузить всю документацию по PVS-Studio одним файлом.

  • Введение
  • Применение анализатора кода
  • Настройки PVS-Studio
  • Описание диагностируемых ошибок
    • Проблемы при работе анализатора кода
      • V001. A code fragment from 'file' cannot be analyzed.
      • V002. Some diagnostic messages may contain incorrect line number.
      • V003. Unrecognized error found...
      • V004. For a more precise verification, please select x64 or Itanium configuration instead of Win32.
      • V005. Cannot determine active configuration for project. Please check projects and solution configurations.
      • V006. File cannot be processed. Analysis aborted by timeout.
      • V007. Verification of CLR projects is not implemented. Incorrect diagnostics are possible.
      • V008. Unable to start the analysis on this file.
      • V009. Intel C++ project toolset is not supported. Select Visual C++ toolset to verify this project.
    • Диагностика 64-битных ошибок (Viva64)
      • V101. Implicit assignment type conversion to memsize type.
      • V102. Usage of non memsize type for pointer arithmetic.
      • V103. Implicit type conversion from memsize to 32-bit type.
      • V104. Implicit type conversion to memsize type in an arithmetic expression.
      • V105. N operand of '?:' operation: implicit type conversion to memsize type.
      • V106. Implicit type conversion N argument of function 'foo' to memsize type.
      • V107. Implicit type conversion N argument of function 'foo' to 32-bit type.
      • V108. Incorrect index type: 'foo[not a memsize-type]'. Use memsize type instead.
      • V109. Implicit type conversion of return value to memsize type.
      • V110. Implicit type conversion of return value from memsize type to 32-bit type.
      • V111. Call of function 'foo' with variable number of arguments. N argument has memsize type.
      • V112. Dangerous magic number N used.
      • V113. Implicit type conversion from memsize to double type or vice versa.
      • V114. Dangerous explicit type pointer conversion.
      • V115. Memsize type is used for throw.
      • V116. Memsize type is used for catch.
      • V117. Memsize type is used in the union.
      • V118. malloc() function accepts a dangerous expression in the capacity of an argument.
      • V119. More than one sizeof() operator is used in one expression.
      • V120. Member operator[] of object 'foo' declared with 32-bit type argument, but called with memsize type argument.
      • V121. Implicit conversion of the type of 'new' operator's argument to size_t type.
      • V122. Memsize type is used in the struct/class.
      • V123. Allocation of memory by the pattern "(X*)malloc(sizeof(Y))"
      • V124. Function 'Foo' writes/reads 'N' bytes. The alignment rules and type sizes have been changed. Consider reviewing this value.
      • V125. It is not advised to declare type 'T' as 32-bit type.
      • V126. Be advised that the size of the type 'long' varies between LLP64/LP64 data models.
      • V127. An overflow of the 32-bit variable is possible inside a long cycle which utilizes a memsize-type loop counter.
      • V201. Explicit conversion from 32-bit integer type to memsize type.
      • V202. Explicit conversion from memsize type to 32-bit integer type.
      • V203. Explicit type conversion from memsize to double type or vice versa.
      • V204. Explicit conversion from 32-bit integer type to pointer type.
      • V205. Explicit conversion of pointer type to 32-bit integer type.
      • V220. Suspicious sequence of types castings: memsize -> 32-bit integer -> memsize.
      • V301. Unexpected function overloading behavior. See N argument of function 'foo' in derived class 'derived' and base class 'base'.
      • V302. Member operator[] of 'foo' class has a 32-bit type argument. Use memsize-type here.
      • V303. The function is deprecated in the Win64 system. It is safer to use the 'foo' function.
    • Диагностика общего назначения (General Analysis)
      • V501. There are identical sub-expressions to the left and to the right of the 'foo' operator.
      • V502. Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the 'foo' operator.
      • V503. This is a nonsensical comparison: pointer < 0.
      • V504. It is highly probable that the semicolon ';' is missing after 'return' keyword.
      • V505. The 'alloca' function is used inside the loop. This can quickly overflow stack.
      • V506. Pointer to local variable 'X' is stored outside the scope of this variable. Such a pointer will become invalid.
      • V507. Pointer to local array 'X' is stored outside the scope of this array. Such a pointer will become invalid.
      • V508. The use of 'new type(n)' pattern was detected. Probably meant: 'new type[n]'.
      • V509. The 'throw' operator inside the destructor should be placed within the try..catch block. Raising exception inside the destructor is illegal.
      • V510. The 'Foo' function is not expected to receive class-type variable as 'N' actual argument.
      • V511. The sizeof() operator returns size of the pointer, and not of the array, in given expression.
      • V512. A call of the 'Foo' function will lead to a buffer overflow or underflow.
      • V513. Use _beginthreadex/_endthreadex functions instead of CreateThread/ExitThread functions.
      • V514. Dividing sizeof a pointer by another value. There is a probability of logical error presence.
      • V515. The 'delete' operator is applied to non-pointer.
      • V516. Consider inspecting an odd expression. Non-null function pointer is compared to null.
      • V517. The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence.
      • V518. The 'malloc' function allocates strange amount of memory calculated by 'strlen(expr)'. Perhaps the correct variant is strlen(expr) + 1.
      • V519. The 'x' variable is assigned values twice successively. Perhaps this is a mistake.
      • V520. The comma operator ',' in array index expression.
      • V521. Such expressions using the ',' operator are dangerous. Make sure the expression is correct.
      • V522. Dereferencing of the null pointer might take place.
      • V523. The 'then' statement is equivalent to the 'else' statement.
      • V524. It is odd that the body of 'Foo_1' function is fully equivalent to the body of 'Foo_2' function.
      • V525. The code containing the collection of similar blocks. Check items X, Y, Z, ... in lines N1, N2, N3, ...
      • V526. The 'strcmp' function returns 0 if corresponding strings are equal. Consider examining the condition for mistakes.
      • V527. It is odd that the 'zero' value is assigned to pointer. Probably meant: *ptr = zero.
      • V528. It is odd that pointer is compared with the 'zero' value. Probably meant: *ptr != zero.
      • V529. Odd semicolon ';' after 'if/for/while' operator.
      • V530. The return value of function 'Foo' is required to be utilized.
      • V531. It is odd that a sizeof() operator is multiplied by sizeof().
      • V532. Consider inspecting the statement of '*pointer++' pattern. Probably meant: '(*pointer)++'.
      • V533. It is likely that a wrong variable is being incremented inside the 'for' operator. Consider reviewing 'X'.
      • V534. It is likely that a wrong variable is being compared inside the 'for' operator. Consider reviewing 'X'.
      • V535. The variable 'X' is being used for this loop and for the outer loop.
      • V536. Be advised that the utilized constant value is represented by an octal form.
      • V537. Consider reviewing the correctness of 'X' item's usage.
      • V538. The line contains control character 0x0B (vertical tabulation).
      • V539. Consider inspecting iterators which are being passed as arguments to function 'Foo'.
      • V540. Member 'x' should point to string terminated by two 0 characters.
      • V541. It is dangerous to print the string into itself.
      • V542. Consider inspecting an odd type cast: 'Type1' to ' Type2'.
      • V543. It is odd that value 'X' is assigned to the variable 'Y' of HRESULT type.
      • V544. It is odd that the value 'X' of HRESULT type is compared with 'Y'.
      • V545. Such conditional expression of 'if' operator is incorrect for the HRESULT type value 'Foo'. The SUCCEEDED or FAILED macro should be used instead.
      • V546. Member of a class is initialized by itself: 'Foo(Foo)'.
      • V547. Expression is always true/false.
      • V548. Consider reviewing type casting. TYPE X[][] in not equivalent to TYPE **X.
      • V549. The first argument of 'Foo' function is equal to the second argument.
      • V550. An odd precise comparison. It's probably better to use a comparison with defined precision: fabs(A - B) < Epsilon or fabs(A - B) > Epsilon.
      • V551. The code under this 'case' label is unreachable.
      • V552. A bool type variable is being incremented. Perhaps another variable should be incremented instead.
      • V553. The length of function's body or class's declaration is more than 2000 lines long. You should consider refactoring the code.
      • V554. Incorrect use of smart pointer.
      • V555. The expression of the 'A - B > 0' kind will work as 'A != B'.
      • V556. The values of different enum types are compared.
      • V557. Array overrun is possible.
      • V558. Function returns the pointer to temporary local object.
      • V559. Suspicious assignment inside the condition expression of 'if/while' operator.
      • V560. A part of conditional expression is always true/false.
      • V561. It's probably better to assign value to 'foo' variable than to declare it anew.
      • V562. It's odd to compare a bool type value with a value of N.
      • V563. It is possible that this 'else' branch must apply to the previous 'if' statement.
      • V564. The '&' or '|' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&&' or '||' operator.
      • V565. An empty exception handler. Silent suppression of exceptions can hide the presence of bugs in source code during testing.
      • V566. The integer constant is converted to pointer. Possibly an error or a bad coding style.
      • V567. Undefined behavior. The variable is modified while being used twice between sequence points.
      • V568. It's odd that the argument of sizeof() operator is the expression.
      • V569. Truncation of constant value.
      • V570. The variable is assigned to itself.
      • V571. Recurring check. This condition was already verified in previous line.
      • V572. It is odd that the object which was created using 'new' operator is immediately casted to another type.
      • V573. Uninitialized variable 'Foo' was used. The variable was used to initialize itself.
      • V574. The pointer is used simultaneously as an array and as a pointer to single object.
      • V575. Function receives an odd argument.
      • V576. Incorrect format. Consider checking the N actual argument of the 'Foo' function.
      • V577. Label is present inside a switch(). It is possible that these are misprints and 'default:' operator should be used instead.
      • V578. An odd bitwise operation detected. Consider verifying it.
      • V579. The 'Foo' function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the N argument.
      • V580. An odd explicit type casting. Consider verifying it.
      • V581. The conditional expressions of the 'if' operators situated alongside each other are identical.
      • V582. Consider reviewing the source code which operates the container.
      • V583. The '?:' operator, regardless of its conditional expression, always returns one and the same value.
      • V584. The same value is present on both sides of the operator. The expression is incorrect or it can be simplified.
      • V585. An attempt to release the memory in which the 'Foo' local variable is stored.
      • V586. The 'Foo' function is called twice for deallocation of the same resource.
      • V587. An odd sequence of assignments of this kind: A = B; B = A;
      • V588. The expression of the 'A =+ B' kind is utilized. Consider reviewing it, as it is possible that 'A += B' was meant.
      • V589. The expression of the 'A =- B' kind is utilized. Consider reviewing it, as it is possible that 'A -= B' was meant.
      • V590. Consider inspecting this expression. The expression is excessive or contains a misprint.
      • V591. Non-void function should return a value.
      • V592. The expression was enclosed by parentheses twice: ((expression)). One pair of parentheses is unnecessary or misprint is present.
      • V593. Consider reviewing the expression of the 'A = B == C' kind. The expression is calculated as following: 'A = (B == C)'.
      • V594. The pointer steps out of array's bounds.
      • V595. The pointer was utilized before it was verified against nullptr. Check lines: N1, N2.
      • V596. The object was created but it is not being used. The 'throw' keyword could be missing.
      • V597. The compiler could delete the 'memset' function call, which is used to flush 'Foo' buffer. The RtlSecureZeroMemory() function should be used to erase the private data.
      • V598. The 'memset/memcpy' function is used to nullify/copy the fields of 'Foo' class. Virtual method table will be damaged by this.
      • V599. The virtual destructor is not present, although the 'Foo' class contains virtual functions.
      • V600. Consider inspecting the condition. The 'Foo' pointer is always not equal to NULL.
      • V601. An odd implicit type casting.
      • V602. Consider inspecting this expression. '<' possibly should be replaced with '<<'.
      • V603. The object was created but it is not being used. If you wish to call constructor, 'this->Foo::Foo(....)' should be used.
      • V604. It is odd that the number of iterations in the loop equals to the size of the pointer.
      • V605. Consider verifying the expression. An unsigned value is compared to the number – NN.
      • V606. Ownerless token 'Foo'.
      • V607. Ownerless expression 'Foo'.
      • V608. Recurring sequence of explicit type casts.
      • V609. Divide or mod by zero.
      • V610. Undefined behavior. Check the shift operator.
      • V611.The memory allocation and deallocation methods are incompatible.
      • V612. An unconditional 'break/continue/return/goto' within a loop.
      • V613. Strange pointer arithmetic with 'malloc/new'.
      • V614. Uninitialized variable 'Foo' used.
    • Диагностика анализа производительности (Optimization)
      • V801. Decreased performance. It is better to redefine the N function argument as a reference. Consider replacing 'const T' with 'const .. &T' / 'const .. *T'.
      • V802. On 32-bit/64-bit platform, structure size can be reduced from N to K bytes by rearranging the fields according to their sizes in decreasing order.
      • V803. Decreased performance. It is more effective to use the prefix form of ++it. Replace iterator++ with ++iterator.
      • V804. Decreased performance. The 'Foo' function is called twice in the specified expression to calculate length of the same string.
      • V805. Decreased performance. It is inefficient to identify an empty string by using 'strlen(str) > 0' construct. A more efficient way is to check: str[0] != '\\0'
      • V806. Decreased performance. The expression of strlen(MyStr.c_str()) kind can be rewritten as MyStr.length().
      • V807. Decreased performance. Consider creating a pointer/reference to avoid using the same expression repeatedly.
    • Диагностика параллельных ошибок (VivaMP)
      • V1000. Did you forget to enable the /openmp compiler option?
      • V1001. Missing 'parallel' keyword.
      • V1002. Missing 'omp' keyword.
      • V1003. Missing 'for' keyword. Each thread will execute the entire loop.
      • V1004. Nested parallelization of a 'for' loop.
      • V1005. The 'ordered' directive is not present in an ordered loop.
      • V1006. Missing omp.h header file. Use '#include <omp.h>'.
      • V1101. Redefining number of threads in a parallel code.
      • V1102. Non-symmetrical use of set/unset functions for the following lock variable(s): foo.
      • V1103. Threads number dependent code. The 'omp_get_num_threads' function is used in an arithmetic expression.
      • V1104. Redefining nested parallelism in a parallel code.
      • V1201. Concurrent usage of a shared resource via an unprotected call of the 'foo' function.
      • V1202. The 'flush' directive should not be used for the 'foo' variable, because the variable has pointer type.
      • V1203. Using the 'threadprivate' directive is dangerous, because it affects the entire file. Use local variables or specify access type for each parallel block explicitly instead.
      • V1204. Data race risk. Unprotected static variable declaration in a parallel code.
      • V1205. Data race risk. Unprotected concurrent operation with the 'foo' variable.
      • V1206. Data race risk. The value of the 'foo' variable can be changed concurrently via the 'bar' function.
      • V1207. Data race risk. The 'foo' object can be changed concurrently by a non-const function.
      • V1208. The 'foo' variable of reference type cannot be private.
      • V1209. Warning: The 'foo' variable of pointer type should not be private.
      • V1210. The 'foo' variable is marked as lastprivate but is not changed in the last section.
      • V1211. The use of 'flush' directive has no sense for private 'foo' variable, and can reduce performance.
      • V1212. Data race risk. When accessing the array 'foo' in a parallel loop, different indexes are used for writing and reading.
      • V1301. The 'throw' keyword cannot be used outside of a try..catch block in a parallel section.
      • V1302. The 'new' operator cannot be used outside of a try..catch block in a parallel section.
      • V1303. The 'foo' function which throws an exception cannot be used in a parallel section outside of a try..catch block.
    • Реализовано по запросам пользователей
      • V2001. Consider using the extended version of the 'foo' function here: bar.
      • V2002. Consider using the 'Ptr' version of the 'foo' function here: bar.
      • V2003. Explicit conversion from 'float/double' type to signed integer type.
      • V2004. Explicit conversion from 'float/double' type to unsigned integer type.
      • V2005. C-style explicit type casting is utilized. Consider using: static_cast/const_cast/reinterpret_cast.
  • Дополнительная информация

PVS-Studio в двух словах

Мы делаем статический анализатор кода PVS-Studio для C/C++/C++11.

PVS-Studio - это статический анализатор C/C++ кода (Visual Studio 2005/2008/2010) с простой лицензионной и ценовой политикой, который легко установить и использовать без необходимости развертывания сложного окружения.

Для его продвижения мы проверяем код известных проектов, таких как Chromium, WinMerge, TortoiseSVN, Apache HTTP Server, Qt, Clang и публикуем результаты в виде статей.

Знакомство с анализатором кода PVS-Studio

Аннотация

В статье приведено краткое описание анализатора кода PVS-Studio.

Введение

PVS-Studio - статический анализатор кода компании ООО "СиПроВер" предназначен для разработчиков современных ресурсоемких приложений. Объединяя в себе возможности анализа 64-битного кода из модуля Viva64, параллельного кода из модуля VivaMP и анализа общего назначения. PVS-Studio позволяет разрабатывать, тестировать, выполнять миграцию и верификацию, и, конечно же, создавать приложения на C/C++/C++11 с высоким уровнем надежности.

Компания ООО "СиПроВер" занимается разработкой и продажей анализаторов кода. Наши продукты: Viva64 - анализатор кода для миграции и разработки 64-битных приложений, и VivaMP - анализатор кода для верификации параллельных OpenMP программ. Эти анализаторы оказались востребованными одной группой пользователей. Поэтому мы подготовили новый программный продукт PVS-Studio, который обединяет в себе эти два инструмента и предоставляет пользователям единое решение для разработки современных ресурсоемких приложений на C/C++/C++11. В анализатор PVS-Studio также был добавлен новый набор диагностик общего назначения.

Возможности

Установка PVS-Studio достаточно проста. На машине должна быть установлена среда Microsoft Visual Studio 2005/2008/2010. Для анализа 64-битных приложений желательно также иметь 64-битный компилятор, входящий в состав Visual Studio.

После установки PVS-Studio интегрируется в меню Visual Studio как показано на рисунке.

Рисунок 1 - Интеграция PVS-Studio в Microsoft Visual Studio

Рисунок 1 - Интеграция PVS-Studio в Microsoft Visual Studio

Можно выделить следующие наборы правил, включенных в состав PVS-Studio:

  • Диагностика 64-битных ошибок (Viva64)
  • Диагностика параллельных ошибок (VivaMP)
  • Диагностика общего назначения
  • Диагностика возможных оптимизаций

PVS-Studio позволяет обнаруживать в исходном коде программ на C/C++/C++0x следующие типы дефектов:

  • ошибки миграции 32-битных приложений на 64-битные системы;
  • ошибки, возникающие при разработке новых 64-битных приложений;
  • неоптимальное использование памяти в 64-битных программах вследствие особенностей выравнивания;
  • ошибки в параллельных программах, связанные с незнанием синтаксиса технологии OpenMP;
  • ошибки в параллельных программах, связанные с недостаточным знанием принципов распараллеливания кода с использованием OpenMP;
  • ошибки из-за некорректной работы с памятью в параллельном коде (незащищенный доступ к общей памяти, отсутствие синхронизации, неправильный режим доступа к переменным, и т. п.).

Все эти группы дефектов возникают как в новых приложениях, так и в старых при попытке либо перенести их на 64-битную платформу, либо во время распараллеливания кода.

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

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

Все диагностируемые ошибки подробно описаны в online справочной системе, которая открывается по нажатию на сообщении об ошибке. Посмотреть справочную систему по PVS-Studio можно на нашем сайте и без установки программы.

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

Выводы

Анализатор кода PVS-Studio нужен тем, кто:

  • разрабатывает новые 64-битные приложения;
  • выполняет миграцию 32-битного кода на 64-битные системы;
  • добавляет в программу поддержку параллельного исполнения с помощью технологии OpenMP.

Системные требования анализатора PVS-Studio

Анализатор PVS-Studio предназначен для работы на Windows-платформе. При этом он интегрируется в среды разработки Microsoft Visual Studio 2005/2008/2010. Системные требования к анализатору совпадают с требованиями к Microsoft Visual Studio:

  • Среда разработки: Microsoft Visual Studio 2005/2008/2010. Для анализа 64-битных приложений желательно установить компонент Visual Studio под названием "X64 Compilers and Tools". Он входит во всех перечисленные версии Visual Studio и может быть установлен через Visual Studio Setup. Обратите внимание, что работа PVS-Studio с Visual C++ Express Edition не возможна, поскольку эта система не поддерживает модули расширения.
  • Операционная система: Windows XP/2003/Vista/2008/7 x86 или x64. Для анализа 64-битных приложений операционная система не обязательно должна быть 64-битной.
  • Аппаратная часть: PVS-Studio работает на системах с не менее чем 1 гигабайтом оперативной памяти (рекомендуется 2 гигабайта и более); анализатор поддерживает работу на нескольких ядрах (чем больше ядер, тем быстрее выполняется анализ кода).

Ознакомительный режим PVS-Studio

Для получения большей информации о заказе и условиях лицензирования PVS-Studio посетите страницу заказа на сайте www.viva64.com.

Мы придумали использовать концепцию "кликов" – это переход к исходному коду с ошибкой при помощи клика в списке сообщений. Скачав PVS-Studio без всяких регистраций, потенциальный пользователь получает полнофункциональную версию анализатора кода. Он может проверять ей какие угодно проекты сколько угодно раз. Он получает полный список сообщений. Но у него есть только какое-то ограниченное количество кликов (переходов к коду). Например, 100. Но число не принципиально и возможно мы его будем менять. Таким образом, пользователь может посмотреть и перейти к 100 диагностикам. Повторюсь, без каких либо ограничений.

Если пользователь израсходовал эти клики, то он должен принять одно из следующих решений:

  • либо купить лицензию;
  • либо отказаться от использования инструмента, если он ему не понравился;
  • либо попросить у нас продлить trial-режим, предоставив информацию о себе, чтобы мы могли уже как-то с ним поработать в почте.

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

А вот продление режима у нас в версии PVS-Studio 4.55 реализовано так. В программе автоматическая форма для указания этой информации, и после ее отправки пользователь будет получать еще, к примеру, 100 кликов. Продлеваться trial-режим будет один раз.

Если пользователь не хочет продлевать trial, то у него будут следующие ограничения:

  • При проверке новых проектов не будут выдаваться имена файлов с ошибками. Вместо них будет фраза "TRIAL RESTRICTION".
  • Если пользователь открывает заранее сохраненный лог с найденными ошибками, то у него не будет работать переход к коду по клику.

Конечно же, пользователь, даже имея 0 кликов, может открыть заранее сохраненный отчет и вручную выполнять навигацию – открывать файл, переходить к соответствующей строке в нем. Это сделать можно. Но надо понимать, что статический анализ – это инструмент, который, прежде всего, позволяет экономить время (за счет раннего обнаружения ошибок в программах до релиза, а не после). Если же время потенциального пользователя дешевое или бесплатное настолько, что он готов делать навигацию вручную, то статический анализ не для него и он не наш клиент в любом случае.

История версий PVS-Studio

PVS-Studio 4.61 (22 мая 2012)

  • Навигация для сообщений с несколькими номерами строк. Некоторые сообщения (например, V595) относятся к нескольким строкам кода. Раньше в окне PVS-Studio Output Window выдавался в столбце Line только один номер строки, а остальные строки указывались в тексте сообщения. Из-за этого навигация была затруднена. Теперь в поле Line могут выводиться несколько номеров строк, по которым возможно осуществлять навигацию.
  • Новая сборка Clang включена в дистрибутив. В PVS-Studio используется Clang в качестве препроцессора. В новой сборке исправлено несколько незначительных ошибок. Обратите внимание, что мы не используем Clang для диагностики ошибок.
  • Новая диагностика – V612. An unconditional 'break/continue/return/goto' within a loop.
  • Новая диагностика – V613. Strange pointer arithmetic with 'malloc/new'.
  • Новая диагностика – V614. Uninitialized variable 'Foo' used.

PVS-Studio 4.60 (18 апреля 2012)

  • Новая группа диагностических сообщений "Оптимизация" (OP) предлагает диагностику возможных оптимизаций. Это набор правил статического анализа для выявления участков кода в программах на языке C/C++/C++11, которые можно оптимизировать. Следует понимать, что статический анализатор решает задачи оптимизации в узкой нише микро-оптимизаций. Полный список диагностируемых ситуаций приведен в документации (коды V801-V807).
  • Существенно сокращено количество ложных срабатываний в анализаторе 64-битных ошибок (Viva64).
  • Теперь игнорируются ошибки в автогенерируемых файлах (MIDL).
  • Изменения в логике показа диалога сохранения отчета.
  • Исправлена работа на локализованной китайской версии Visual Studio (локаль zh).
  • Новая диагностика – V610. Undefined behavior. Check the shift operator.
  • Новая диагностика – V611. The memory allocation and deallocation methods are incompatible.

PVS-Studio 4.56 (14 марта 2012)

  • Добавлена опция TraceMode в Common Analyzer Settings. Настройка задаёт режим трассировки (протоколирования хода выполнения программы).
  • Исправлена ошибка при проверке Itanium-проектов.
  • Исправлена проблема вызова 64-битной версии clang.exe вместо 32-битной на 32-битной версии Windows при проверке проекта с выбранной платформой x64.
  • Изменено количество используемых при анализе ядер в инкрементальном режиме. Теперь для обычного анализа (Check solution/project/file) используется количество ядер, указанное в настройках. Для инкрементального анализа же используется другое количество ядер: если в настройках больше чем (количество ядер-1) и в системе несколько ядер, то (количество ядер-1); иначе – также как в настройках. Проще говоря, в инкрементальном анализе используется на одно ядро меньше, чтобы такой тип анализа не грузил систему.
  • Новая диагностика – V608. Recurring sequence of explicit type casts.
  • Новая диагностика – V609. Divide or mod by zero.

PVS-Studio 4.55 (28 февраля 2012)

  • Новое окно для продления триал-режима (без необходимости написания письма).
  • Исправлено падение при перезагрузке проекта во время работы анализатора кода.
  • В инсталляторе (при первой установке на машине) выдается запрос на включение инкрементального анализа в PVS-Studio. Если ранее PVS-Studio на машине уже устанавливалась, то запрос выдан не будет. Включить или выключить инкрементальный анализ можно также с помощью команды "Incremental Analysis after Build" в меню PVS-Studio.
  • Количество используемых ядер по-умолчанию устанавливается как значение, равное количеству ядер минус один. Это можно изменить с помощью опции ThreadCount в настройках PVS-Studio.
  • Новая статья в документации: "Режим инкрементального анализа PVS-Studio".
  • Изменения в версии для командной строки – теперь возможна обработка нескольких файлов за один запуск PVS-Studio.exe по аналогии с компилятором (cl.exe file1.cpp file2.cpp). Раньше можно было в командной версии только один файл обрабатывать за раз. Подробнее об использовании версии для командной строки смотрите в документации.
  • Возможность проверки проектов Microsoft Visual Studio для ARMv4 архитектуры удалена.
  • Новая диагностика V604. It is odd that the number of iterations in the loop equals to the size of the pointer.
  • Новая диагностика V605. Consider verifying the expression. An unsigned value is compared to the number – NN.
  • Новая диагностика V606. Ownerless token 'Foo'.
  • Новая диагностика V607. Ownerless expression 'Foo'.

PVS-Studio 4.54 (1 февраля 2012)

  • Новый trial-режим. Теперь ограничиваются только клики. Подробности в блоге и в документации.
  • Новая команда меню "Disable Incremental Analysis until IDE restart". Иногда бывает полезно отключить инкрементальный анализ. Например, при редактировании базовых h-файлов, что приводит к перекомпиляции большого количества файлов, Но отключить не навсегда, ведь тогда его можно забыть включить, а временно. Команда также доступна в системном трее во время инкрементального анализа.
  • Новая диагностика V602. Consider inspecting this expression. '<' possibly should be replaced with '<<'.
  • Новая диагностика V603. The object was created but it is not being used. If you wish to call constructor, 'this->Foo::Foo(....)' should be used.
  • Новая диагностика V807. Decreased performance. Consider creating a pointer/reference to avoid using the same expression repeatedly.
  • Новая статья в документации: "Команды меню PVS-Studio".

PVS-Studio 4.53 (19 января 2012)

  • Новая команда для совместной (групповой) работы над кодом "Add TODO comment for Task List". PVS-Studio позволяет автоматически сгенерировать и внести в код комментарий TODO специального вида, содержащий всю необходимую информацию для оценки и анализа отмеченного им фрагмента программы. Такой комментарий будет сразу отображён в окне Task List в Visual Studio.
  • Новая диагностика V599. The virtual destructor is not present, although the 'Foo' class contains virtual functions.
  • Новая диагностика V600. Consider inspecting the condition. The 'Foo' pointer is always not equal to NULL.
  • Новая диагностика V601. An odd implicit type casting.

PVS-Studio 4.52 (28 декабря 2011)

  • Изменения в режиме работы анализатора из командной строки без использования .sln-файла. Теперь анализатор можно запускать в несколько процессов одновременно, выходной файл (--output-file) не будет потерян. Также в параметр --cl-params надо передавать всю строку параметров cl.exe вместе с именем файла: --cl-params $(CFLAGS) $**.
  • Исправлена ошибка "Analysis aborted by timeout", появляющаяся при проверке .sln-файла из командной строки через PVS-Studio.exe.
  • Новая диагностика V597. The compiler could delete the 'memset' function call, which is used to flush 'Foo' buffer. The RtlSecureZeroMemory() function should be used to erase the private data.
  • Новая диагностика V598. The 'memset/memcpy' function is used to nullify/copy the fields of 'Foo' class. Virtual method table will be damaged by this.

PVS-Studio 4.51 (21 декабря 2011)

  • Исправлена проблема, связанная с директивой #import при использовании Clang в качестве препроцессора. Clang не поддерживает #import так, как это реализовано в Microsoft Visual C++, поэтому использовать Clang для таких файлов нельзя. Это определяется автоматически и для подобных файлов запускается препроцессор Visual C++.
  • Существенно переделана настройка в PVS-Studio исключения из анализа файлов и папок Don't Check Files. Теперь отдельно указываются исключаемые папки (по короткому или полному пути или маске) и отдельно – исключаемые файлы (по имени, расширению или также маске).
  • Некоторые библиотеки добавлены в список исключаемых путей по-умолчанию. Можно настроить на вкладке Don't Check Files.

PVS-Studio 4.50 (15 декабря 2011)

  • Для препроцессирования файлов в PVS-Studio используется внешний препроцессор. Раньше у нас использовался только один препроцессор от Microsoft Visual C++. В PVS-Studio 4.50 появилась поддержка второго препроцессора Clang, который работает существенно быстрее и лишен ряда недостатков препроцессора от Microsoft (хотя и имеет свои недостатки). Тем не менее, в большинстве случаев использование препроцессора Clang позволяет повысить скорость работы в 1.5-1.7 раз. Однако здесь есть нюанс, который надо учитывать. Указать используемый препроцессор можно в настройках PVS-Studio Options -> Common Analyzer Settings -> Preprocessor. Доступны варианты: VisualCPP, Clang и VisualCPPAfterClang. Первые два варианта очевидны, а третий вариант означает, что сначала будет использоваться Clang, в случае если при препроцессировании будут ошибки, то затем файл будет заново препроцессирован с помощью Visual C++. По-умолчанию выбрана именно эта опция (VisualCPPAfterClang).
  • Анализатор по-умолчанию не выдает диагностические сообщения на библиотеки zlib и libpng (это можно отключить).
  • Новая диагностика V596. The object was created but it is not being used. The 'throw' keyword could be missing.

PVS-Studio 4.39 (25 ноября 2011)

  • Добавлены новые диагностические правила (V594, V595).
  • Анализатор по-умолчанию не выдает диагностические сообщения на библиотеку Boost (это можно отключить).
  • При инкрементальном анализе не выводится диалог прогресса, вместо него в трее показывается иконка, с помощью которой можно сделать паузу или прервать анализ.
  • В контекстном меню результатов анализа появилась команда "Don't Check Files and hide all messages from ...". С её помощью можно удалить сообщения и в дальнейшем не проверять файлы из определенной папки. Список исключенных папок можно посмотреть в настройках Don't Check Files
  • Улучшено определение использования Intel C++ Compiler - PVS-Studio не работает с проектами, которые собираются этим компилятором, нужно сменить компилятор на Visual C++.
  • Добавлена функция "Quick Filters", позволяющая фильтровать все сообщения, не удовлетворяющие заданным фильтрам.

PVS-Studio 4.38 (12 октября 2011)

  • Повышена скорость работы анализатора (примерно на 25% для четырехъядерных машин).
  • В контекстное меню окна PVS-Studio добавлена команда перехода по ID ("Navigate to ID").
  • Новое окно "Find in PVS-Studio Output" позволяет осуществлять поиск по ключевым словам в результатах анализа.
  • Добавлены новые диагностические правила (V2005).
  • Кнопка Options переименована в Suppression, и в ней только три вкладки.

PVS-Studio 4.37 (20 сентября 2011)

  • Добавлены новые диагностические правила (V008, V2003, V2004).
  • Появилась возможность экспортировать результаты анализа в текстовый файл.
  • Теперь мы используем расширенный номер сборки в некоторых случаях.

PVS-Studio 4.36 (31 августа 2011)

  • Добавлены новые диагностические правила (V588, V589, V590, V591, V592, V593).

PVS-Studio 4.35 (12 августа 2011)

  • Добавлены новые диагностические правила (V583, V584, V806, V585, V586, V587).

PVS-Studio 4.34 (29 июля 2011)

  • 64-битный анализ по умолчанию отключен.
  • Инкрементальный анализ по умолчанию включен.
  • Изменения в поведении в ознакомительном режиме.
  • Появился предопределенный макрос PVS_STUDIO.
  • Исправлена проблема с инкрементальным анализом на локализованных версиях Visual Studio.
  • Всплывающие уведомления и иконка в трее добавлены (по завершении анализа).
  • Добавлены новые диагностические правила (V582).
  • Изменена картинка для показа с левой стороны мастера установки в дистрибутиве.

PVS-Studio 4.33 (21 июля 2011)

  • Режим работы "Incremental Analysis" теперь доступен во всех версиях Microsoft Visual Studio (2005/2008/2010), а не только в VS2010.
  • Повышена скорость работы анализатора (примерно на 20% для четырехъядерных машин).
  • Добавлены новые диагностические правила (V127, V579, V580, V581).

PVS-Studio 4.32 (15 июля 2011)

  • Изменения в лицензионной политике PVS-Studio.
  • Реализована динамическая балансировка загрузки процессора.
  • Кнопка останова анализа работает быстрее.

PVS-Studio 4.31 (6 июля 2011)

  • Исправлена проблема взаимодействия с некоторыми плагинами, включая Visual Assist.
  • Добавлены новые диагностические правила (V577, V578, V805).

PVS-Studio 4.30 (23 июня 2011)

  • Появилась полноценная поддержка работы анализатора из командной строки. Можно проверять отдельные файлы или группы файлов, вызывая анализатор из Makefile. При этом сообщения анализатора можно видеть не только на экране (для каждого файла), но и записать в один файл, который в дальнейшем можно открыть в Visual Studio и уже получить полноценную работу с результатами анализа: настройки кодов ошибок, фильтры сообщений, навигация по коду, сортировка и т.п. Подробнее.
  • Новый важный режим работы Incremental Analysis. PVS-Studio теперь может автоматически запускать анализ измененных файлов, которые надо пересобрать с помощью команды Build в Visual Studio. Теперь все разработчики команды могут сразу же видеть проблемы в новом только что написанном коде без необходимости явно запускать проверку кода – это происходит автоматически. Incremental Analysis работает подобно IntelliSense в Visual Studio. Возможность доступна только в Visual Studio 2010. Подробнее.
  • Добавлена команда "Check Selected Item(s)".
  • Изменения в запуске команды "Check Solution" при старте из командной строки. Подробнее.
  • Добавлены новые диагностические правила (V576).

PVS-Studio 4.21 (20 мая 2011)

  • Добавлены новые диагностические правила (V220, V573, V574, V575).
  • Добавлена поддержка TFS 2005/2008/2010.

PVS-Studio 4.20 (29 апреля 2011)

  • Добавлены новые диагностические правила (V571, V572).
  • Добавлена экспериментальная поддержка архитектур ARMV4/ARMV4I для Visual Studio 2005/2008 (Windows Mobile 5/6, PocketPC 2003, Smartphone 2003).
  • Новая опция "Show License Expired Message".

PVS-Studio 4.17 (15 апреля 2011)

  • Добавлены новые диагностические правила (V007, V570, V804).
  • Исправлено неправильное отображение времени анализа файлов при некоторых настройках локали.
  • Новая опция "Analysis Timeout". Данная настройка позволяет задать лимит времени, по истечении которого анализ отдельных файлов завершается с ошибкой V006, или совсем отключить прерывание анализа по лимиту времени.
  • Новая опция " Save File After False Alarm Mark". Сохранять или нет файл каждый раз после пометки как False Alarm.
  • Новая опция " Use Solution Folder As Initial". Определяет папку, которая открывается при сохранении файла с результатами анализа.

PVS-Studio 4.16 (1 апреля 2011)

  • При запуске инструмента из командной строки теперь можно задаться список файлов, которые будут проверяться. Это можно использовать для проверки, к примеру, только тех файлов, которые были обновлены в системе контроля версий. Подробнее.
  • В настройки инструмента добавлена опция "Check only Files Modified In". Данное поле позволяет задать временной интервал, для которого будет осуществляться проверка наличия изменений в анализируемых файлах с использованием файлового атрибута "Date Modified". Другими словами, такой подход позволит проверять "все файлы, которые были изменены за последний день". Подробнее.

PVS-Studio 4.15 (17 марта 2011)

  • Значительно уменьшено количество ложных срабатываний для 64-битного анализа.
  • Изменения в интерфейсе задания безопасных типов (safe-types).
  • Исправлена ошибка с обработкой stdafx.h в некоторых особых случаях.
  • Улучшена работа с файлом отчета.
  • Улучшен диалог прогресса: показывается затраченное время и сколько осталось.

PVS-Studio 4.14 (2 марта 2011)

  • Значительно уменьшено количество ложных срабатываний для 64-битного анализа.
  • Добавлены новые диагностические правила (V566, V567, V568, V569, V803).
  • В окне сообщений PVS-Studio появилась новая колонка "Звездочка". Теперь вы можете отметить интересные диагностики звездочкой для того, чтобы в дальнейшем обсудить их с коллегами. Пометки сохраняются в файл отчета.
  • Опции PVS-Studio теперь доступны не только из меню (в обычном диалоге настроек), но и в окне PVS-Studio. Благодаря этому настройка инструмента теперь делается быстрее и удобнее.
  • Добавлена возможность сохранять и восстанавливать настройки PVS-Studio. Это позволяет переносить настройки между различными компьютерами и рабочими местами. Также появилась команда "Восстановить настройки по-умолчанию".
  • Добавлено сохранение статус кнопок окна PVS-Studio (включено/выключено) при следующем запуске Microsoft Visual Studio.

PVS-Studio 4.13 (11 февраля 2011)

  • Добавлены новые диагностические правила (V563, V564, V565).
  • Команда "Check for updates" добавлена в меню PVS-Studio.
  • Команда "Hide all VXXX errors" для выключения ошибок по их коду добавлена в контекстное меню окна PVS-Studio. Если вы хотите включить обратно показдиагностик VXXX вы можете сделать это через страницу настроек PVS-Studio->Options->Detectable errors.
  • Подавление ложных предупреждений в макросах (#define) добавлено.

PVS-Studio 4.12 (7 февраля 2011)

  • Добавлены новые диагностические правила (V006, V204, V205, V559, V560, V561, V562).
  • Изменения в диагностических правилах V201 и V202.

PVS-Studio 4.11 (28 января 2011)

  • Диагностическое правило V401 заменено на V802.
  • Исправлена проблема с копированием сообщений в буфер обмена.

PVS-Studio 4.10 (17 января 2011)

  • Добавлены новые диагностические правила (V558).

PVS-Studio 4.00 (24 декабря 2010)

  • Добавлены новые диагностические правила (V546-V557).
  • Исправлена проблема с обработкой property sheets в Visual Studio 2010.
  • Исправлена ошибка с обходом дерева проектов.
  • В окно PVS-Studio добавлено поле "Project" - проект, к которому относится диагностическое сообщение.
  • Исправлена установка PVS-Studio для Visual Studio 2010 – теперь PVS-Studio устанавливается для всех пользователей, а не только для текущего.
  • Исправлено падение при попытке сохранить пустой файл отчета.
  • Исправлена проблема с отсутствующим файлом safe_types.txt.
  • Исправлена ошибка, возникающая при попытке проверить файлы, которые включены в проект, но реально на диске не существуют (например, это автогенерируемые файлы).
  • Добавлена индикация процесса обработки дерева проекта.
  • Файл с результатами анализа PVS-Studio (расширение .plog) теперь загружается по двойному клику.
  • Изменения в лицензионной политике.

PVS-Studio 4.00 BETA (24 ноября 2010)

  • Новый набор правил статического анализа общего назначения (V501-V545, V801).
  • Добавлены новые правила (V124-V126).
  • Изменения в лицензионной политике.
  • Новое окно для диагностических сообщений, выдаваемых анализатором.
  • Повышение скорости работы.

Старая история версий

Старую историю версий для прошлых релизов смотрите здесь.

Старая история версий PVS-Studio (до версии 4.00)

Новую историю версий смотрите здесь.

PVS-Studio 3.64 (28 сентября 2010)

  • Существенное обновление документации по PVS-Studio, добавлены новые разделы.

PVS-Studio 3.63 (10 сентября 2010)

  • Исправлена проблема, иногда возникающая при анализе файлов с не системного диска.
  • Исправлена проблема с вычислением значений макросов для отдельных файлов (а не всего проекта).
  • Команда "What Is It?" удалена.
  • Демонстрационные примеры проблем 64-битного кода (PortSample) и параллельного кода (ParallelSample) объединены в единый пример OmniSample, который подробно описан в документации.
  • Исправлено падение при наличии в решении выгруженного (unloaded) проекта.

PVS-Studio 3.62 (16 августа 2010)

  • Добавлено новое правило V123: Allocation of memory by the pattern "(X*)malloc(sizeof(Y))" where the sizes of X and Y types are not equal.
  • Упрощена работа с PVS-Studio из командной строки (без файла проекта Visual Studio).
  • По-умолчанию не выдаются сообщения об ошибках в tli/tlh файлах.

PVS-Studio 3.61 (22 июля 2010)

  • Исправлено падение в VS2010 при выставленном параметре EnableAllWarnings в настройках проекта.
  • Исправлена ошибка – проверялись даже те проекты, которые были исключены из сборки в Configuration Manager.
  • Улучшен анализ кода.

PVS-Studio 3.60 (10 июня 2010)

  • Добавлено новое правило V122: Memsize type is used in the struct/class.
  • Добавлено новое правило V303: The function is deprecated in the Win64 system. It is safer to use the NewFOO function.
  • Добавлено новое правило V2001: Consider using the extended version of the FOO function here.
  • Добавлено новое правило V2002: Consider using the 'Ptr' version of the FOO function here.

PVS-Studio 3.53 (7 мая 2010)

  • Добавлена возможность задать вопрос разработчикам PVS-Studio про сообщения, выдаваемые анализатором кода (команда "What Is It").
  • Значительно улучшен анализ кода, связанного с использованием неименованных структур.
  • Исправлена ошибка, приводившая в некоторых случаях к неверному вычислению размера структур.

PVS-Studio 3.52 (27 апреля 2010)

  • Добавлена новая online справочная система. Старая справочная система
    интегрировалась в MSDN. По некоторым причинам это неудобно (как
    пользователям, так и разработчикам). Теперь PVS-Studio будет открывать
    справку на нашем сайте. От интеграции с MSDN мы отказались. И, как и
    прежде, доступна pdf-версия документации.
  • Отказались от поддержки Windows 2000.
  • Удалена страница настроек "Exclude From Analysis" - вместо нее
    страница "Don't Check Files".
  • Улучшена работа в Visual Studio 2010.
  • Исправлена проблема с интеграцией в VS2010 при повторной инсталляции.
  • Исправлена работа функции "Mark As False Alarm" с read-only файлами.

PVS-Studio 3.51 (16 апреля 2010)

  • PVS-Studio поддерживает работу в Visual Studio 2010 RTM.
  • Добавлено новое правило V003: Unrecognized error found...
  • Добавлено новое правило V121: Implicit conversion of the type of 'new' operator's argument to size_t type.
  • Добавлена возможность исключить из анализа файлы по маске (PVS-Studio Options, вкладка Don't Check Files).
  • Переделан интерфейс страницы настроек Exclude From Analysis.
  • Убрана опция MoreThan2Gb из страницы настроек Viva64 (опция устарела и более не имеет смысла).
  • При проверке кода из командной строки надо указывать тип анализатора (Viva64 или VivaMP).
  • Снижен приоритет процесса анализатора, что позволяет комфортно работать на машине во время проверки кода.

PVS-Studio 3.50 (26 марта 2010)

  • PVS-Studio поддерживает работу в Visual Studio 2010 RC. Хотя официальный выпуск Visual Studio еще не состоялся, мы уже добавили поддержку этой среды в анализатор. Сейчас PVS-Studio интегрируется в Visual Studio 2010 и может проверять проекты в этой среде. В Visual Studio 2010 изменена справочная система, поэтому пока справка от PVS-Studio не интегрируется в документацию, как это делается в Visual Studio 2005/2008. Но вы по-прежнему можете пользоваться online-справкой. Поддержка Visual Studio 2010 RC реализована не полностью.
  • Доступна PDF-версия справочной системы. Теперь в дистрибутиве с PVS-Studio идет 50-страничный PDF-документ. Это полная копия нашей справочной системы (которая интегрируется в MSDN в Visual Studio 2005/2008 или доступна online).
  • В PVS-Studio появился механизм автоматического определения новых версий инструмента на нашем сайте. Определение новых версий контролируется через опцию CheckForNewVersions вкладки настроек "Common Analyzer Settings". В случае если опция CheckForNewVersions имеет значения True, то при запуске проверки кода (команды Check Current File, Check Current Project, Check Solution меню PVS-Studio) выполняется загрузка специального текстового файла с сайта www.viva64.com. В этом файле прописан номер самой последней версии PVS-Studio, доступной на сайте. Если версия на сайте окажется новее, чем версия, установленная у пользователя, то пользователь увидит запрос на обновление. В случае разрешения этого обновления запустится специальное отдельное приложение PVS-Studio-Updater, которое автоматически загрузит новый дистрибутив PVS-Studio с сайта и запустит его установку. В случае если опция CheckForNewVersions установлена в False, то проверка новой версии производиться не будет.
  • Реализована поддержка стандарта C++0x на уровне, на котором она осуществлена в Visual Studio 2010. Реализована поддержка лямбда-выражений, auto, decltype, static_assert, nullptr и так далее. В дальнейшем, с развитием поддержки C++0x в Visual C++, анализатор PVS-Studio также будет поддерживать новые возможности языка Си++.
  • Стало возможно запускать проверку проектов с помощью PVS-Studio не из Visual Studio, а с помощью командой строки. Обратите внимание, что речь идет все равно о проверке из Visual Studio с использованием файлов проектов (.vcproj) и решений (.sln), но при этом запуск анализа будет осуществляться не из IDE, а из командной строки. Такой вариант запуска удобен для регулярной проверки кода с помощью систем сборки (build system) или систем непрерывной интеграции (continuous integration system).
  • Добавлено новое правило V1212: Data race risk. When accessing the array 'foo' in a parallel loop, different indexes are used for writing and reading.
  • В этой версии инструмента мы ввели сертификат подписи кода. Это позволит пользователям быть уверенным в подлинности дистрибутива и получать меньше сообщений от операционной системы при установке приложения.

PVS-Studio 3.45 (1 февраля 2010)

  • Существенно улучшена работа анализатора с шаблонами.
  • Улучшена работа на многоядерных системах.

PVS-Studio 3.44 (21 января 2010)

  • Частичная поддержка проверки кода для процессоров Itanium. Теперь код, который собирается в Visual Studio Team System для процессоров Itanium также можно проверять с помощью анализатора. Анализ выполняется на системах x86 и x64, запуск на Itanium пока не реализован.
  • Сокращение количества ложных срабатываний анализатора при анализе доступа к массивам. Теперь анализатор в ряде случаев "понимает" диапазоны значений в цикле for и не выводит лишних сообщений про доступе к массивам с помощью таких индексов. Например: for (int i = 0; i < 8; i++) arr[i] = foo(); // нет сообщения анализатора.
  • Сокращенено количество ложных сообщений анализатора - введен список типов данных, которые не образуют большие массивы. Например, HWND, CButton. Пользователь может составлять свои списки типов.
  • Исправлена ошибка в работе инсталятора при установке программы в папку, отличную от папки по-умолчанию.

PVS-Studio 3.43 (28 декабря 2009)

  • Удалена опция ShowAllErrorsInString (теперь она всегда имеет значение true).
  • Новое правило V120: Member operator[] of object 'foo' declared with 32-bit type argument, but called with memsize type argument.
  • Новое правило V302: Member operator[] of 'foo' class has a 32-bit type argument. Use memsize-type here.
  • Улучшен анализ operator[].
  • Исправлена ошибка с долгим удалением программы при многократной установке "поверх".
  • Исправлена ошибка анализа файлов с символом "^" в имени.

PVS-Studio 3.42 (9 декабря 2009)

  • Улучшена диагностика ошибок с магическими числами. Теперь в сообщении о проблеме выдается больше информации, что позволяет более гибко использовать фильтры.
  • Исправлена ошибка при работе с прекомпилированными заголовочными файлами специального типа.
  • Опция DoTemplateInstantiate (выполнять инстанцирования шаблонов) теперь по умолчанию включена.
  • Исправлена ошибка с зависанием препроцессора при большом количестве сообщений препроцессора.
  • Улучшен анализ operator[].

PVS-Studio 3.41 (30 ноября 2009)

  • Исправлена ошибка анализа файлов с одинаковыми именами при работе на многоядерной машине.
  • Исправлена ошибка некорректной диагностики некоторых типов cast-выражений.
  • Существенно улучшен разбор перегруженных функций в анализаторе.
  • Добавлена диагностика некорректного использования time_t типа.
  • Добавлена обработка специальных параметров в настройках файлов проекта Visual C++.

PVS-Studio 3.40 (23 ноября 2009)

  • Добавлена возможность "Mark as False Alarm". Благодаря этому возможно пометить в исходном коде те строки, в которых происходит ложное срабатывание анализатора кода. После разметки, анализатор более не будет выдавать диагностических сообщений на такой код. Это позволяет более удобно постоянно использовать анализатор в процессе разработки программного обеспечения для проверки нового кода.
  • Добавлена поддержка Project Property Sheets - механизма удобной настройки проектов Visual Studio.
  • При проверке параллельных программ анализатор может выполнять два прохода по коду, что позволяет собрать больше информации и выполнить более точную диагностику некоторых ошибок.

PVS-Studio 3.30 (25 сентября 2009)

  • В PVS-Studio добавлена возможность проверки 32-битных проектов для оценки сложности и стоимости миграции кода на 64-битные системы.
  • Добавлено новое правило для анализа 64-битного кода V118: malloc() function accepts a dangerous expression in the capacity of an argument.
  • Добавлено новое правило для анализа 64-битного кода V119: More than one sizeof() operators are used in one expression.
  • Добавлено новое правило для анализа параллельного кода V1211: The use of 'flush' directive has no sense for private '%1%' variable, and can reduce performance.
  • Улучшена совместная работа с Intel C++ Compiler (исправлено падение при попытке проверки кода с установленным Intel C++ Compiler).
  • Улучшена поддержка локализованных версий Visual Studio.

PVS-Studio 3.20 (7 сентября 2009)

  • Исправлена ошибка с некорректным выводом некоторых сообщений в локализованных версиях Visual Studio.
  • Улучшена загрузка log-файла.
  • Улучшена обработка критических ошибок - теперь о возможных проблемах с инструментов сообщить нам стало просто.
  • Улучшена работа инсталлятора.
  • Исправлена ошибка перебора файлов проекта.

PVS-Studio 3.10 (10 августа 2009)

  • Добавлена поддержка инстанцирования шаблонов. Теперь поиск потенциальных ошибок выполняется не просто по телу шаблонов (как ранее), но и еще выполняется подстановка аргументов шаблона для более тщательной диагностики.
  • Теперь анализатор кода может работать в режиме имитации Linux-окружения. Мы добавили поддержку различных моделей данных. Поэтому теперь на Windows-системе можно проверять кросс-платформенные программы также, как это делалось бы на Linux-системе.
  • Исправлена ошибка, связанная с некорректной работой анализатора параллельных ошибок в 32-битном окружении.
  • Существенно улучшена работа анализатора с шаблонами.

PVS-Studio 3.00 (27 июля 2009)

  • Программные продукты Viva64 и VivaMP объединены в один программный комплекс PVS-Studio.
  • Новая версия представляет собой существенно модернизированный программный продукт.
  • Существенно повышена стабильность работы модуля интеграции в Visual Studio.
  • Повышена скорость работы на многопроцессорных системах: анализ выполняется в несколько потоков. Причем количество рабочих потоков анализатора можно настраивать с помощью опции "Thread Count". По умолчанию количество потоков соответствует количеству ядер в процессоре, однако количество потоков можно уменьшить.
  • Добавлена возможность работы анализатора из командной строки. В настройки программы добавлена новая опция "Remove Intermediate Files" ("Удалять промежуточные файлы"), которая позволяет не удалять командные файлы, создаваемые во время работы анализатора кода. Эти командные файлы можно запускать отдельно, без Visual Studio для выполнения анализа. Кроме того, создавая новые командные файлы по аналогии можно выполнять анализ всего проекта без использования Visual Studio.
  • Управлять диагностикой отдельных ошибок стало проще, удобнее и быстрее. Теперь можно включать и выключать показ отдельных ошибок в результатах анализа. Самое главное, что изменение списка сообщений происходит автоматически, без необходимости перезапуска анализа. Выполнив анализ, вы можете просмотреть список ошибок и просто выключить показ тех из них, которые для вашего проекта не актуальны.
  • Значительно улучшена работа с фильтрами ошибок. Фильтры для сокрытия отдельных сообщений теперь задаются просто как список строк. Причем применение фильтров также как и управление диагностикой отдельных ошибок не требует перезапуска анализа.
  • Изменение лицензионной политики. Хотя PVS-Studio является единым продуктом, лицензирование возможно и для отдельных модулей анализа, таких как Viva64 и VivaMP, и для всех модулей сразу. Кроме того, появились лицензии для одного пользователя и для команды разработчиков. Все эти изменения нашли отражение в регистрационных ключах.
  • Поддержка локализованных версий Visual Studio существенно улучшена.
  • Интегрирующаяся в MSDN справочная система для новой версии PVS-Studio была значительно переработана и усовершенствована. Описание новых разделов позволяет лучше изучить работу с программным продуктом.
  • Улучшено графическое оформление программного продукта. Новые иконки и графика в инсталляторе придали анализатору более красивый вид.

VivaMP 1.10 (20 апреля 2009)

  • Улучшен анализ кода, содержащего вызовы статических функций класса.
  • Реализованы новые диагностические правила для анализа ошибок связанных с исключениями: V1301, V1302, V1303.
  • Исправлена ошибка с некорректным отображением индикатора прогресса анализа на компьютерах с нестандартным DPI.
  • Реализованы некоторые другие усовершенствования.

VivaMP 1.00 (10 марта 2009)

  • Выпущена финальная версия VivaMP.

VivaMP 1.00 beta (27 ноября 2008)

  • Первая публичная бета-версия VivaMP выложена в Интернете.

Viva64 2.30 (20 апреля 2009)

  • Реализовано новое диагностическое правило V401.
  • Улучшена обработка констант, что в ряде случаев сокращает количество ложных диагностических предупреждений.
  • Исправлена ошибка с некорректным отображением индикатора прогресса анализа на компьютерах с нестандартным DPI.
  • Исправлен ряд недочетов.

Viva64 2.22 (10 марта 2009)

  • Улучшена совместная работа Viva64 и VivaMP.
  • Увеличена скорость работы анализатора кода на 10%.

Viva64 2.21 (27 ноября 2008)

  • Добавлена поддержка совместной работы Viva64 и VivaMP.

Viva64 2.20 (15 октября 2008)

  • Улучшена диагностика потенциально опасных конструкций. В результате примерно на 20% сократилось количество "ложных срабатываний" анализатора кода. Теперь разработчик потратит меньше времени на анализ кода, диагностируемого как потенциально опасный.
  • Изменения в справочной системе. Справка было расширена и дополнена новыми примерами. Так как в данной версии улучшена диагностика потенциально опасных конструкций, то в справочную систему также были добавлены пояснения, касающиеся того, какие конструкция теперь не считаются опасными.
  • Увеличена скорость анализа структуры проекта. Теперь же такая работа выполняется примерно в 10 раз быстрее. В конечном итоге это сокращает общее время анализа всего проекта.
  • Улучшен анализ шаблонов в языке Си++. Не секрет, что далеко не все анализаторы кода полностью понимают шаблоны (template). Мы постоянно работаем над улучшением диагностики потенциально опасных конструкций в шаблонах. Очередное подобное улучшение сделано в этой версии.
  • Изменен формат ряда сообщений анализатора кода для возможности осуществления более тонкой настройки фильтров. Так, например, теперь анализатор не просто сообщает о некорректном типе индекса при доступе к массиву, но при этом и указывает имя самого массива. Если разработчик уверен, что подобный массив никак не может быть источником проблем для 64-битного режима работы, то он может отфильтровать все сообщения, относящиеся к данному массиву по его имени.

Viva64 2.10 (05 сентября 2008)

  • Добавлена поддержка Visual C++ 2008 Service Pack 1.

Viva64 2.0 (09 июля 2008)

  • Добавлена поддержка Visual C++ 2008 Feature Pack (и TR1).
  • Добавлен режим Pedantic, позволяющий в случае необходимости находить конструкции, представляющие потенциальную опасность, но редко приводящие к ошибкам.
  • Улучшена диагностика шаблонных функций.

Viva64 1.80 (03 февраля 2008)

  • Visual Studio 2008 полностью поддерживается.
  • Увеличена скорость анализа кода.
  • Улучшен установщик. Теперь возможно установить Viva64 без прав администратора.

Viva64 1.70 (20 декабря 2007)

  • Добавлена поддержка нового диагностического сообщения (V117). Memsize type used in union.
  • Исправлена серьезная проблема связанная с диагностикой нескольких ошибок в одной строке кода.
  • Исправлена ошибка вычисления типов в сложных синтаксических конструкциях.
  • Улучшен интерфейс пользователя. Индикатор прогресса отслеживает анализ всего решения (solution).
  • Visual Studio 2008 частично поддерживается(BETA).

Viva64 1.60 (28 августа 2007)

  • Добавлена поддержка нового диагностического сообщения (V112). Dangerous magic number used.
  • Добавлена поддержка нового диагностического сообщения (V115). Memsize type used for throw.
  • Добавлена поддержка нового диагностического сообщения (V116). Memsize type used for catch.
  • Изменены ограничения ознакомительной версии. Для каждого анализируемого файла показываются только несколько диагностических сообщений.

Viva64 1.50 (15 мая 2007)

  • Добавлена поддежка анализа кода на языке Си.

Viva64 (1.40 - 1 мая 2007)

  • Добавлена возможность подавления некоторых сообщений. Вы можете задавать свои фильтры на странице настроек "Message Suppression". Например, вы можете захотеть пропускать сообщения с определенным кодом ошибки или сообщения, содержащие определенные имена функций. (См. Settings: Message Suppression).
  • Появилась возможность сохранять/загружать результаты анализа..
  • Улучшено представление результатов работы анализатора. Результаты теперь выдаются в стандартном окне Visual Studio под названием Error List, подобно сообщениям компилятора.

Viva64 1.30 (17 марта 2007)

  • Улучшено представление процесса анализа кода. Убраны лишние переключения окон, создан общий индикатор прогресса.
  • Добавлена панель инструментов Viva64.
  • Пользователь может указать анализатору, что проверяемая программа может использовать более 2Гб оперативной памяти. Для программ, использующих меньше 2Гб памяти некоторые диагностические сообщения будут отключены.
  • Добавлена поддержка нового диагностического сообщения (V113). Implicit type conversion from memsize to double type or vice versa.
  • Добавлена поддержка нового диагностического сообщения (V114). Dangerous explicit type pointer conversion.
  • Добавлена поддержка нового диагностического сообщения (V203). Explicit type conversion from memsize to double type or vice versa.

Viva64 1.20 (26 января 2007)

  • Добавлена фильтрация повторяющихся сообщений. Речь идет об ошибках в h-файлах. Ранее если *.h файл с ошибкой включался в различные *.cpp файлы, то диагностические сообщения выдавались несколько раз. Теперь же будет только одно диагностическое сообщение.
  • Теперь Viva64 сообщает о количестве ошибок, обнаруженных после анализа кода. Вы всегда можете определить:
    - сколько кода осталось проверить;
    - как много ошибок уже исправлено;
    - какие модули содержать наибольшее количество ошибок.
  • Добавлена поддержка горячих клавиш. Теперь можно прервать работу анализатора с помощью Ctrl+Break. А для проверки текущего файла достаточно нажать Ctrl+Shift+F7.
  • Исправлены некоторые ошибки в работе анализатора.

Viva64 1.10 (16 января 2007)

  • С помощью анализатора Viva64 мы подготовили 64-битную версию Viva64! Но пользователю не надо беспокоиться о выборе подходящей версии. Правильная версия выбирается автоматически во время установки.
  • Добавлена поддержка нового диагностического сообщения (V111).
  • Удалена ненужная диагностика на доступ к массивам с помощью enum-значений.
  • Удалена ненужная диагностика ошибок на конструкции вида int a = sizeof(int) .
  • Улучшения справочной системы.

Viva64 1.00 (31 декабря 2006)

  • Первая публичная версия Viva64 выложена в Интернете.

Ограничения анализатора кода

Анализатор кода не полностью поддерживает диагностику ошибок при использовании ряда конструкций C/C++/C++11. Это может приводить к ложным сообщениям или к отсутствию сообщений в некоторых случаях.

Анализатор кода не полностью поддерживает некоторые расширения языка, реализованные в Microsoft Visual C++. Не полностью поддерживается ряд аспектов современного стандарта языка Си++.

Также анализатор не работает с файлами в формате Unicode и с файлами, в путях которых есть Unicode-символы.

Основные ограничения:

  • Неполная поддержка сложных шаблонов (например, с частичной специализацией);
  • Неполная поддержка перегруженных функций;
  • Анализ управляемого кода не реализован;
  • Пространство имен msclr не поддерживается;

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

Общие сведения о принципах работы с анализатором PVS-Studio

Аннотация

Статья представляет собой учебное пособие (tutorial) по работе с анализатором кода PVS-Studio. Данный раздел содержит примеры выполнения наиболее общих задач при работе с анализатором PVS-Studio.

Системные требования и установка PVS-Studio

Анализатор PVS-Studio предназначен для работы на Windows-платформе. При этом он интегрируется в среды разработки Microsoft Visual Studio 2005/2008/2010. Системные требования к анализатору можно посмотреть в соответствующем разделе документации.

Получив установочный пакет PVS-Studio можно приступить к установке программы.

Рисунок 1 - Установка PVS-Studio

Рисунок 1 - Установка PVS-Studio

Во время установки будет выполнена интеграция анализатора кода в среду разработки Microsoft Visual Studio. В случае если на машине установлено несколько версий Microsoft Visual Studio, анализатор будет интегрирован во все версии автоматически.

Для того чтобы удостовериться, что инструмент PVS-Studio корректно установлен можно запустить Microsoft Visual Studio и открыть окно About Microsoft Visual Studio (пункт меню Help/About Microsoft Visual Studio). При этом анализатор PVS-Studio должен присутствовать в списке установленных компонентов (рисунок 2).

Рисунок 2 - окно About Microsoft Visual Studio с установленным компонентом PVS-Studio

Рисунок 2 - окно About Microsoft Visual Studio с установленным компонентом PVS-Studio

Перед началом работы также рекомендуется распаковать в удобную для вас директорию коллекцию примеров 64-битных и параллельных ошибок OmniSample из меню Start\PVS-Studio\. (64-bit and Parallel issues example, файл OmniSample.zip)

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

Знакомство с PVS-Studio

Откроем проект OmniSample (vs2010) в Microsoft Visual Studio 2010 (либо в другой имеющейся версии Microsoft Visual Studio).

Использовать для знакомства с инструментом PVS-Studio проект OmniSample следует по следующим причинам:

  • в нём собрано большинство дефектов кода, которые находятся наборами диагностик Viva64 и VivaMP в PVS-Studio;
  • возможность на реальном примере увидеть поведение приложения с ошибкой;
  • ознакомительная версия PVS-Studio показывает расположение в коде не всех дефектов с ошибками, а только некоторых (хотя обнаруживает все). Но для OmniSample отображаются все дефекты.

Рассмотрим в качестве примера работу PVS-Studio c 64-битными ошибками. Открыв проект OmniSample, выберем 64-битную конфигурацию x64 для проверки на наличие дефектов 64-битного кода и запустим проверку всего решения командой "Check Solution", как показано на рисунке 4.

Рисунок 3 - Проверка проекта OmniSample на наличие 64-битных проблем

Рисунок 3 - Проверка проекта OmniSample на наличие 64-битных проблем

Конфигурация x64 выбрана специально. На проблемы 64-битного кода рекомендуется проверять 64-битные конфигурации. Дело в том, что в 64-битной конфигурации настройки проекта отличаются от 32-битной. И делать проверку 64-битного кода в 32-битной конфигурации не желательно.

После запуска проверки на экране появится индикатор прогресса с кнопками Pause (приостановить анализ) и Stop (прервать анализ). Обнаруженные потенциально опасные конструкции во время анализа будут выводиться в окно найденных дефектов (рисунок 5).

Рисунок 4 - Анализ проекта - обнаруженные в коде проблемы сразу же выводятся в окно

Рисунок 4 - Анализ проекта - обнаруженные в коде проблемы сразу же выводятся в окно

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

После завершения анализа кода можно приступить к просмотру сообщений.

Исправление ошибок

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

error V101: Implicit assignment type conversion to memsize type.
x64sample.cpp 24

Вот ее код:

  size_t bufferSize = imageWidth * imageHeght *
                      bytePerPixel * maxFrameCountInBuffer;

Здесь проблема в том, что результирующий размер буфера (переменная bufferSize) имеет правильный размер size_t, а вот переменные, участвующие в выражении (imageWidth, imageHeight, bytePerPixel и maxFrameCountInBuffer), представлены типом int. В результате перемножение этих переменных дает значение также типа int. Это не страшно для значений меньше двух гигабайт, поскольку выполнится приведение типа. Но если в результате получится число больше двух гигабайт, то оно "обрежется" до двух гигабайт несмотря на то что переменная bufferSize имеет правильный тип. Для исправления ситуации необходимо изменить типы переменных, участвующих в выражении. Сделать это можно чуть выше по коду. Вместо:

  unsigned imageWidth = 1000;
  unsigned imageHeght = 1000;
  unsigned bytePerPixel = 3;
  unsigned maxFrameCountInBuffer;

напишем так:

  size_t imageWidth = 1000;
  size_t imageHeght = 1000;
  size_t bytePerPixel = 3;
  size_t maxFrameCountInBuffer;

Узнать об этом исправлении можно из справочной системы. Нажав на поле с кодом ошибки в столбце 'Code', мы увидим открывшееся окно c описанием данной ошибки (рисунок 6):

Рисунок 5 - Подробное описание ошибки, а также способы исправления

Рисунок 5 - Подробное описание ошибки, а также способы исправления

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

Работа со списком диагностических сообщений

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

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

Рисунок 6 - Отключение некоторых диагностических сообщений по коду

Рисунок 6 - Отключение некоторых диагностических сообщений по коду

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

Теперь рассмотрим другой вариант фильтрации на основе текста диагностических сообщений. Вернемся к примеру OmniSample. Одной из ошибок в этом примере является доступ к массиву array с использованием индекса типа int:

error V108: Incorrect index type: array[not a memsize-type]. 
Use memsize type instead. x64sample.cpp 390

Вот код:

volatile int index = 0;
  for (size_t i = 0; i != n; ++i) {
    array[index++] = 1; // проблема здесь
    if (array[i] != 1)
      throw CString("x64 portability issues");
  }

Конечно же, можно просто исправить тип переменной index с unsigned на size_t и проблема исчезнет. Но если подобного кода с доступом к переменной array много, а вы точно знаете, что в array не больше двух миллиардов элементов, то можно "попросить" анализатор кода не выдавать сообщения, в тексте которых используется слово array. Делается это с помощью настроек на странице MessageSuppression:

Рисунок 7 - Отключение некоторых диагностических сообщений по тексту

Рисунок 7 - Отключение некоторых диагностических сообщений по тексту

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

Последним механизмом сокращения количества диагностических сообщений является фильтрация по маскам имён файлов проекта и путям к ним.

Предположим, в вашем проекте используется библиотека Boost. Анализатор будет, конечно же, сообщать и о потенциальных проблемах в этой библиотеке. Однако если вы уверены, что эти сообщения не стоит учитывать, то можно просто добавить путь к папке с Boost на странице Don't check files (рисунок 8):

Рисунок 8 - Настройка фильтрации сообщений по расположению и именам файлов

Рисунок 8 - Настройка фильтрации сообщений по расположению и именам файлов

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

Также в PVS-Studio имеется функция "Mark as False Alarm". Благодаря ей возможно пометить в исходном коде те строки, в которых происходит ложное срабатывание анализатора кода. После разметки, анализатор более не будет выдавать диагностических сообщений на такой код. Это позволяет более удобно постоянно использовать анализатор в процессе разработки программного обеспечения для проверки нового кода.

Так в этом примере отключен вывод диагностического сообщения с кодом V104:

size_t n = 100;
  for (unsigned i = 0;
       i < n; //-V104
       i++)
  {
      // ...
  }

Подробно и обстоятельно эта возможность описана в разделе "Подавление ложных предупреждений".

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

Надо ли добиваться исправления всех потенциальных ошибок, на которые указывает анализатор?

Когда вы просмотрите все сообщения, которые выдал анализатор кода, то вы найдете как реальные ошибки в программах, так и конструкции не являющиеся ошибочными. Дело в том, что анализатор не может на 100% точно определить все ошибки в программах без так называемых "ложных срабатываний". Только программист, зная и понимая программу, может определить есть в конкретном месте ошибка или нет. Анализатор кода же только существенно сокращает количество мест, которые необходимо просмотреть разработчику.

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

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

Режимы работы анализатора кода

Диагностические модули анализатора

Анализатор состоит из нескольких модулей, выполняющих диагностику. В настоящее время это:

  • модуль диагностики общего назначения;
  • модуль диагностики возможных оптимизаций;
  • модуль диагностики проблем в 64-битном коде(Viva64);
  • модуль диагностики проблем в параллельном OpenMP коде (VivaMP);

Для включения/выключения отображения диагностических сообщений, относящихся к одному конкретному модулю, можно воспользоваться специальными кнопками-переключателями (GA, OP, 64, MP), как показано ниже (рисунок 1):

Рисунок 1 — Диагностические модули анализатора PVS-Studio

Рисунок 1 — Диагностические модули анализатора PVS-Studio

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

Группировка сообщений анализатора по уровням важности

Все сообщения, выдаваемые анализатором PVS-Studio, распределяются по 4 группам: Fails и 3-м уровням важности сообщений — Level 1, Level 2 и Level 3, как показано ниже (Рисунок 2):

Рисунок 2 – Разделение сообщений анализатора по группам

Рисунок 2 – Разделение сообщений анализатора по группам

В группу Fails попадают сообщения об ошибках анализатора (например, сообщения с кодами V001, V003 и т.п.), а также любой не обработанный вывод вспомогательных программ, используемых самим анализатором во время анализа (препроцессор, командный процессор cmd), выдаваемый ими в stdout/stderr. Например, в группу Fails может попасть сообщение препроцессора об ошибках компиляции исходного кода, об ошибках доступа к файлам (файл не существует или заблокирован антивирусом) и т.п.

Все диагностические сообщения о найденных потенциальных проблемах в коде распределяются по группам уровней важности. Значимость любого диагностического сообщения статического анализатора может быть оценена исходя из 2-х параметров: критичность потенциальной ошибки и частота возникновения её ложно-позитивных срабатываний. В соответствии с данными критериями каждое диагностическое сообщение, производимое PVS-Studio, определяется на одном из 3-х уровней. Так 1 уровень содержит высоко критичные диагностики, с наибольшей вероятностью являющиеся реальными ошибками, а 3 уровень — диагностики с низкой критичностью, либо диагностики с очень высокой вероятностью ложно-позитивного срабатывания. Стоит помнить, что конкретный код ошибки не обязательно привязывает её к определённому уровню важности, а распределение сообщений по группам сильно зависит от контекста, в котором они были сгенерированы.

При первом запуске уровни 2 и 3 по умолчанию отключены, для их включения необходимо использовать соответствующие кнопки-переключатели, как было показано на рисунке 2.

Подавление ложных предупреждений

Аннотация

В статье приведены описание и пример использования функции, появившейся в PVS-Studio 3.40, "Mark as False Alarm" ("Пометить как ложное срабатывание"). Эта функция позволяет разметить те диагностические сообщения анализатора PVS-Studio, которые являются "ложными срабатываниями", чтобы не видеть эти сообщения при следующем запуске анализатора.

Механизм подавления отдельных ложных срабатываний

Любой анализатор кода всегда выдает помимо полезных сообщений об ошибках еще множество так называемых "ложных срабатываний". Это ситуации, когда программисту совершенно очевидно, что в коде нет ошибки, а анализатору это не очевидно. Такие ложные срабатывания называют False Alarm. Рассмотрим пример кода:

ptrdiff_t value;
fread(&value, 4, 1, f);
char RGBA[4];

Здесь будет выдано два предупреждения V112, так как используется магическая константа 4. В первом случае это ошибка, так как размер переменной типа ptrdiff_t не будет равен четырем байтам в 64-битной системе. Во втором случае число 4 обозначает количество компонент цвета и является безопасным. В PVS-Studio, начиная с версии 3.40, появилась возможность пометить сообщение об ошибке, полученное от PVS-Studio, как ложное срабатывание. Это можно делать либо вручную, либо с помощью команды контекстного меню. Появление в PVS-Studio возможности "Mark as False Alarm" значительно расширяет потенциал внедрения анализатора кода в процесс разработки программного обеспечения на этапе ежедневного постоянного использования, позволяя не только выполнять миграцию приложений на 64-битную платформу, но и быть уверенным в отсутствии опасных дефектов в новом только что разработанном коде.

Чтобы подавить ложное срабатывание, в код можно добавить специальный комментарий:

char RGBA[4]; //-V112

Теперь анализатор не будет выдавать предупреждение V112 на эту строку.

Комментарий, подавляющий предупреждения можно вписать в код самостоятельно. Также можно воспользоваться специальной командной для этого, представляемой PVS-Studio. Пользователю предоставляется две команды, доступные из контекстного меню PVS-Studio (смотри рисунок 1).

Рисунок 1 - Команды для работы с механизмом подавления ложных предупреждений

Рисунок 1 - Команды для работы с механизмом подавления ложных предупреждений

Рассмотрим доступные команды, относящиеся к подавлению ложных предупреждений:

1. Mark selected errors as False Alarm. Вы можете выбрать одно или несколько предупреждений в списке (смотри рисунок 2) и воспользоваться этой командой для пометки соответствующего кода, как безопасного.

Рисунок 2 - Выбор предупреждений перед выполнением команды Mark selected errors as False Alarms

Рисунок 2 - Выбор предупреждений перед выполнением команды Mark selected errors as False Alarms

2. Remove False Alarm marks from selected errors. Убирает комментарий, помечающий код как безопасный. Функция, например, может быть полезна, если вы поспешили и ошибочно отметили код как безопасный. Как и в предыдущем случае, вы должны выбрать сообщения из списка, которые планируете обработать.

Групповое подавление ложных предупреждений с помощью фильтров

Возможны ситуации, в которых определённый тип диагностик не актуален для анализируемого проекта (Например, если ошибки, связанные с явным приведением типа — коды V201, V202, V203 — вас не интересуют, и т.п.), или какая-либо из диагностик анализатора выдаёт предупреждения на код, в корректности которого вы уверены. В таком случае можно воспользоваться системой группового подавления сообщений, основанной на фильтрации полученных результатов анализа. Список доступных режимов фильтрации можно открыть кнопкой "Suppressions", либо через общее меню PVS-Studio -> Options. (смотри рисунок 3)

Рисунок 3 – Режимы групповой фильтрации сообщений

Рисунок 3 – Режимы групповой фильтрации сообщений

Режимы фильтрации включают в себя группы Detectable Errors, Don't Check Files и Message Suppression.

С помощью команды контекстного меню "Hide all Vxxx errors" (смотри рисунок 1) возможно отключить отображение всех ошибок, относящихся к одному коду. Для того чтобы вновь показать отключенные таким образом ошибки необходимо выбрать на странице Suppressions режим Detectable Errors и отметить соответствующий код ошибки как True.

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

Демонстрация функции Mark as False Alarm на примере проекта OmniSample

Покажем, как пользоваться функцией Mark as False Alarm на демонстрационном примере OmniSample, который идет в дистрибутиве вместе с PVS-Studio.

Сначала необходимо распаковать OmniSample в удобную для вас директорию из меню Start\PVS-Studio\. (64-bit and Parallel issues example, файл OmniSample.zip) или через пункт меню среды Visual Studio 'PVS-Studio\Open Omnisample.zip examples' и открыть его в среде Microsoft Visual Studio.

Открыв проект, запустим анализ решения с помощью команды "Check Solution" (рисунок 4).

Рисунок 4 - Запуск анализа

Рисунок 4 - Запуск анализа

По завершении анализа будет выведен список сообщений об обнаруженных проблемах (рисунок 5).

Рисунок 5 - Список проблем, обнаруженных с помощью PVS-Studio

Рисунок 5 - Список проблем, обнаруженных с помощью PVS-Studio

Этот список сообщений нужно просмотреть и проанализировать.

Перейдем на самое первое сообщение об ошибке:

error V101: Implicit assignment type conversion to memsize type.
x64sample.cpp 24

Этому сообщению (и этой строке) соответствует код:

size_t bufferSize = imageWidth * imageHeght *
                    bytePerPixel * maxFrameCountInBuffer;

Проблема в переменных, которые объявлены выше и имеют тип unsigned:

  unsigned imageWidth = 1000;
  unsigned imageHeght = 1000;
  unsigned bytePerPixel = 3;
  unsigned maxFrameCountInBuffer;

Это ошибка, и правильно было бы использовать тип size_t вместо unsigned. Однако если в вашем случае эта ошибка не является важной, то вы можете "отключить" выдачу конкретно этого типа сообщения (V101) в конкретно этой строке (24) этого файла (x64sample.cpp). Для этого нужно выделить в окне PVS-Studio сообщение об ошибке и выбрать команду "Mark selected errors as False Alarms" в контекстном меню PVS-Studio (рисунок 6).

Рисунок 6 - Команда "Mark Selected Errors as False Alarm"

Рисунок 6 - Команда "Mark Selected Errors as False Alarm"

После этого в код автоматически добавится комментарий " //-V101":

size_t bufferSize = imageWidth * imageHeght * //-V101
                    bytePerPixel * maxFrameCountInBuffer;

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

Комментарий можно добавить и вручную без использования команды "Mark selected errors as False Alarms", но важно полностью соблюдать формат записи: две косые черты, минус (без пробела), код ошибки.

После пометки сообщения как ложное срабатывание можно воспользоваться кнопкой-переключателем "False Alarms" (рисунок 7) для того, чтобы обновить список сообщений об ошибках и скрыть ненужное сообщение. В окне PVS-Studio станет на одно сообщение меньше.

Рисунок 7 – Кнопка FA, предназначенная для управления отображением размеченных сообщений

Рисунок 7 – Кнопка FA, предназначенная для управления отображением размеченных сообщений

Удалить комментарий можно с помощью команды "Remove False Alarm marks from selected Errors", предварительно выделив сообщение об ошибке в окне PVS-Studio. Также комментарий можно удалить и вручную.

Рисунок 8 - Команда "Remove False Alarm Marks from selected errors"

Рисунок 8 - Команда "Remove False Alarm Marks from selected errors"

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

Если необходимо в окне PVS-Studio получить все-таки все сообщения, включая и помеченные как False Alarm, то можно опять включить их отображение с помощью кнопки-переключателя "FA" (рисунок 9).

Рисунок 9 — Включение отображения размеченных сообщений

Рисунок 9 — Включение отображения размеченных сообщений

Можно пометить сразу несколько сообщений. Для этого нужно выбрать их в окне PVS-Studio (рисунок 10).

Рисунок 10 — Выбор нескольких сообщений для разметки в окне PVS-Studio

Рисунок 10 — Выбор нескольких сообщений для разметки в окне PVS-Studio

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

Реализация функции подавления ложных предупреждений

Обычно в компиляторах для подавления отдельных сообщений об ошибках используют #pragma-директивы. Приведем пример кода:

unsigned arraySize = n * sizeof(float);

Компилятор выдает сообщение:

warning C4267: 'initializing' : conversion from 'size_t' to 'unsigned
int', possible loss of data x64Sample.cpp 151

Это сообщение можно подавить с помощью следующей конструкции:

#pragma warning (disable:4267)

Точнее, чтобы подавить конкретно это сообщение, лучше оформить код так:

#pragma warning(push)
#pragma warning (disable:4267) 
  unsigned arraySize = n * sizeof(float);
#pragma warning(pop)

Анализатор PVS-Studio в качестве разметки использует комментарии специального вида. Для той же строчки кода подавление сообщения PVS-Studio будет выглядеть так:

unsigned arraySize = n * sizeof(INT_PTR); //-V103

Такой подход был выбран для повышения наглядности конечного кода. Дело в том, что PVS-Studio может сообщать о проблемах в середине многострочных выражений, как, например, здесь:

  size_t n = 100;
  for (unsigned i = 0;
       i < n; // анализатор сообщит о проблеме здесь
       i++)
  {
      // ...
  }

Для того чтобы подавить это сообщение при использовании комментария, достаточно написать:

  size_t n = 100;
  for (unsigned i = 0;
       i < n; //-V104
       i++)
  {
      // ...
  }

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

Хранение разметки в исходном коде позволяет вносить в него модификации без опасения потерять информацию о строках с ошибками.

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

Подавление ложных предупреждений в макросах (#define)

В макросах (#define) анализатор также, разумеется, может находить потенциальные проблемы и выдавать на них диагностические сообщения. Но при этом анализатор будет выдавать сообщения в тех местах, где макрос используется, то есть где фактически происходит подстановка тела макроса в код. Пример:

#define TEST_MACRO \
  int a = 0;       \
  size_t b = 0;    \
  b = a; 

void func1()
{
  TEST_MACRO // V101 here
}

void func2()
{
  TEST_MACRO // V101 here
}

Для того чтобы подавить это сообщения можно, использовать команду "Mark as False Alarm". Тогда код с расставленными командами подавления будет выглядеть так:

#define TEST_MACRO \
  int a = 0;       \
  size_t b = 0;    \
  b = a; 

void func1()
{
  TEST_MACRO //-V101
}

void func2()
{
  TEST_MACRO //-V101
}

Однако если макрос используется очень активно, то везде размечать его как False Alarm не очень удобно. Есть возможность в коде сделать вручную специальную пометку, чтобы анализатор автоматически размечал диагностики в этом макросе как False Alarm. С этой пометкой код будет выглядеть так:

//-V:TEST_MACRO:101

#define TEST_MACRO \
  int a = 0;       \
  size_t b = 0;    \
  b = a; 

void func1()
{
  TEST_MACRO
}

void func2()
{
  TEST_MACRO
}

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

//-V:TEST_MACRO:101, 105, 201

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

#define NO_ERROR 0
#define VB_NODATA ((long)(77))
size_t stat;

#define CHECK_ERROR_STAT                        \
    if( stat != NO_ERROR &&  stat != VB_NODATA ) \
      return stat;

size_t testFunc()
{
    {
      CHECK_ERROR_STAT // #1
    }

    {
      CHECK_ERROR_STAT // #2
    }

    return VB_NODATA; // #3
}

В указанном примере диагностика V126 появляется в трех местах. Для того, чтобы автоматически помечать ее как False Alarm в местах #1 и #2 нужно добавить такой код:

//-V:CHECK_ERROR_STAT:126

А для того, чтобы и в #3 это сработало, необходимо указать еще:

//-V:VB_NODATA:126

К сожалению, просто указать "сразу помечать V126 в макросе VB_NODATA" и не указывать про макрос CHECK_ERROR_STAT нельзя из-за технических особенностей механизма препроцессирования.

Массовое подавление ложных предупреждений

Предположим, есть следующая структура:

struct EXRGBA
{
unsigned data;
};

И ряд функций, которые ее используют:

void f1(const struct EXRGBA aaa)
{
}

long int f2(int b, const struct EXRGBA aaa)
{
  return int();
}

long int f3(float b, const struct EXRGBA aaa,  char c)
{
  return int();
}

На все эти функции анализатор три сообщения V801: Decreased performance. It is better to redefine the N function argument as a reference. Сообщение в подобном коде будет ложным, так как компилятор сам оптимизирует код, и проблемы не будет.

Можно конечно каждое сообщение пометить как False Alarm с помощью функции Mark As False Alarm. Однако есть способ лучше. Достаточно добавить в код строку:

//-V:EXRGBA:801 

Мы рекомендуем добавлять такую строку в .h-файл рядом с объявлением структуры, но если это невозможно (например, структура в системном .h-файле), то можно прописать это в stdafx.h.

И тогда, после перепроверки, все три сообщения V801 будут автоматически помечены как False Alarm.

Другие способы фильтрации сообщений в анализаторе PVS-Studio

В анализаторе также существуют ещё три способа фильтрации сообщений об ошибках.

Во-первых, можно отключить диагностику тех или иных ошибок по их коду. Это делается с помощью вкладки "Настройки: Detectable Errors". На вкладке обнаруживаемых ошибок можно указать номера ошибок, какие не надо показывать в отчете по анализу. Иногда бывает целесообразно убрать в отчете ошибки с определенными кодами. Например, если вы уверены, что ошибки, связанные с явным приведением типа (коды V201, V202, V203) вас не интересуют, то вы можете скрыть их показ.

Во-вторых, можно отключить анализ некоторых частей проекта (некоторых папок или файлов проекта). Раздел "Настройки: Don't Check Files". На этой можно ввести информацию о библиотеках, включения (через директиву #include) из файлов которых анализировать не надо. Это может потребоваться для уменьшения количества лишних диагностических сообщений. Например, в проекте используется библиотека Boost. И хотя на какой-то код из этой библиотеки анализатор выдает диагностические сообщения, вы считаете, что эта библиотека является достаточно надежной и написана хорошо. Поэтому, возможно, не имеет смысла получать диагностические сообщения по поводу кода в этой библиотеке. В этом случае можно отключить анализ файлов из этой библиотеки, указав путь к ней на странице настроек. Кроме того возможно ввести файловые маски для исключения некоторых файлов из анализа. Анализатор не будет проверять файлы, удовлетворяющие условиям маски. Например, подобным образом можно исключить из анализа автогенерируемые файлы.

Маски путей для файлов, сообщения из которых попали в текущий сгенерированный отчёт можно автоматически добавить в список Don't Check Files с помощью команды контекстного меню “Don’t check files and hide all messages from...” для выделенного в окне PVS-Studio Output сообщения (Рисунок 11).

Рисунок 11 — Добавление масок путей через контекстное меню

Рисунок 11 — Добавление масок путей через контекстное меню

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

В-третьих, можно подавлять отдельные сообщения по тексту. На вкладке "Настройки: Message Suppression" можно настроить фильтрацию ошибок по содержащемуся в них тексту, а не по коду. При необходимости можно скрыть из отчета сообщения о диагностированных ошибках, содержащих определенные слова или фразы. Например, если в отчете есть ошибки, в которых указаны названия функций printf и scanf, а вы считаете, что ошибок, связанных с ними, быть не может, то просто добавьте эти два слова с помощью редактора подавляемых сообщений.

Возможные проблемы

При использовании возможности "Mark as False Alarm" существуют несколько потенциальных проблем. Иногда анализатор кода "промахивается" с номером строки содержащей ошибку. Например, анализатор говорит, что ошибка в строке 57, а строка 57 - вообще пустая строка. При этом исходный код, который вызывает ошибку, находится строчкой выше, например, в строке 56.

Дело в том, что анализатор кода использует препроцессор из Visual C++, имеющий проблемы при работе с многострочными макросами (#define). В Visual Studio 2005 Service Pack 1 и более поздних версиях эти проблемы были устранены.

Другая проблема препроцессора связана с многострочными #pragma-директивами определенного типа, из-за которых также сбивается нумерация строк. К сожалению, эта ошибка пока не исправлена ни в каких версиях Visual Studio.

Таким образом, автоматически расставленные разметки могут быть поставлены не в том месте, где должны быть. И тогда анализатор вновь выдаст эти же сообщения об ошибках, так как маркер не будет найден. Решением проблемы является пометка сообщений, на которых заметен сбой, вручную. PVS-Studio всегда сообщает о подобных ошибках сообщением "V002. Some diagnostic messages may contain incorrect line number".

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

Принцип постоянного использования PVS-Studio в процессе разработки программного проекта

Появление в PVS-Studio возможности "Mark as False Alarm" значительно расширяет потенциал внедрения анализатора кода в процесс разработки программного обеспечения. Теперь анализатор удобно запускать не время от времени, а, например, ежедневно.

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

Мы предлагаем такой вариант использования нашего инструмента.

Сначала команда из пяти разработчиков создает базовую компилирующуюся конфигурацию 64-битной версии проекта, исправляет основные ошибки и предупреждения компилятора. После чего, разделив проект между собой по частям, запускает анализатор PVS-Studio. В течение некоторого времени (дней/недель/месяцев) в зависимости от объема исходного кода команда из пяти разработчиков либо исправляет обнаруженные проблемы (сообщения от анализатора), либо помечает их как False Alarm в коде. В результате после завершения работы команды над миграцией кода PVS-Studio не будет выдавать ни одного нового сообщения, так как все ошибки будут либо исправлены, либо помечены как ложные срабатывания.

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

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

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

Работа со списком диагностических сообщений

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

Навигация и сортировка

Окно вывода результатов PVS-Studio в первую очередь предназначено для упрощения навигации по коду анализируемого проекта и переходу к участкам кода, содержащим потенциальные ошибки. Двойной щелчок мыши по любому из сообщений в списке автоматически откроет в редакторе кода файл, на который данное сообщение указывает, и переведёт курсор на интересующую строку. Кнопки быстрой навигации (рисунок 1) позволяют легко просматривать обнаруженные потенциально опасные места в исходном коде без необходимости постоянно переключаться между окнами среды разработки.

Рисунок 1 — Кнопки быстрого перехода

Рисунок 1 — Кнопки быстрого перехода

Для представления результатов анализа окно PVS-Studio использует виртуальную таблицу, позволяющую быстро отображать и осуществлять сортировку сгенерированных сообщений даже для очень крупных проектов (виртуальная таблица позволяет работать со списками из сотен тысяч строк без заметного ущерба для производительности). Крайний левый столбец таблицы предназначен для пометки интересных сообщений, например тех, к которым имеет смысл вернуться повторно. Данный столбец также поддерживает сортировку, поэтому найти все сообщения, размеченные подобным образом, не составит труда. Пункт контекстного меню "Show Columns" позволяет настроить отображаемые в таблице столбцы (рисунок 2):

Рисунок 2 — Настройка отображения таблицы вывода результатов

Рисунок 2 — Настройка отображения таблицы вывода результатов

Таблица поддерживает множественное выделение с помощью стандартных комбинаций Ctrl и Shift, при этом выделение строк сохраняется и после пересортировки по любой другой колонке. Пункт меню "Copy selected messages to clipboard" (либо сочетание Ctrl+C) позволяет скопировать в буфер обмена содержимое всех выделенных в таблице строк.

Фильтрация сообщений

Механизмы фильтрации окна вывода PVS-Studio позволяют быстро найти и отобразить как отдельные диагностические сообщения, так целые их группы. Панель инструментов окна содержит ряд переключателей, позволяющих включить либо отключить отображение сообщений из соответствующих им групп сообщений (рисунок 3).

Рисунок 3 — группы фильтрации сообщений

Рисунок 3 — группы фильтрации сообщений

Все переключатели можно разбить на 3 группы: фильтры по уровню диагностической важности сообщений (соответственно, уровни 3, 2, 1 и fail, в порядке возрастания их значимости), фильтры по принадлежности сообщения к определённому типу диагностических правил (64-битные диагностики, OpenMP диагностики и диагностические сообщения общего назначения), и фильтр размеченных в коде ложных срабатываний. Отключение этих фильтров мгновенно отключает отображение соответствующих им сообщений в списке.

Механизм быстрой фильтрации (quick filters) позволяет отфильтровать отчёт анализатора по заданным ключевым словам. Открыть панель быстрой фильтрации можно с помощью кнопки Quick Filters панели инструментов окна (рисунок 4).

Рисунок 4 — панель быстрой фильтрации

Рисунок 4 — панель быстрой фильтрации

Быстрая фильтрация позволяет отобразить сообщения в соответствии с фильтрами по 3-м ключевым словам: по коду сообщения, по тексту сообщения и по файлу, содержащему данное сообщение. Например, отобразить все сообщения, содержащие слово ‘odd’ из файла command.cpp. Изменения в списке сообщений становятся видны сразу после выхода из поля ввода ключевого слова (при потере фокуса). Кнопка Reset Filters очищает заданные в данный момент ключевые слова.

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

Поиск отдельных сообщений, быстрый переход

Окно вывода результатов PVS-Studio имеет встроенные средства для быстрого поиска в таблице ключевых слов и переходов к отдельным сообщениям. Окно поиска открывается кнопкой "Find in PVS-Studio Output" (рисунок 5)

Рисунок 5 — Открытие окна поиска

Рисунок 5 — Открытие окна поиска

Рисунок 6 — Окно поиска PVS-Studio

Рисунок 6 — Окно поиска PVS-Studio

Нажатие "Find Next" выделяет и осуществляет автоматическую фокусировку на следующей (или предыдущей) строке относительно текущей выделенной, в которой было найдено заданное ключевое слово. Стоит заметить, что поиск по ключевым словам осуществляется только в указанном через выпадающий список столбце.

При необходимости перехода на какое-либо конкретное сообщение в таблице можно воспользоваться диалогом быстрого перехода к строке, который вызывается через пункт контекстного меню " Navigate to ID..."(рисунок 7):

Рисунок 7 – Вызов диалога быстрого перехода

Рисунок 7 – Вызов диалога быстрого перехода

Рисунок 8 – Диалог быстрого перехода к сообщению

Рисунок 8 – Диалог быстрого перехода к сообщению

Каждое сообщение в списке вывода PVS-Studio имеет уникальный идентификатор — порядковый номер добавления этого сообщения в таблицу, который отображён в колонке ID. Диалог быстрого перехода позволяет выделить и автоматически сфокусировать сообщение с заданным идентификатором ID, независимо от текущей сортировки таблицы и выделенных строк. Обратите внимание, что ID идентификаторы отображённых в таблице сообщений не всегда идут последовательно, т.к. часть сообщений может быть скрыта с помощью механизмов фильтрации, переход к таким сообщениям не возможен.

Организация работы с помощью Visual Studio Task List

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

PVS-Studio позволяет автоматически сгенерировать и внести в код комментарий TODO специального вида, содержащий всю необходимую информацию для оценки и анализа отмеченного им фрагмента программы. Такой комментарий будет сразу отображён в окне задач Visual Studio (окно Task List, для версии Visual Studio 2010 необходимо включить разбор комментариев в настройках Tools->Options->Text Editor->C++->Formatting->Enumerate Comment Tasks->true) при условии, что в настройках Tools->Options->Environment->Task List->Tokens задана соответствующая TODO лексема (присутствует в настройках по умолчанию). Комментарий может быть добавлен с помощью команды контекстного меню 'Add TODO comment for Task List' (рисунок 9)

Рисунок 9 – вставка TODO комментария

Рисунок 9 – вставка TODO комментария

TODO комментарий будет вставлен в строку, сгенерировавшую сообщение анализатора, и будет содержать код ошибки, текст сообщения анализатора и ссылку на online документацию для данного типа ошибок. Благодаря окну заданий (Task List) данный комментарий может быть легко найден любым имеющим доступ к исходному коду разработчиком, а сам текст комментария позволит выявить и исправить потенциальную ошибку даже в случае отсутствия у программиста установленной версии PVS-Studio или полного отчёта о работе анализатора (рисунок 10).

Рисунок 10 — окно заданий Visual Studio

Рисунок 10 — окно заданий Visual Studio

Открыть окно Task List можно через меню View->Other Windows->Task List. Комментарии TODO отображаются в разделе Comments окна.

Коллекция примеров 64-битных и параллельных ошибок OmniSample

Аннотация

Статья содержит обзор программы OmniSample – коллекции примеров 64-битных и параллельных ошибок, диагностируемых статическим анализатором кода PVS-Studio. OmniSample распространяется вместе с дистрибутивом PVS-Studio.

Введение

Инструмент PVS-Studio — это набор статических анализаторов исходного C++ кода. В его состав входят анализаторы Viva64, предназначенный для выявления ошибок миграции на 64-битные платформы, VivaMP, диагностирующий ошибки в параллельных программах, написанных с использованием технологии OpenMP и анализатор общего назначения. Несмотря на то, что различная справочная документация для программистов содержит описание этих ошибок, оно зачастую бывает ограничено лишь общими фразами. Между тем статей, содержащих конкретные примеры ошибок в коде приложений достаточно мало. Проект OmniSample призван восполнить данный пробел, а также показать все возможности диагностики инструмента PVS-Studio

Получение OmniSample

Проект OmniSample распространяется вместе с анализатором PVS-Studio. Для получения проекта вам достаточно скачать дистрибутив с нашего сайта и установить PVS-Studio. После установки в разделе главного меню PVS Studio появится ссылка на zip-архив (64-bit and Parallel issues example), содержащий OmniSample.

Структура проекта

OmniSample представляет собой набор С++ проектов для сред разработки Visual Studio 2005, Visual Studio 2008 и Visual Studio 2010. Проект OmniSample включает в себя примеры всех ошибок, диагностируемых анализаторами Viva64 и VivaMP. Каждый такой пример представлен отдельной функцией, которые в свою очередь сгруппированы по файлам x64Sample.cpp (для 64-битных ошибок) и ParallelSample.cpp (для параллельных ошибок). Каждому примеру диагностируемой анализаторами ошибки соответствуют 2 функции: содержащая ошибку и корректная. Заметим, что корректная версия функции здесь содержит исправленную версию кода некорректной функции.

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

Использование программы

Для изучения представленных в программе примеров ошибок удобно использовать анализатор PVS-Studio. Результаты анализа одного из проектов OmniSample, открытого в среде Visual Studio соответствующей версии, позволят вам быстро найти интересующую вас ошибку и перейти на содержащую её строку кода.

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

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

Рисунок 1 — Интерфейс программы OmniSample

Рисунок 1 — Интерфейс программы OmniSample

Для этого отметьте в расположенном слева древовидном списке интересующие вас номера сообщений PVS-Studio и нажмите кнопку "Execute selected samples". Заметим, что все функции сгруппированы по типам ошибок, т.е. 64-битные и параллельные ошибки. Для выполнения всех примеров ошибок, относящихся к одному типу, достаточно отметить соответствующую корневую ветвь списка. Результат работы выбранных функций выводится в поле "Output", рассоложенное справа от списка ошибок (рисунок 2).

Рисунок 2 — Результат выполнения функций V1004, V1201 и V1207

Рисунок 2 — Результат выполнения функций V1004, V1201 и V1207

По умолчанию программа исполнит код корректных (исправленных) версий выбранных функций. Для выполнения неисправленных примеров стоит снять отметку "Use sample's correct versions", расположенную под списком. Следует помнить, что использование таких функций может привести к аварийному завершению работы программы. Примеры, исполнение некорректных версий которых приводит к падению OmniSample, отмечены символом "*".

В поле "Current build configuration" отображены текущие параметры сборки проекта OmniSample. Для изучения примеров параллельных ошибок необходимо собирать проект с использование стандарта OpenMP (параметр компилятора /openmp). Для изучения примеров 64-битных ошибок необходимо собрать проект в 64-битной конфигурации (x64).

Знакомство с кодом проекта

Приведём в качестве примера работы с проектом OmniSample код функций, соответствующих сообщению анализатора V102. Рассмотрим для начала функцию std::string V102(), содержащую в себе 64-битную ошибку V102:

std::string V102()
{
  std::ostringstream str;

  int domainWidth;
  int domainHeght;
  int domainDepth;
  if (IsX64Platform()) {
    domainWidth = 1000;
    domainHeght = 1000;
    domainDepth = 3000;
  } else {
    domainWidth = 100;
    domainHeght = 100;
    domainDepth = 300;
  }
  
  char *buffer =
    new char [size_t(domainWidth) * size_t(domainHeght) * size_t(domainDepth)];
  
  char *current = buffer;
  char *end = buffer;
  end += domainWidth * domainHeght * domainDepth;

  while (current != end)
    *current++ = 1;

  delete [] buffer;

  return str.str();
}

После проверки проекта анализатором Viva64 в приведённой ниже строке будет обнаружена ошибка V102 Usage of non memsize type for pointer arithmetic:

  end += domainWidth * domainHeght * domainDepth;

Проблема в данном коде заключается в арифметике с указателями, точнее в использовании для этой арифметики не memsize-типов. Ошибка в том, что на 64-битной платформе указатель end никогда не получит приращение более 4 гигабайт. В исправленной версии данной функции std::string V102_correct() для переменных domainWidth, domainHeght и domainDepth используется memsize-тип ptrdiff_t:

std::string V102_correct()
{
  std::ostringstream str;

  ptrdiff_t domainWidth;
  ptrdiff_t domainHeght;
  ptrdiff_t domainDepth;
  if (IsX64Platform()) {
    domainWidth = 1000;
    domainHeght = 1000;
    domainDepth = 3000;
  } else {
    domainWidth = 100;
    domainHeght = 100;
    domainDepth = 300;
  }
  
  char *buffer =
    new char [size_t(domainWidth) * size_t(domainHeght) * size_t(domainDepth)];
  
  char *current = buffer;
  char *end = buffer;
  end += domainWidth * domainHeght * domainDepth;

  while (current != end)
    *current++ = 1;

  delete [] buffer;

  return str.str();
} 

Можно посмотреть весь код как 64-битных, так и параллельных ошибок, включая исправленные варианты кода.

Проверка кода с помощью PVS-Studio из командной строки (если есть solution-файл Visual Studio)

Аннотация

В статье демонстрируется, как запускать проверку проектов с помощью PVS-Studio не из Visual Studio, а с помощью командой строки. Это новая возможность версии PVS-Studio 3.50. Обратите внимание, что речь идет все равно о проверке из Visual Studio с использованием файлов проектов (.vcproj) и решений (.sln), но при этом запуск анализа будет осуществляться не из IDE, а из командной строки. Такой вариант запуска удобен для регулярной проверки кода с помощью систем сборки (build system) или систем непрерывной интеграции (continuous integration system).

Введение

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

В настоящей статье мы не будем подробно останавливаться на описании инструмента PVS-Studio и его предназначения. Для этого отсылаем читателя к обзорной статье "Знакомство с анализатором кода PVS-Studio" и более основательной "Учебное пособие по PVS-Studio". Фокус этой статьи направлен на использование инструмента из командной строки, но с использованием Visual Studio. Такой вариант использования применяется в командах разработчиков, где внедрены либо системы сборки (не важно на чем основанные вроде Make, NMake, NAnt, MSBuild), либо системы непрерывной интеграции (вроде Cruise Control, Draco.NET или Team Foundation Build из Visual Studio Team System).

Простой запуск из командной строки с появлением окна Visual Studio

Для демонстрации запуска PVS-Studio рекомендуем установить проект OmniSample из дистрибутива PVS-Studio и тестировать запуск на нем. Будем считать, что проект OmniSample установлен в папку C:\Users\evg\Documents\ OmniSample. Пусть нам надо проверить этот проект в Visual Studio 2008. Тогда достаточно в командной строке написать (аргументы, содержащие указание на директории не должны заканчиваться символом \, т.к. сочетание \" не всегда корректно обрабатывается операционной системой):

"C:\Program Files (x86)\PVS-Studio\x64\PVS-Studio.exe" --sln-file "C:\Users\evg\Documents\ OmniSample\OmniSample (vs2008).sln" --plog-file "C:\Users\evg\Documents\result.plog" --vcinstalldir "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC" --platform "x64" --configuration "Release"

При этом запустится нужная версия devenv.exe, откроется решение " OmniSample (vs2008).sln", выберется архитектура x64, конфигурация Release и начнется анализ решения в PVS-Studio. По завершении работы PVS-Studio отчет будет сохранен в файле C:\Users\evg\Documents\result-log.plog и Visual Studio закроется. В дальнейшем сохраненный отчет можно открыть в Visual Studio с помощью команды "Load Analysis Report" меню PVS-Studio или двойным щелчком напрямую из файлового менеджера и уже просмотреть его в удобном окружении с навигацией по коду, фильтрами и всем функционалом PVS-Studio. То есть работать с файлом отчета можно и нужно именно из PVS-Studio, так как он создается в специальном формате.

Если проект корректный и все указано правильно, то через некоторое время анализ завершится, окно закроется, и вы сможете открыть файл result-log.plog. Если же что-то будет не так, то будет выдано окно с сообщением о проблеме. К сожалению, Visual Studio не предоставляет возможностей для выдачи сообщений в консоль, поэтому иногда из-за выданных во время ночной сборки сообщений анализ может "зависнуть", ожидая реакции пользователя на сообщение. Сделать с этим вряд ли что-то можно, разве что только порекомендовать сначала "прогнать" анализ под присмотром человека.

Если длинная командная строка кажется по каким-то причинам не очень удобной, то можно все эти же параметры задать через специальный файл настроек PVS-Studio и указать только этот файл в качестве параметров:

PVS-Studio.exe --cfg "PVS-Studio.cfg"

Содержимое конфигурационного файла PVS-Studio.cfg такое:

sln-file = C:\Users\evg\Documents\OmniSample\OmniSample (vs2008).sln

plog-file = C:\Users\evg\Documents\OmniSample\result.plog

vcinstalldir = C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC

platform = x64

configuration = Release

Пакетный режим работы при запуске анализатора из командной строки

При запуске анализа из командной строки PVS-Studio по умолчанию проверяет все файлы, включённые в проекты указанного solution-файла Visual Studio. Однако при наличии необходимости проверить только определённый набор файлов может быть использован пакетный режим работы, в котором анализу подвергнуться только те файлы, пути к которым прописаны явно. Для запуска анализа в пакетном режиме необходимо указать путь к списку проверяемых файлов в качестве параметра командной строки (этот параметр не является обязательным).

Для проверки файлов в пакетном режиме из командной строки нужно запустить анализ с использованием аргументов следующего вида:

"C:\Program Files (x86)\PVS-Studio\x64\PVS-Studio.exe" --sln-file "C:\Users\evg\Documents\ OmniSample\OmniSample (vs2008).sln" --plog-file "C:\Users\evg\Documents\result.plog" --vcinstalldir "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC" --platform "x64" --configuration "Release" –filelist-file "C:\Users\evg\Documents\ OmniSample\ExtFilelist.txt"

Необходимым условием является включённость всех C++ файлов, перечисленных внутри filelist-file в проекты заданного .sln-файла. Список должен содержать полные пути до C++ файлов, разделённые newline (\r\n) символами. Допустимыми являются файлы с расширениями c, cc, cpp и cxx, то есть те файлы, которые являются компилируемыми.

Вот пример текстового файла ExtFilelist.txt (каждый файл на одной строке):

c:/Users/evg/Documents/OmniSample/OmniSample/../OmniSample/OmniSample.cpp

"c:\\Users\\evg\\Documents\\OmniSample\\OmniSample\\..\\OmniSample\\ParallelSample.cpp"

Влияние настроек PVS-Studio на запуск из командной строки

При запуске анализа кода из командной строки используются все те же настройки, что и при запуске из Visual Studio. Ведь фактически запуск и происходит из Visual Studio. При этом используется столько ядер процессора, сколько указано в настройках.

Что касается, к примеру, системы фильтров (Message Suppression и Detectable Error), то она НЕ применяется при анализе из командной строки. В том смысле, что в файле отчета независимо от заданных параметров будут все сообщения об ошибках. Но при загрузке файла с результатами в Visual Studio фильтры уже будут применены. Такое поведение происходит из-за того, что фильтры применяются динамически к результатам (и при запуске из Visual Studio также). Это очень удобно, поскольку получив список сообщений, вы можете захотеть отключить некоторые из них (например, V201). Достаточно отключить их в настройках и соответствующие сообщения пропадут из списка БЕЗ перезапуска анализа.

Регулярное использование PVS-Studio и интеграция в процесс "ежедневных сборок"

При регулярном использовании PVS-Studio очень важно пользоваться функцией "Mark as False Alarm". Эта функция позволяет разметить те диагностические сообщения анализатора PVS-Studio, которые являются "ложными срабатываниями", чтобы не видеть эти сообщения при следующем запуске анализатора. Например, на этом коде анализатор выдавал сообщение V101, но после добавления комментария специального вида перестал это делать:

size_t bufferSize = imageWidth * imageHeght * //-V101
                    bytePerPixel * maxFrameCountInBuffer;

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

Работа с анализатором кода PVS-Studio состоит из двух этапов: внедрение и постоянное использование. На этапе внедрения PVS-Studio в существующий крупный проект программисты просматривают сообщения анализатора и либо исправляют код, либо размечают код с помощью функции "Mark as False Alarm". Разобравшись со всеми сообщениям от PVS-Studio, разработчики запускают окончательную проверку всего кода и получают 0 сообщений от анализатора (если не включено отображение ошибок, размеченных как ложные срабатывания). Это означает, что стадия внедрения завершена и начинается стадия регулярного использования.

Теперь весь новый код, который будет добавляться в проект, будет проверяться с помощью PVS-Studio. На самом деле проверяться будет ВЕСЬ код, но ошибки будут обнаруживаться только новые. Ошибки будут найдены во вновь написанном/исправленном коде или в старом коде, который так и не был размечен..

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

Запустить проверку кода из командной строки.

Отправить получившийся файл отчета всем заинтересованным разработчикам.

При запуске PVS-Studio из командной строки помимо xml-файла (к примеру, result-log.plog), создается текстовый файл с именем, заканчивающимся на ".only_new_messages.txt" (к примеру, result-log.plog.only_new_messages.txt). Этот файл содержит новые (не помеченные как False Alarm) сообщения от PVS-Studio:

c:\users\evg\documents\omnisample\x64Sample.cpp(17): error V101: Implicit assignment type conversion to memsize type.

c:\users\evg\documents\omnisample\x64Sample.cpp(50): error V201: Explicit type conversion. Type casting to memsize.

c:\users\evg\documents\omnisample\x64Sample.cpp(54): error V102: Usage of non memsize type for pointer arithmetic.

Либо, если новых сообщений нет, то файл result-log.plog.only_new_messages.txt содержит фразу:

New diagnostic messages are not produced.

При этом xml-файл будет и в том, и в другом случае содержать равное количество сообщений, а txt-файл - только те сообщения, которые не были помечены как "False Alarm".

Имеющийся txt-файл с новыми сообщениями от PVS-Studio не будет большим при ежедневном запуске PVS-Studio и обработке сообщений от анализатора. Его можно рассылать заинтересованным разработчикам по e-mail. Рассылать же xml-файл по e-mail вряд ли разумно, так как он содержит полные пути до файлов. Однако если у разработчиков совпадают пути до файлов, то можно рассылать и xml-файлы.

Таким образом, ежедневный запуск PVS-Studio позволит избежать появления новых ошибок в вашем коде.

Запуск PVS-Studio из командной строки без использования Visual Studio

Рассмотренный в статье вариант запуска PVS-Studio из командной строки работает ТОЛЬКО если у вас есть корректно настроенные файлы проектов (.vcproj) и решений (.sln). Если же этих файлов у вас нет, но вы хотите проверить код, то ознакомьтесь со статьей "Использование PVS-Studio из командной строки".

Заключение

Ежедневный запуск PVS-Studio из командной строки способен существенно повысить качество кода. Особенно, если код при этом размечен с помощью функции "Mark As False Alarm". В таком случае при корректном коде ежедневно вы будете видеть 0 новых сообщений от анализатора. Если произойдет некорректная модификация старого (ранее просмотренного) кода, то очередной запуск PVS-Studio сразу выявит внесенные дефекты. Новый код естественно также будет проверяться регулярно в автоматическом (независимом от программиста) режиме. Это позволит создавать качественные 64-битные и параллельные приложения.

Использование PVS-Studio из командной строки (без solution-файла Visual Studio). Интеграция в системы автоматизации сборки.

Аннотация

Мы рекомендуем всем использовать PVS-Studio из среды разработки Microsoft Visual Studio, куда инструмент прекрасно интегрируется. Однако бывают ситуации, когда необходим запуск из командной строки. В случае если у вас есть файлы проектов (.vcproj) и решения (.sln), и запуск из командной строки нужен, к примеру, для ежедневных проверок, то рекомендуем ознакомиться со статьей "Проверка кода с помощью PVS-Studio из командной строки (если есть solution-файл Visual Studio)".

Независимый режим работы анализатора PVS-Studio

Итак, как работает анализатор кода (PVS-Studio или любой другой)?

Когда пользователь анализатора дает команду проверить какой-либо файл (например, file.cpp), то анализатор сначала выполняет препроцессирование этого файла. В результате раскрываются все макросы, подставляются #include-файлы. Препроцессированный i-файл уже может разбирать анализатор кода. Обратите внимание, что анализатор не может разбирать файл, который не был препроцессирован. Поскольку тогда он не будет иметь информации об используемых типах, функциях, классах. Работа любого анализатора кода состоит как минимум из двух этапов: препроцессирование и собственно анализ.

Возможна ситуация, в которой программы не имеют связанных с ними проектных Visual C++ файлов, например в случае мультиплатформенного ПО или старых проектов, собираемых с помощью пакетных утилит командной строки. Зачастую в таких случаях для управления сборочным процессом используют различные Make системы, например Microsoft NMake, GnuMake и т.п.

Для проверки подобных проектов потребуется встроить в сборочный процесс прямой вызов анализатора (файл %programfiles%\PVS-Studio\x64\PVS-Studio.exe) и передать ему все необходимые для препроцессирования аргументы. Фактически анализатор необходимо вызывать для тех же файлов, для которых вызывается компилятор (cl.exe в случае Visual C++).

Анализатор PVS-Studio должен быть вызван в пакетном режиме для каждого C/C++ файла проекта либо для целой группы файлов (файлы с расширением c/cpp/cxx и т.п., анализатор не нужно вызывать для заголовочных h файлов) с аргументами следующего вида:

PVS-Studio.exe --cl-params  %ClArgs%
--source-file %cppFile% --cfg %cfgPath%  --output-file %ExtFilePath%

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

%cppFile% — путь до анализируемого C/C++ файла или пути до группы C/C++ файлов (имена файлов, разделённые пробелами)

Параметры %ClArgs% и %cppFile% должны передаваться анализатору PVS-Studio аналогично тому, как они передаются компилятору Visual C++

%cfgPath% — путь до конфигурационного файла PVS-Studio.cfg. Этот файл общий для всех C/C++ файлов, который может быть создан вручную (пример будет показан ниже).

%ExtFilePath% — необязательный параметр, путь до внешнего файла, в который будут сохранены результаты работы анализатора. Если данный параметр не указан, анализатор будет выдавать сообщения о найденных ошибках в stdout. Полученные здесь результаты анализа можно открыть в окне PVS-Studio среды Visual Studio с помощью команды PVS-Studio/Load Analysis Report (выбрав в качестве типа файла Unparsed output). Обратите внимание на то, что начиная с версии PVS-Studio 4.52, анализатор при запуске из командной строки поддерживает работу с одним выходным файлом (--output-file) из нескольких процессов PVS-Studio.exe. Это сделано для того, чтобы как и в обычных makefile при компиляции можно было запускать несколько процессов анализатора. При этом выходной файл не будет перезаписан и потерян, так как используется механизм блокировок файла.

Конфигурационный файл PVS-Studio.cfg  (параметр --cfg) должен содержать следующие строки:
exclude-path = C:\Program Files (x86)\Microsoft Visual Studio 10.0
vcinstalldir = C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\ 
platform = Win32
preprocessor = visualcpp
exclude-path = C:\Program Files (x86)\Microsoft Visual Studio 10.0
vcinstalldir = C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\ 
platform = Win32
preprocessor = visualcpp

Рассмотрим эти параметры:

  • Параметр exclude-path содержит папку, файлы из которой проверять не надо. Если не указать папку, в которой содержатся файлы Visual Studio, то анализатор выдаст сообщения на заголовочные .h-файлы, что довольно бессмысленно. Ведь поправить эти ошибки нельзя. Поэтому мы рекомендуем всегда добавлять этот путь в исключения. Параметров exclude-path может быть несколько.
  • Параметр vcinstalldir задает папку, в которой находится используемый препроцессор. Поддерживаются препроцессоры: Microsoft Visual C++ (cl.exe), Clang(clang.exe) и MinGW (gcc.exe).
  • Параметр platform показывает, какую версию компилятора использовать – Win32, x64, Itanium или ARMV4. Обычно это Win32 или x64.
  • Параметр preprocessor показывает, какой препроцессор должен находиться в vcinstalldir. Поддерживаемые значения: visualcpp, clang, gcc.

Вы можете фильтровать диагностические сообщения от анализатора с помощью опций analyzer-errors и analysis-mode (заданных или в cfg-файле, или в командной строке). Это необязательные параметры:

  • Параметр analyzer-errors позволяет перечислить только те коды ошибок, которые интересны. Пример: analyzer-errors=V112 V111. Рекомендуем не указывать этот параметр.
  • Параметр analysis-mode позволяет управлять используемыми анализаторами. Значения: 0 – полный анализ (по умолчанию), 1 – только 64-битный анализ, 2 – только OpenMP анализ, 4 – только анализ общего назначения, 8 – только анализ оптимизации". Рекомендуем указывать значение 4.

Полный список параметров командной строки можно получить с помощью запуска:

PVS-Studio.exe --help

Использование режима независимого запуска анализатора на примере Makefile проекта

Возьмём для примера Makefile проект, в котором сборка осуществляется компилятором Visual C++, и она описана в makefile проекта следующим правилом:

$(CC) $(CFLAGS) $<

Здесь $(CC) вызывает cl.exe, которому передаются параметры компиляции $(CFLAGS), и наконец с помощью макроса $< осуществляется подстановка всех С\С++ файлов, от которых зависит текущая цель сборки. Т.о. для всех файлов с исходным кодом будет вызван компилятор cl.exe с необходимыми параметрами.

Модифицируем данный сценарий таким образом, чтобы перед вызовом компилятора для каждого файла осуществлялась проверка статическим анализатором PVS-Studio:

$(PVS) --source-file $<  --cl-params $(CFLAGS)  $<
--cfg "C:\CPP\PVS-Studio.cfg"
$(CC) $(CFLAGS) $<

Здесь $(PVS) – путь до исполняемого файла анализатора (%programfiles%\PVS-Studio\x64\PVS-Studio.exe). Обратите внимание, что после вызова анализатора на следующей строке вызывается компилятор Visual C++ с теми же параметрами, что и изначально. Это делается для того, чтобы были построены правильные цели (targets) и сборка не остановился из-за отсутствия .obj-файлов.

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

Инструмент PVS-Studio разрабатывался для работы в рамках среды Visual Studio. И его работа из командной строки - это дополнение к основному режиму работы. Тем не менее, все его диагностические возможности доступны.

Также при работе из командной строки не доступны механизмы фильтрации сообщений об ошибках (смотри: диагностируемые ошибки и подавление отдельных сообщений).

Однако обнаруженные в данном режиме ошибки могут быть также перенаправлены в файл с помощью аргумента командной строки --output-file. Данный файл будет содержать необработанный и неотфильтрованный вывод анализатора.

Такой файл можно открыть в окне PVS-Studio среды Visual Studio командой Load Analysis Report (выбрав в качестве типа файла 'Unparsed output') и в дальнейшем его можно будет сохранить в стандартном формате отчета PVS-Studio log file (plog), что позволит избежать дублирования сообщений, а также использовать все стандартные механизмы фильтрации сообщений.

Инкрементальный анализ в версии для командной строки

Пользователи, знакомые с режимом инкрементального анализа в PVS-Studio при работе из Visual Studio IDE естественно не хотят от него отказываться и в версии для командной строки. К счастью, любая система сборки автоматически позволяет получить инкрементальный анализ "из коробки". Ведь говоря "make" мы компилируем только изменившиеся файлы. Соответственно и PVS-Studio.exe будет вызван только для изменившихся файлов. Так что инкрементальный анализ в версии для командной строки есть автоматически.

Использование Microsoft IntelliSense в независимом режиме работы анализатора

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

Решить рассматриваемую проблему можно, создав пустой проект Visual C++ (например, Makefile проект) в одной директории с проверяемыми анализатором исходными C++ файлами (vcproj/vcxproj файл при этом должен находится в папке, являющейся корневой для всех проверяемых исходных файлов). После этого можно включить режим Show All Files проекта (кнопка Show All Files в окне Solution Explorer), что позволит увидеть в древовидной структуре окна Solution Explorer все файлы, располагающиеся в нижележащих директориях файловой системы. Затем через контекстную команду Include in Project можно добавить в проект все необходимые cpp, с и h файлы (возможно, что для некоторых файлов также придётся прописать пути до include директорий, если в них, например, присутствуют включения из сторонних библиотек). Стоит помнить, что при включении в проект лишь части от всех проверяемых файлов, IntelliSense может не распознать часть из упоминаемых в них типов, если эти типы определяются как раз в отсутствующих файлах.

Рисунок 1 — добавление файлов в проект

Рисунок 1 — добавление файлов в проект

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

Разница в поведении консольной версии PVS-Studio.exe при обработке одного файла и при обработке нескольких файлов

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

cl.exe ... file1.cpp
cl.exe ... file2.cpp
cl.exe ... file2.cpp

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

cl.exe ... file1.cpp file2.cpp file3.cpp

И тот, и другой режим работы поддерживается в консольной версии PVS-Sudio.exe, примеры приведены выше.

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

Заключение

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

Использование PVS-Studio вместе с системами continuous integration

Аннотация

В статье показаны приемы организации работы анализатора кода PVS-Studio вместе с системами непрерывной интеграции (continuous integration). Приведены примеры настройки запуска PVS-Studio.

Введение

Непрерывная интеграция (continuous integration, CI) — это практика разработки программного обеспечения, предполагающая частую сборку и последующее тестирование текущей версии продукта. Системы непрерывной интеграции, как правило, взаимодействуют напрямую с системами контроля версий и позволяют значительно повысить надёжность интеграционного процесса и снизить его трудоёмкость за счёт полной автоматизации всех этапов сборки и тестирования. Статический анализатор кода PVS-Studio предназначен для поиска 64-битных и параллельных ошибок и может быть использован на этапе тестирования в процессе обеспечения и контроля качества разрабатываемого продукта.

Для использования в системах непрерывной интеграции PVS-Studio предусматривает возможность "тихого запуска" анализа тестируемого проекта из командной строки. Подобная интеграция PVS-Studio в процесс регулярных сборок позволит эффективно использовать результаты анализа при групповой работе над проектом.

Использование функции "Mark As False Alarm" в процессе автоматизации тестирования

При запуске анализа из командной строки PVS-Studio каждый раз будет осуществлять проверку всего кода тестируемого проекта. Такой подход к проверке обусловлен тем, что изменения в одном заголовочном h-файле часто могут затрагивать более чем один cpp файл, и поэтому проверки только модифицированных или новых файлов, а не всего решения, недостаточно.

Но стоит помнить, что методика статического анализа предполагает генерацию анализатором большого числа т.н. "ложных срабатываний" и для подавления этого "шума" очень важно при регулярной проверке кода с помощью PVS-Studio пользоваться функцией "Mark as False Alarm". Работу с анализатором PVS-Studio можно разделить на 2 этапа: внедрение и постоянное использование. На этапе внедрения анализатора в систему непрерывной интеграции программист должен просмотреть все полученные сообщения об ошибках и либо исправить соответствующий им код, либо разметить его как ложные срабатывания. После этого на этапе тестирования анализатор будет генерировать сообщения только для добавленного в проект нового кода.

Интеграция PVS-Studio с системами непрерывной сборки (continuous integration)

В связи с тем, что PVS-Studio является расширением-надстройкой для среды разработки Microsoft Visual Studio, возможна интеграция анализатора в любую систему CI, способную запускать сборки для Visual Studio из командной строки с передачей аргументов. При этом от CI системы не требуется прямая поддержка сборок в Visual Studio, достаточно лишь иметь возможность передавать команды интерпретатору командной строки ОС и обрабатывать стандартный поток вывода stdout.

Как вызывать анализатор для проверки из командной строки, подробно показано в отдельной статье.

Открытие лог-файла результата в среде Visual Studio (двойным щелчком мыши напрямую из файлового менеджера или через меню PVS-Studio/Load Analysys Report) позволяет осуществлять навигацию по фрагментам кода проанализированного проекта, содержащим ошибки. Поскольку сам XML лог-файл не удобен для чтения, анализатор генерирует в том же каталоге текстовый файл, содержащий простой список ошибок (не помеченных как False Alarm), обнаруженных PVS-Studio. Такой файл наглядно демонстрирует результаты анализа, и поэтому его удобно присоединять к общим отчётам CI системы о сборке, которые могут, к примеру, рассылаться по электронной почте всем заинтересованным разработчикам.

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

Пакетный режим работы при запуске анализатора из командной строки

При запуске анализа из командной строки PVS-Studio по умолчанию проверяет все файлы, включённые в проекты указанного solution-файла Visual Studio. Однако при наличии необходимости проверить только определённый набор файлов может быть использован пакетный режим работы, в котором анализу подвергнуться только те файлы, пути к которым прописаны явно. Об этом режиме запуска (с параметром filelist-file) также говорится в отдельной статье.

Интеграция с системой CruiseControl .NET

Интеграция анализатора PVS-Studio в систему CruiseControl.NET может быть осуществлена путём добавления в необходимый сборочный проект (build project) задачи (сборочного шага) типа Executable, в которой будет вызван интерпретатор командной строки Windows cmd.exe. Ниже приведён фрагмент конфигурационного файла сервера CruiseControl.NET, в котором описана подобная сборочная задача.

<exec>
 <description>PVS-Studio check solution example</description>
 <executable>&CMD_PATH;</executable>
 <baseDirectory>&PROJECT_ROOT;<baseDirectory>
 <buildArgs>
  <item>
  /c PVS-Studio.exe --sln-file VSSol.sln --plog-file result-log.plog 
--vcinstalldir "C:\Program Files (x86)\Microsoft Visual Studio
10.0\VC" --platform "Win32" --configuration "Release"
  </item>
 </buildArgs>
 </exec>

Результаты анализа, содержащиеся в файле result-log.plog.only_new_messages.txt, могут быть опубликованы любыми задачами публикации (publisher tasks), например копированием файла в общий сборочный каталог или рассылкой сборочного лога сервера по электронной почте. Для включения результатов анализа в общий лог можно воспользоваться сборочной задачей Executable:

<exec>
 <description>PVS-Studio load error log</description>
 <executable>&CMD_PATH;</executable>
 <baseDirectory>&PROJECT_ROOT;</baseDirectory>
 <buildArgs>
  <item>
  /c type result-log.plog.only_new_messages.txt
  </item>
 </buildArgs>
 <buildTimeoutSeconds>0</buildTimeoutSeconds>
</exec>

Интеграция с системой Hudson

Для интеграции анализатора PVS-Studio с системой Hudson необходимо в требуемый проект (Job) добавить шаг сборки "Выполнить команду Windows". Пример такой команды:

PVS-Studio.exe --sln-file VSSol.sln --plog-file result-log.plog 
--vcinstalldir "C:\Program Files (x86)\Microsoft Visual Studio
10.0\VC" --platform "Win32" --configuration "Release"

Полученный в результате анализа текстовый файл result-log.plog.only_new_messages.txt, содержащий полный список не размеченных как ложные срабатывания ошибок, можно присоединить к общему логу сборки проекта, дополнительно добавив команду

type result-log.plog.only_new_messages.txt

В дальнейшем полученный лог может быть опубликован с использованием любых встроенных в систему Hudson механизмов. Например, для публикации результатов сборки по электронной почте можно воспользоваться расширением ext-email, добавив в тело письма лексему (token) $BUILD_LOG:

${BUILD_LOG, maxLines=5000}

Интеграция с Microsoft Team Foundation Server 2005/2008/2010

Использование анализатора PVS-Studio совместно со сборочным сервером Team Foundation осложнено наличием тесной интеграции проектов Visual Studio с системой контроля версий от Microsoft, и поэтому требует дополнительных шагов по сравнению с системами непрерывной интеграции других компаний в случае, если в качестве системы контроля версий также используется Microsoft Team Foundation. Заметим, что сборочный сервер TFS использует утилиту MSBuild для сборок и не требует для своей работы среды Visual Studio. В связи с тем, что PVS-Studio является надстройкой – расширением для Visual Studio, обязательным требованием является наличие доступа сборочного сервера TFS к среде Visual Studio (в частности к devenv.exe) с установленным анализатором PVS-Studio.

Типичная сборка в TFS с использованием анализатора PVS-Studio будет включать следующий минимальный набор обязательных этапов:

  • Сервер загружает из системы контроля версий исходные тексты нужных проектов в заданную локальную директорию.
  • Исходные тексты программ проверяются анализатором PVS-Studio (для этого необходимо вызвать devenv.exe с командой /command PVSStudio.Checksolution), результаты анализа сохраняются в указанную локальную директорию.
  • Сервер производит сборку проектов с помощью MSBuild.
  • Результаты проверки и сборки публикуются в указанной для данной сборки директории.

Team Foundation 2010 Build Server

Для интеграции проверки анализатором PVS-Studio (шаг 2) в сборочный процесс сервера Team Foundation 2010 нужно добавить сборочное действие (Build Activity) типа InvokeProcess в сценарий сборки определения сборки (Build Definition), описанный в xaml файле (пункт меню Edit Build Defenition на нужном определении сборки, вкладка Process, поле Build Process Template), которому в качестве атрибута Filename необходимо передать путь до PVS-Studio.exe и в качестве атрибута Arguments строку:

--sln-file VSSol.sln --plog-file result-log.plog --vcinstalldir "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC" --platform "Win32" --configuration "Release"

Team Foundation 2005/2008 Build Server

Для интеграции проверки анализатором PVS-Studio (шаг 2) в сборочный процесс сервера Team Foundation 2005/2008 нужно для интересующего определения сборки (Build Definition) модифицировать соответствующий ему проект MSBuild (файл с расширением proj), добавив в него сборочную задачу типа Exec. (задача Exec должны быть наследником одного из узлов типа Target, объединяющего сборочные задачи MSBuild) В качестве атрибута command следует указать строку такого вида:

PVS-Studio.exe --sln-file VSSol.sln --plog-file result-log.plog --vcinstalldir "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC" --platform "Win32" --configuration "Release"

Запуск сборок Team Foundation System в интерактивном режиме рабочего стола

Сборочный сервер Team Foundation System по умолчанию запускает сборки, используя локальную системную учётную запись (NT AUTHORITY\SYSTEM) или учётную запись локальной службы (Local Service), в неинтерактивном режиме, т.е. работает в виде Windows – сервиса, не имея возможности взаимодействовать с рабочим столом пользователя. Для корректной работы среды Visual Studio рекомендуется использовать интерактивный режим сборок. Для запуска интерактивной сборки необходимо запустить сборочный сервис Team Foundation Build из-под учётной записи пользователя, имеющей доступ к рабочему столу (например, ваша текущая учётная запись). В системе Team Foundation 2008 этот сервис должен быть запущен вручную (C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\ TFSBuildService.exe). В системе Team Foundation 2010 параметры запуска сервиса можно изменить в утилите конфигураторе сервера.

При первом запуске devenv.exe из-под локальной системной учётной записи (NT AUTHORITY\SYSTEM) в неинтерактивном режиме, Visual Studio выдаёт диалоговое окно, позволяющее выбрать один из шаблонов настроек среды. Это диалоговое окно также блокирует работу анализатора PVS-Studio, поэтому его требуется один раз закрыть вручную, что невозможно сделать для приложения, запущенного в неинтерактивном режиме. Однако, начиная с Windows Vista, приложениям, запущенным из-под локальной системной учётной записи не разрешено запускаться в интерактивном режиме. Для того чтобы запустить devenv.exe от имени локальной системной учётной записи в интерактивном режиме можно воспользоваться программой PsExec из набора утилит PSTools. psexec.exe –i –s %devenvPath%.

Проверка проектов, использующих Team Foundation Server source control

Если в качестве системы контроля версий также используется Microsoft Team Foundation (TFS версий 2008/2010), подобный режим запуска делает невозможным открытие и последующий анализ solution файлов среды Visual Studio, содержащих метаинформацию системы контроля версий TFS, т.к. выдаваемые средой диалоговые окна блокируют работу расширения PVS-Studio. PVS-Studio не поддерживает проверку с запуском из командной строки проектов, содержащих метаинформацию системы контроля версий TFS 2008/2010.

Для запуска проверки проектов Visual C++ анализатором PVS-Studio из сборочного сервера Team Foundation System, использующего систему контроля версий Team System, необходимо перед началом проверки очистить sln и проектные (vcxproj) файлы от метаинформации системы контроля версий TFS. Это можно сделать, не прерывая сборочного процесса и в автоматическом режиме, с помощью консольной утилиты TFSRipper. Для этого необходимо добавить её вызов до и после вызова devenv.exe, также используя сборочное действие InvokeProcess. При этом первый раз ей нужно передать аргумент вида: %LocalSourcePath% - путь до выкачанных сборочным сервером исходных текстов программ, которые будут проверяться анализатором, а второй раз аргумент вида: %LocalSourcePath% \restore. Утилита обработает все sln и vcxproj файлы, удалив из них метаинформацию системы контроля версий и предварительно создав копии данных файлов (с расширением pvsbak), а после повторного её вызова с аргументом /restore восстановит эти файлы из созданных ранее копий.

Предопределенный макрос PVS_STUDIO

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

  int rawArray[5];
  rawArray[-1] = 0;

Однако если его "обернуть" с помощью этого макроса, то сообщения не будет:

  int rawArray[5];
#ifndef PVS_STUDIO
  rawArray[-1] = 0;
#endif

Макрос PVS_STUDIO автоматически подставляет при проверке кода из Visual Studio. Если же вы используете проверку кода из командной строки, то по-умолчанию макрос не передается анализатору и это нужно сделать вручную.

Вопросы и ответы по PVS-Studio (PVS-Studio FAQ)

При проверке группы проектов или отдельного проекта C или C++ выдаётся сообщение "Files with C or C++ source code for analysis not found."

Не проверяются проекты, выключенные в общей сборке через окно Configuration Manager среды Visual Studio.

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

Picture 1

Не включённые в сборку проекты будут пропущены. Если ни один из имеющихся проектов не включён в сборку, либо если выбрана проверка одного не включённого в сборку проекта, будет выдано сообщение "Files with C or C++ source code for analysis not found" и анализ не будет запущен. Посмотреть, какие проекты включены, а какие выключены в общей сборке можно с помощью окна Configuration Manager для текущего решения Visual Studio.

Я получаю множество сообщений "V001. A code fragment from 'file' cannot be analyzed", инструмент совсем не работает.

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

Почему иногда проверка в несколько потоков (настройка ThreadCount) работает медленнее, чем при одном потоке?

Анализатор PVS-Studio весьма требователен к объему операттивной памяти. Вероятно, анализатор сильно свопится и из-за этого все замедляется. Расчет примерно такой - на каждый экземпляр анализатора требуется 1.5 гигабайта оперативной памяти. Поэтому, к примеру, конфигурация 4 ядра и 2 гигабайта может работать гораздо медленнее, чем система с 2 ядрами и 4 гигабайтами памяти.

Количество используемых при анализе ядер можно задать в настройках PVS-Studio (опция ThreadCount).

Почему инструмент имеет только Windows версию? В чём сложность анализа текстовых файлов (с кодом), полученных от разных компиляторов?

Технически перенести код PVS-Studio.exe на другую платформу можно, так как никаких Windows-specific функции там почти не используются.

Для того чтобы сделать PVS-Studio для другой операционной системы нужно уметь обеспечить на той платформе уровень качества не ниже, чем на Windows-платформе. Для обеспечения уровня качества PVS-Studio мы используем много различных типов тестов: статический анализ, юнит-тесты, функциональные тесты, тесты UI и так далее. Убедиться в том, что на другой платформе анализатор ведет себя точно также как и на Windows, можно только пройдя все эти тесты. Тестирование анализатора составляет около 30% от общей цены разработки.

Кроме того следует учитывать, что файлы с кодом, обрабатываемые компилятором Visual C++, содержат определенные специфичные для данного компилятора конструкции. И PVS-Studio их поддерживает. В других компиляторах (например, GCC) есть также специфичные конструкции, которые надо будет поддержать. Это незаметный, но очень большой объем работ. То, что ключевое слово '__restrict' используется в 10 000 раз реже, чем 'for' вовсе не означает, что его можно не поддерживать. Многие программисты и не подозревают о таких вещах как __w64, __noop, __if_exists, __int3264, __uuidof, __based, __LPREFIX и так далее и так далее.

Наконец используемые наборы заголовочных (include) файлов в разных системах также различаются. И если PVS-Studio работает с заголовочными файлами Visual C++, то это совершенно не значит, что также все будет работать с заголовочными файлами GCC.

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

У меня в Visual Studio Express Edition после установки не появилось меню PVS-Studio. Почему?

Никакая версия Visual Studio Express Edition не поддерживает подключаемые модули расширений – это ограничение данной редакции Visual Studio и PVS-Studio здесь ни при чём.

Советы по повышению скорости работы PVS-Studio

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

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

  • Используйте многоядерный компьютер с большим объемом оперативной памяти.
  • Используйте SSD диск и для системы, и для проверяемого проекта.
  • Настройте (или выключите) антивирус.
  • По-возможности используйте Clang в качестве препроцессора вместо Visual C++ (задается в настройках PVS-Studio).
  • Исключите из анализа лишние библиотеки (задается в настройках PVS-Studio).
  • Проверяйте только файлы, в которые вносились изменения за последние дни.

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

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

PVS-Studio давно (с версии 3.00, вышедшей в 2009 году) поддерживает работу в несколько потоков. Распараллеливание выполняется на уровне файлов. Если анализ выполняется на четырех ядрах, то одновременно проверяется четыре файла. Такой уровень параллелизма позволяет обеспечить существенное повышение производительности. По нашим замерам разница во времени работы на тестовых проектах очень заметна. Анализ в один поток выполняется за 3 часа 11 минут, в то время как анализ в четыре потока выполняется за 1 час 11 минут (данные получены на машине с четырьмя ядрами и восемью гигабайтами оперативной памяти). Отличие в 2.7 раза.

Для каждого потока анализатора рекомендуется иметь не менее одного гигабайта оперативной памяти. В противном случае (если будет много потоков и мало оперативной памяти) будет использоваться файл подкачки, что приведет к замедлению работы. При необходимости можно ограничить количество потоков анализатора с помощью настроек PVS-Studio Options -> Common Analyzer Settings -> Thread Count (документация). По-умолчанию запускается столько потоков сколько ядер в системе.

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

Используйте SSD диск и для системы, и для проверяемого проекта

Медленный жесткий диск, как ни странно, является очень слабым местом в работе анализатора кода. Но надо пояснить механизм работы анализатора для того, чтобы было понятно, почему это так. Для того чтобы выполнить анализ файла, необходимо сначала его препроцессировать, т.е. раскрыть все #define, подставить все #include и т.п. Файл после препроцессинга имеет размер в среднем 10 мегабайт и записывается на диск в папку проекта. Затем уже читается анализатором и начинается его разбор. Рост файла происходит как раз за счет вставки содержимого #include-файлов, которые читаются из системных папок.

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

Настройте (или выключите) антивирус

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

На каждый анализируемый файл запускается отдельный процесс анализатора (модуль PVS-Studio.exe). Если в проекте 3000 файлов, то столько раз и будет запущен PVS-Studio.exe. Для своей работы PVS-Studio.exe вызывает настройку переменных окружения Visual C++ (файлы vcvars*.bat). Также во время работы создается большое количество препроцессированных файлов (*.i), по одному на каждый компилируемый файл. Используются вспомогательные командные (.cmd) файлы.

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

Мы рекомендуем сделать следующие исключения в настройках антивируса:

  • Не проверять системные папки с Visual Studio:
    • C:\Program Files (x86)\Microsoft Visual Studio 8
    • C:\Program Files (x86)\Microsoft Visual Studio 9.0
    • C:\Program Files (x86)\Microsoft Visual Studio 10.0
    • и т.п.
  • Не проверять папку с PVS-Studio:
    • C:\Program Files (x86)\PVS-Studio
  • Не проверять папку проекта с кодом:
    • Например, C:\Users\UserName\Documents\MyProject
  • Не проверять исполняемые файлы Visual Studio:
    • C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDE\devenv.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe
    • и т.п.
  • Не проверять исполняемые файлы компилятора cl.exe (разных версий):
    • C:\Program Files (x86)\Microsoft Visual Studio 8\VC\bin\cl.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 8\VC\bin\x86_amd64\cl.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 8\VC\bin\amd64\cl.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\cl.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\x86_amd64\cl.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\amd64\cl.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cl.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\x86_amd64\cl.exe
    • C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\amd64\cl.exe
    • и т.п.
  • Не проверять исполняемые файлы PVS-Studio и Clang (разных версий):
    • C:\Program Files (x86)\PVS-Studio\x86\PVS-Studio.exe
    • C:\Program Files (x86)\PVS-Studio\x86\clang.exe
    • C:\Program Files (x86)\PVS-Studio\x64\PVS-Studio.exe
    • C:\Program Files (x86)\PVS-Studio\x64\clang.exe

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

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

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

По-возможности используйте Clang в качестве препроцессора вместо Visual C++ (задается в настройках PVS-Studio)

Для препроцессирования файлов в PVS-Studio используется внешний препроцессор. Раньше у нас использовался только один препроцессор от Microsoft Visual C++. В PVS-Studio 4.50 появилась поддержка второго препроцессора Clang, который работает существенно быстрее и лишен ряда недостатков препроцессора от Microsoft (хотя и имеет свои недостатки). Тем не менее, в большинстве случаев использование препроцессора Clang позволяет повысить скорость работы в 1.5-1.7 раз.

Однако здесь есть нюанс, который надо учитывать. Указать используемый препроцессор можно в настройках PVS-Studio Options -> Common Analyzer Settings -> Preprocessor (документация). Доступны варианты: VisualCPP, Clang и VisualCPPAfterClang. Первые два варианта очевидны, а третий вариант означает, что сначала будет использоваться Clang, в случае если при препроцессировании будут ошибки, то затем файл будет заново препроцессирован с помощью Visual C++. По-умолчанию выбрана именно эта опция (VisualCPPAfterClang).

Если ваш проект проверяется без проблем с помощью Clang, то вы можете использовать стандартное значение VisualCPPAfterClang или Clang – без разницы. Однако если ваш проект может быть проверен только с помощью Visual C++, рекомендуем выставить именно это значение, чтобы анализатор не запускал напрасно Clang в попытках препроцессировать им.

Исключите из анализа лишние библиотеки (задается в настройках PVS-Studio)

Любой крупный программный проект использует много сторонних библиотек, таких как zlib, libjpeg, Boost и др. Иногда эти библиотеки собираются отдельно и тогда в основном проекте доступны только заголовочные и библиотечные (lib) файлы. А иногда библиотеки очень плотно интегрированы в проект, фактически становясь его частью. В этом случае при компиляции основного проекта компилируются также и файлы с кодом от этих библиотек.

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

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

Для того чтобы исключить из анализа какие-то папки или отдельные файлы используйте настройки PVS-Studio -> Don't Check Files (документация).

Для исключения папок можно указать в списке папок либо одну общую папку вроде c:\external-libs, либо явно перечислить некоторые из них: c:\external-libs\zlib, c:\external-libs\libjpeg и т.д. Можно указывать полный путь, относительный или маску. Например, достаточно указать zlib и libjpeg в списке папок – это автоматически будет трактоваться как папка с маской *zlib* и *libjpeg*. Подробнее смотрите в документации.

Проверяйте только файлы, в которые вносились изменения за последние дни

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

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

Задать такой промежуток для проверки можно с помощью настройки ‘Check only Files Modified In’ в окне PVS-Studio/Common Analyzer Settings. По умолчанию PVS-Studio проверяет все файлы, независимо от времени и даты их модификаций.

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

Описанные режим работы имеет ряд ограничений, накладываемых особенностями работы файловой системы, компилятора Visual C++ и используемой системой управления версиями. Подробно с ними можно познакомиться в документации данного режима.

Итоги

Перечислим еще раз способы повысить скорость работы PVS-Studio:

  • Используйте многоядерный компьютер с большим объемом оперативной памяти.
  • Используйте SSD диск и для системы, и для проверяемого проекта.
  • Настройте (или выключите) антивирус.
  • По-возможности используйте Clang в качестве препроцессора вместо Visual C++ (задается в настройках PVS-Studio).
  • Исключите из анализа лишние библиотеки (задается в настройках PVS-Studio).
  • Проверяйте только файлы, в которые вносились изменения за последние дни.

Наибольший эффект достигается применением всех этих рекомендаций сразу.

Команды меню PVS-Studio

Check Current File

Данная команда запускает проверку анализатором PVS-Studio файла, открытого в данный момент в окне текстового редактора среды Visual Studio, для выбранных сборочных конфигурации и платформы. Если же не один из файлов не открыт в редакторе кода, будет запущена проверка для текущего выбранного элемента в окне Solution Explorer. Стоит заметить, что данная команда предназначена в первую очередь именно для проверки кода открытого в редакторе, а для проверки выделенных в окне Solution Explorer элементов (одного или нескольких) удобнее использовать команду меню Check Selected item(s).

Check Current Project

Данная команда запускает проверку анализатором PVS-Studio всех файлов проекта, выделенного в окне среды Solution Explorer, для выбранных сборочных конфигурации и платформы. Команда Check Current Project предназначена для проверки только одного выделенного проекта, для проверки сразу нескольких проектов можно использовать команду меню Check Selected item(s).

Check Selected item(s)

Данная команда запускает проверку анализатором PVS-Studio объектов, выделенных в окне среды Solution Explorer, для выбранных сборочных конфигурации и платформы. При этом можно выбирать как отдельные C/C++ файлы или целые Visual C++ проекты, так и папки и группы проектов, однако для проверки всех объектов дерева Solution Explorer удобнее использовать команду меню Check Solution.

Check Solution

Данная команда запускает проверку анализатором PVS-Studio всех файлов с C/C++ исходным кодом, принадлежащим к открытому в данный момент в среде Visual Studio solution-файлу для выбранных сборочных конфигурации и платформы, т.е. для всех проектов, открытых в окне Solution Explorer. Следует помнить, что проекты, исключённые из сборки в настройках Configuration Manager для выбранной сборочной конфигурации, проверяться не будут.

Incremental Analysis after Build

Команда Incremental Analysis after Build позволяет задать режим работы (включить или выключить) инкрементального анализа PVS-Studio. Во включённом режиме IDE-плагин будет следить за выполнением сборок Visual C++ проектов и отдельных C/C++ файлов и автоматически проверять те файлы, которые были модифицированы с момента предыдущей успешной сборки.

Disable incremental analysis until IDE restart

При использовании инкрементального анализа PVS-Studio (команда Incremental Analysis after Build), данная команда при необходимости позволит отключить его для текущего сеанса среды Visaul Studio, т.е. после перезапуска инкрементальный анализ снова будет активирован. Данный пункт меню при запуске среды разработки по умолчанию всегда отключен. Инкрементальный анализ также может быть временно отключен в контекстном меню области уведомлений Windows (tray иконка).

Open/Save

Подменю Open/Save содержит группы команд, отвечающие за работу с файлами-отчётами PVS-Studio (файлы с расширением plog) и за управление конфигурационным файлом анализатора Settings.xml.

Open Analysis Report

Команда Open Analysis Report позволяет загрузить ранее сохранённый отчёт анализатора в окно вывода PVS-Studio среды разработки. С помощью данной команды возможна загрузка как стандартного отчёта в XML формате (расширение plog), так и необработанного вывода анализатора PVS-Studio.exe в случае его запуска из командной строки в независимом от Visual Studio режиме (для этого необходимо задать в стандартном диалоге открытия файла тип как 'unparsed output').

Save Analysis Report As...

Позволяет сохранить текущие результаты работы анализатора из окна PVS-Studio Output в виде стандартного XML файла PVS-Studio (файл типа plog), либо в виде обычного текстового файла, тип файла можно задать в выпадающем меню стандартного диалога сохранения Windows.

Save Analysis Report

Данная команда позволяет сохранить текущие результаты работы анализатора PVS-Studio. В случае если результаты получены путём загрузки уже существующего отчёта, либо если в текущей сессии среды разработки сохранение результатов уже производилось, исходный файл будет перезаписан. Иначе будет вызвано стандартное окно сохранения файла Windows (как и в команде 'Save Analysis Report As...')

Export current configuration to file...

Позволяет экспортировать текущие настройки анализатора PVS-Studio, сохранив их в отдельный xml файл. Настройки, используемые анализатором, хранятся в файле Settings.xml в директории %AppData%/PVS-Studio.

Import current configuration from file...

Данная команда позволяет импортировать настройки анализатора из внешнего Xml файла настроек PVS-Studio. Импортированные настройки переписывают текущие настройки анализатора, находящемся в файле Settings.xml в директории %AppData%/PVS-Studio. Текущие настройки также могут быть экспортированы во внешний xml файл с помощью команды меню 'Export current configuration to file...'.

Reset to default configuration

Данная команда позволяет откатить настройки анализатора к значения по умолчанию. Использование этой команды не приведёт к потере регистрационной информации. Отметим, что для сброса отображения всех типов ошибок по умолчанию (меню Detectable Errors) удобнее использовать команду меню Reset detectable errors to default, это позволит сохранить значения остальных фильтров подавления предупреждений.

Reset detectable errors to default

Позволяет сбросить значения фильтров страницы настроек Detectable errors к значению по умолчанию, в котором включено отображение всех наиболее значимых типов диагностик. Для полного сброса настроек (включая все фильтры системы подавления предупреждений) можно использовать команду меню Reset to default configuration.

Show PVS-Studio Output window

Данная команда открывает окно вывода PVS-Studio среды разработки, либо передаёт данному окну фокус, если оно уже было открыто ранее.

Find in PVS-Studio output...

Команда открывает окно среды 'Find in PVS-Studio Output' либо передаёт ему фокус, если оно уже было открыто. Данное окно также может быть открыто с помощью кнопки 'Find in PVS-Studio output...' на инструментальной панели окна 'PVS-Studio Output'. Окно 'Find in PVS-Studio Output' предназначено для текстового поиска в результатах работы анализатора, выводимых в его окно вывода.

Help

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

Open PVS-Studio Documentation (PDF)

Команда открывает единый PDF файл, содержащий полную документацию по анализатору. Данная документация также доступна на нашем сайте с помощью команды Open PVS-Studio Documentation (html, online).

Open PVS-Studio Documentation (html, online)

Данная команда открывает оглавление документации анализатора PVS-Studio на сайте viva64.com. Локальная версия данной документации также доступна в виде PDF-файла через команду Open PVS-Studio Documentation (PDF)

Open OmniSample.zip examples

Позволяет открыть архив OmniSample.zip, содержащий набор примеров ошибок, диагностируемых анализатором PVS-Studio (проекты OmniSample для сред Visual Studio 2005/2008/2010)

Request support via website

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

Check for updates

Позволяет проверить наличие обновлений для анализатора PVS-Studio. Заметим, что IDE-плагин Visual Studio оп умолчанию автоматически проверяет доступность новых версий непосредственно перед запуском анализа кода (пункт настроек CheckForNewVersions на вкладке PVS-Studio->Common Analyzer Settings).

Enter registration information...

Позволяет ввести регистрационную информацию для анализатора PVS-Studio. По умолчанию анализатор работает в пробном режиме, который не требует ввода данных в настройках PVS-Studio. Регистрационную информацию также можно ввести, открыв страницу настроек анализатора Registration с помощью команды меню Options либо команды Tools->Options среды Visual Studio. Введение регистрационной информации не требует перезапуска среды разработки Visual Studio.

Options

Данная команда позволяет открыть вкладку настроек анализатора PVS-Studio, которая интегрирована в общие настройки среды разработки. Заметим, что экран настроек PVS-Studio также может быть открыт через стандартный диалог среды разработки Tools->Options->PVS-Studio.

Режим инкрементального анализа PVS-Studio

Введение

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

Полный анализ можно регулярно запускать отдельно, например раз в сутки во время ночных сборок. Однако наибольшего эффекта от использования анализатора можно добиться только за счёт более раннего обнаружения и исправления выявленных им дефектов. Ведь чем раньше удастся обнаружить ошибку, тем меньше кода придётся в итоге править. То есть самым оптимальным вариантом использования статического анализатора является проверка нового кода сразу после его написания. Такой сценарий работы, несомненно, осложняется необходимостью постоянно вручную запускать проверку для всех модифицированных файлов. И каждый раз ждать завершения анализа. Это несовместимо с интенсивной разработкой и отладкой нового кода. Да и просто неудобно. Однако PVS-Studio предлагает решение этой проблемы.

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

  • Автоматизация запуска анализатора сразу же после компиляции на машине разработчика.
  • Получение выгоды от внедрения статического анализа в большом проекте БЕЗ необходимости проверки всего проекта. Фактически вы можете начать внедрять статический анализ кода, проверяя только тот код (те файлы), который модифицируется разработчиками в настоящее время и не лезть в кучу старых файлов, которые не модифицируются.

Использование инкрементального анализа

Режим инкрементального анализа PVS-Studio позволяет решить проблемы с регулярным запуском статического анализа. Пользователь начинает получать практическую пользу без необходимости первичной проверки всего проекта и разметки множества ложно-позитивных срабатываний в неиспользуемом коде. Фактически, такой режим работы PVS-Studio приближает по удобству использования инструмент к ключу /analyze в некоторых версиях Visual Studio. И при этом с намного более удобным интерфейсом и сценариями использования. Теперь вы просто работаете над кодом, компилируете его и периодически получаете сообщения о возможных проблемах от анализатора. Так как анализатор работает в фоне (количество используемых для анализа ядер можно настроить, опция ThreadCount), то использование PVS-Studio не мешает работе других программ. И это значит, что инструмент легко можно установить на машины большого количества (или всех) разработчиков в отделе, чтобы выявлять проблемы в коде сразу же в момент их появления.

Включить режим послесборочного инкрементального анализа можно в меню PVS-Studio/Incremental Analysis After Build (рисунок 1), данный пункт активирован в PVS-Studio по-умолчанию.

Рисунок 1 — управление режимом инкрементального анализа PVS-Studio

Рисунок 1 — управление режимом инкрементального анализа PVS-Studio

После активации режима инкрементного анализа PVS-Studio станет автоматически в фоновом режиме производить анализ всех затронутых модификациями файлов сразу после окончания сборки проекта. Если PVS-Studio обнаружит такие модификации, инкрементальный анализ будет автоматически запущен, а в области уведомлений Windows появится анимированная иконка PVS-Studio (рисунок 2). Обратите внимание, что нередко в Windows новые иконки в этой области оказываются скрыты.

Рисунок 2 — PVS-Studio в процессе инкрементального анализа

Рисунок 2 — PVS-Studio в процессе инкрементального анализа

Контекстное меню области уведомлений позволяет на время приостановить (команда Pause) или отменить (команда Abort) текущую проверку. Иногда бывает полезно временно отключить инкрементальный анализ. Как, например, при редактировании базовых h-файлов, которое приводит к перекомпиляции большого количества файлов. Чтобы отключить инкрементальный анализ не навсегда, а временно, можно воспользоваться командами контекстного и главного меню PVS-Studio 'Disable incremental analysis until IDE restart' (рисунки 1 и 2).

В случае если анализатор во время инкрементного анализа обнаружит ошибки в коде, в названии открытой в фоне вкладки окна PVS-Studio отразится число найденных ошибок, а иконка в заголовке окна станет красной. Клик по иконке в области уведомлений (либо по самому окну) откроет окно PVS-Studio Output, и c ошибками можно будет начать работать, даже не дожидаясь полного завершения анализа.

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

Порядок работы инкрементального анализа в среде Visual Studio

Для определения наличия модификаций в файлах с исходным кодом PVS-Studio контролирует состояние объектных файлов (obj файлы), генерируемых компилятором Visual C++ для каждого C/C++ файла. В момент перед непосредственным началом сборки в среде Visual Studio, IDE плагин PVS-Studio фиксирует наличие объектных файлов и время их модификации для всех C/C++ файлов проекта. В случае если проект содержит большое количество файлов, это может привести к едва заметному подвисанию IDE в момент начала сборки. А после сборки сравнивает их конечное состояние с зафиксированным до начала сборки. И запускает инкрементальный анализ для тех C/C++ файлов, объектные файлы для которых были перегенерированы или сгенерированы заново. Стоит заметить, что анализ будет произведён только для тех файлов, для которых компилятор сгенерировал объектные файлы по окончании сборки. В случае возникновения ошибок компиляции или линковки анализ соответствующих C/C++ файлов производиться не будет, т.к. компилятор не произведёт перегенерации их объектных файлов.

Недостатки инкрементального анализа

Если PVS-Studio использует препроцессор CL.exe, входящий в состав Visual C++ то изредка возникает неприятная ситуация с блокированием файлов. Выглядеть это может так. Вы поправили файл и скомпилировали его. Запустился инкрементальный анализ. В этот момент вы заметили мелкий недостаток, быстро его поправили и вновь попытались скомпилировать файл. Среда Visual Studio предупредит, что отредактированный файл не может быть записан, так как какая-то программа заблокировала его. Этой программой является CL.exe, выполняющий препроцессирование. К сожалению, с этой блокировкой мы ничего сделать не можем. Если возникла такая ситуация, то следует подождать несколько секунд и продолжить прерванную работу. Заметим, что при использовании препроцессора Clang (опция настроек Preprocessor) такая ситуация возникает значительно реже. Поэтому при возможности, используйте Clang в качестве препроцессора.

Развертывание PVS-Studio в больших командах

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

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

В этой статье приведено детальное описание того, как PVS-Studio решает перечисленные выше проблемы.

Тестирование на совместимость

Для PVS-Studio совместимость с другими приложениями не является проблемой - PVS-Studio распространяется как дополнение к Visual Studio.

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

Автоматическое развертывание

Как и большинство других программных продуктов, для установки PVS-Studio требуются права администратора.

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

PVS-Studio_setup[.exe] /verysilent /norestart

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

Установка лицензий

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

Ввод лицензии можно осуществить через настройки PVS-Studio. Откройте страницу настроек PVS-Studio (PVS-Studio Menu -> Options...) из запущенной версии Visual Studio и выберите вкладку "Registration".

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

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

Расположение файлов должно быть следующее:

%USERPROFILE%\AppData\Roaming\PVS-Studio\PVS-Studio.lic
%USERPROFILE%\AppData\Roaming\PVS-Studio\Settings.xml

Настройка приложения

Установка настроек приложения так же проста, как и установка лицензии.

Расположение файла, в котором сохраняются все пользовательские настройки, следующее:

%USERPROFILE%\AppData\Roaming\PVS-Studio\Settings.xml

Этот файл можно редактировать любым текстовым редактором - это простой xml-файл. Также настройки могут быть изменены на компьютере с установленным PVS-Studio.

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

Если у вас есть вопросы, наша служба поддержки с удовольствием на них ответит!

Settings: General

При разработке PVS-Studio простота использования ставилась во главу угла. Мы учли наш опыт общения с традиционными lint-подобными анализаторами кода. Именно поэтому возможность сразу же приступить к работе является одним из основных преимуществ PVS-Studio по сравнению с другими анализаторами кода. Кроме того, PVS-Studio проектировалась так, чтобы разработчику, который будет использовать анализатор, вообще не пришлось его настраивать. Эту задачу удалось решить: разработчик имеет мощнейший анализатор кода, который нет необходимости настраивать при первом запуске.

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

  • какие-то типы ошибок (то есть потенциально опасных конструкций) в данном проекте возникнуть в принципе не могут (отключив соответствующие диагностические сообщения, Detectable Errors);
  • проект не работает с большими массивами данных определенных типов (указав это в настройке, Viva64);
  • проект не содержит некорректных приведений типов (отключив соответствующие диагностические сообщения, Detectable Errors);

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

Настройки PVS-Studio выполняются через стандартный для Microsoft Visual Studio механизм - команду "Options" из меню "Tools". Выбрав эту команду, Вы увидите диалог настройки Microsoft Visual Studio, одним из пунктов которого будет PVS-Studio.

Каждый диалог настройки описан в документации к PVS-Studio.

Settings: Common Analyzer Settings

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

Picture 2

Check For New Versions

Анализатор может автоматически проверять наличие своей новой версии на сайте www.viva64.com . Для этого используется собственный модуль обновления.

В случае если опция CheckForNewVersions имеет значения True, то при запуске проверки кода (команды Check Current File, Check Current Project, Check Solution меню PVS-Studio) выполняется загрузка специального текстового файла с сайта www.viva64.com. В этом файле прописан номер самой последней версии PVS-Studio, доступной на сайте. Если версия на сайте окажется новее, чем версия, установленная у пользователя, то пользователь увидит запрос на обновление. В случае разрешения этого обновления запустится специальное отдельное приложение PVS-Studio-Updater, которое автоматически загрузит новый дистрибутив PVS-Studio с сайта и запустит его установку. В случае если опция CheckForNewVersions установлена в False, то проверка новой версии производиться не будет.

Check only Files Modified In

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

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

  • Редактирование одних только заголовочных h файлов приведёт к выпадению из проверки c\cpp файлов, в которые данные заголовки включены, в случае, если сами c\cpp файлы не были также изменены.
  • При выполнении полного checkout из репозитория системы контроля версий атрибут "Date Modified" может быть выставлен для файлов системой на время самой операции checkout (так в частности поступает subversion), т.е. время модификации у всех файлов будет одинаковым. Аналогичная ситуация гипотетически может возникнуть и в случае простого копирования файлов.

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

Preprocessor

Для препроцессирования файлов в PVS-Studio используется внешний препроцессор. Раньше у нас использовался только один препроцессор от Microsoft Visual C++. В PVS-Studio 4.50 появилась поддержка второго препроцессора Clang, который работает существенно быстрее и лишен ряда недостатков препроцессора от Microsoft (хотя и имеет свои недостатки). Тем не менее, в большинстве случаев использование препроцессора Clang позволяет повысить скорость работы в 1.5-1.7 раз.

Однако здесь есть нюанс, который надо учитывать. Указать используемый препроцессор можно в настройках PVS-Studio Options -> Common Analyzer Settings -> Preprocessor. Доступны варианты: VisualCPP, Clang и VisualCPPAfterClang. Первые два варианта очевидны, а третий вариант означает, что сначала будет использоваться Clang, в случае если при препроцессировании будут ошибки, то затем файл будет заново препроцессирован с помощью Visual C++. По-умолчанию выбрана именно эта опция (VisualCPPAfterClang).

Remove Intermediate Files

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

Show Tray Icon

Данная настройка позволяет управлять уведомлениями о работе анализатора PVS-Studio. После завершения анализа, в случае, если окно PVS-Studio Output содержит сообщения об ошибках (сообщения об ошибках могут быть скрыты фильтрами как ложные срабатывания, по именам проверяемых файлов и т.п.; такие сообщения не попадают в окно PVS-Studio), анализатор проинформирует об их наличии с помощью всплывающего сообщения в области уведомления Windows (notification area, system tray). Одинарный щелчок мышью по данному сообщению либо по значку PVS-Studio откроет окно вывода сообщений PVS-Studio, содержащее найденные ошибки.

Thread Count

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

Trace Mode

Настройка задаёт режим трассировки (протоколирования хода выполнения программы) для IDE пакета расширения PVS-Studio (плагина для среды Visual Studio). Трассировка имеет несколько уровней детализации вывода (Режим Verbose соответствует наиболее подробному протоколированию). При включении режима трассировки PVS-Studio автоматически создаёт лог файл в директории LocalAppData\PVS-Studio с расширением log (Например c:\Users\admin\AppData\Roaming\PVS-Studio\PVSTrace2168_000.log). При этом каждый запущенный процесс IDE Visual Studio будет использовать отдельный лог файл для сохранения результатов своей трассировки.

Settings: Customers Specific Settings

На вкладке "Customers Specific Settings" размещены настройки, предназначенные для интеграции инструмента в среды разработки пользователей, владеющих коммерческой лицензией на использование анализатора.

Picture 1

Analysis Timeout

Данная настройка позволяет задать лимит времени, по истечении которого анализ отдельных файлов завершается с ошибкой V006. File cannot be processed. Analysis aborted by timeout, или совсем отключить прерывание анализа по лимиту времени. Перед изменением данной настройки мы рекомендуем внимательно ознакомиться с описанием ошибки, приведенным по указанной выше ссылке. Часто бывает, что таймаут возникает из-за нехватки оперативной памяти. В этом случае рационально не увеличивать время, а уменьшить количество используемых параллельных потоков. Это может дать существенный прирост производительности, когда процессорных ядер много, а оперативной памяти мало.

Save File After False Alarm Mark

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

Будьте осторожны при изменении данной настройки, т.к. отсутствие сохранений файлов с исходным кодом после их разметки на ложные срабатывания может привести к потере работы в случае закрытия среды разработки Visual Studio.

Use Solution Folder As Initial

По умолчанию PVS-Studio предлагает сохранять файл отчета (.plog) в той же папке, что и .sln-файл.

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

Settings: Detectable Errors

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

Picture 4

Анализатор обнаруживает все ошибки, которые поддерживает текущая версия независимо от указанных на данной странице настроек. Однако иногда бывает целесообразно убрать в отчете ошибки с определенными кодами. Например, если вы уверены, что ошибки, связанные с явным приведением типа (коды V201, V202, V203) вас не интересуют, то вы можете скрыть их показ. Для этого просто нужно установить в False значения напротив каждого из этих кодов ошибок.

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

Settings: Don't Check Files

На вкладке "Don't Check Files" настроек можно ввести файловые маски для исключения некоторых файлов или папок из анализа. Анализатор не будет проверять файлы, удовлетворяющие условиям масок.

Picture 4

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

Маска задается с помощью специальных wildcard символов. Допустим только символ "*" (любое количество любых символов), символ "?" не используется.

Регистр символов не имеет значения. Символ "*" может быть добавлен только в начале или в конце маски, маски вида 'a*b' не поддерживаются. После задания масок исключений, сообщения из соответствующих им файлов исчезнут из окна вывода PVS-Studio, а в следующую проверку они включены уже не будут. Таким образом, исключение файлов и директорий посредством масок может позволить существенно сократить общее время анализа всего проекта.

В окне можно задавать 2 типа масок: маски по путям (Path Mask) и маски по именам файлов (FileName Mask). Маски, заданные в списке FileNameMasks используются для фильтрации сообщений только непосредственно по именам файлов, без учёта директории, в которой эти файлы находятся. Маски из списка PathMasks фильтруют диагностические сообщения с учётом расположения фалов в файловой системе на диске и позволяют подавлять сообщения как для отдельных файлов, так и для целых директорий и поддиректорий. Так, для фильтрации сообщений в одном конкретном файле, полный путь до него необходимо добавить в список PathMasks, а для фильтрации всех файлов с одинаковыми (либо удовлетворяющими wildcard маске) именами можно добавить такое имя или маску в список FileNameMasks.

Примеры допустимых масок для списка FileNameMasks:

*ex.c — будут исключены все файлы с именем, оканчивающимся на символы "ex" и имеющие расширение "c".

*.cpp — будут исключены все файлы имеющие расширение "cpp"

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

Примеры допустимых масок для списка PathMasks:

c:\Libs\ — будут исключены все файлы проекта, расположенные в данной папке и её подпапках.

\Libs\ или *\Libs\* — будут исключены все файлы, расположенные в директориях, путь до которых содержит подпапку Libs. Если символы "*" не указаны, они все равно будет автоматически добавлены, поэтому оба варианта записи одинаковы.

Libs или *Libs* — исключены будут все файлы, путь до которых содержит подпапку, имеющую 'Libs' в качестве имени либо фрагмента имени. Также в этом случае будут исключены файлы, содержащие Libs в имени, например, c:\project\mylibs.cpp. Поэтому для избежания путаницы, рекомендуем папки задавать всегда со слешами.

c:\proj\includes.cpp — будет исключён только один файл с заданным именем, находящийся в директории c:\proj\

Settings: Message Suppression

На вкладке подавления отдельных сообщений можно настроить фильтрацию ошибок по содержащемуся в них тексту.

Picture 4

При необходимости можно скрыть из отчета сообщения о диагностированных ошибках, содержащих определенные слова или фразы. Например, если в отчете есть ошибки, в которых указаны названия функций printf и scanf, а вы считаете, что ошибок, связанных с ними быть не может, то просто добавьте эти два слова с помощью редактора подавляемых сообщений.

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

Settings: Registration

Откройте страницу настроек PVS-Studio (PVS-Studio Menu -> Options...).

На вкладке регистрации вводится лицензионная информация.

Picture 4

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

Информация по условиям лицензирования смотрите на странице заказа на сайте и в разделе "Регистрация" справочной системы.

Settings: Viva64

На вкладке Viva64 содержатся настройки, характерные только для модуля анализа 64-битного кода.

Picture 2

TypesForNonLargeArrays

При выявлении 64-битных ошибок с помощью набора правил Viva64, анализатор выдает много ложных срабатываний. К сожалению это неизбежно, в силу специфики данного типа диагностики. Однако мы стараемся разными способами сокращать набор ложных срабатываний. Одних из этих способов является введение такого понятия, как тип, из которого не создаются большие массивы.

Анализатор вынужден выдавать предупреждение V108 на код в следующей функции, так как не знает, может ли массив содержать более чем INT_MAX (2^31) элементов типа MY_STRUCT.

struct MY_STRUCT { int a; };
class MyArray {
  MY_STRUCT *m_arr;
  MY_STRUCT &Get(int i) {
    return m_arr[i]; //V108
  } 
};

Если объектов типа MY_STRUCT не может быть много, то данный код безопасен. Типа 'int' достаточно, чтобы получить доступ к любому элементу массива 'm_arr'. Можно подсказать анализатору PVS-Studio, объектов каких типов не будет создано много. Для этого имеется настройка с именем "Custom". В ней можно задать имя файла, который будет содержать список типов. Файл должен быть текстовым. Каждая строчка в файле содержит имя типа. Файл, например, может выглядеть следующим образом:

MyType_A
MyHandle
MyPoint

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

WinAPI: HWND, HMENU, HMODULE, HCURSOR, ...

MFC/ATL: CWnd, CComPtr, CTabCtrl, CDC, ...

WX widgets: wxString, wxBrush, wxDC, wxPrintData, ...

QT: QPalette, QFontMetrics, QLocale, QCursor, ...

STL: deque, string, vector, set, ...

Массив, состоящий более чем из двух миллиардов элементов типа CWnd, просто не имеет смысла. Столько окон просто невозможно создать.

С другими типами, такими как deque, не все так очевидно. Еще раз подчеркнем, что здесь речь идет не о размере контейнера deque. Речь идет о размере массива, состоящего из элементов типа deque. Если размер такого массива приближается к INT_MAX, то, пожалуй, использование deque не лучший архитектурный вариант и стоит задуматься над собственной оптимизированной для решаемой задачи структурой данных.

Поэтому анализатор по умолчанию считает, что в программе нет массивов, содержащих в себе более INT_MAX (2^31) строк типа std::string и т.п. Если это не так, то пользователь может отказаться от того, что анализатор считает некоторые массивы безопасными. Для этого имеется возможность включить/выключить в настройках некоторые наборы типов для распространенных библиотек: MFC, Qt, STL, WinAPI, wxWidgets.

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

V001. A code fragment from 'file' cannot be analyzed.

Анализатор не всегда может полностью проанализировать файл с исходным кодом. Это может произойти по трем причинам:

1) Ошибка в коде

В коде имеется шаблонный класс или шаблонная функция с ошибкой в коде. Если эта функция не инстанцируется, то компилятор не диагностирует в ней наличие некоторых ошибок. Другими словами наличие такой ошибки не мешает компиляции. Анализатор PVS-Studio пытается искать потенциальные ошибки даже в классах и функциях, которые нигде не используются. В случае если анализатор не может разобрать код, то он и выдаст предупреждение V001. Рассмотрим пример кода:

template <class T>
class A
{
public:
  void Foo()
  {
    //Забыли ;
    int x
  }
};

Этот код будет скомпилирован Visual C++, если класс A не будет нигде использоваться. Однако ошибка в нем присутствует, что мешает работе PVS-Studio.

2) Ошибка в препроцессоре Visual C++

Анализатор в процессе своей работы использует препроцессор Visual C++. Этот препроцессор очень редко, но все же допускает ошибки при генерации препроцессированных *.i файлов. В результате анализатор получает на вход некорректные данные. Рассмотрим пример:

hWnd = CreateWindow (
  wndclass.lpszClassName,    // window class name
  __T("NcFTPBatch"),         // window caption
  WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
    // window style
  100,            // initial x position
  100,            // initial y position
  450,            // initial x size
  100,            // initial y size
  NULL,           // parent window handle
  NULL,           // window menu handle
  hInstance,      // program instance handle
  NULL);          // creation parameters
if (hWnd == NULL) {
...

Приведенный фрагмент кода, препроцессор Visual C++ превратил в:

hWnd = // window class name// window caption// window style// 
initial x position// initial y position// initial x size// 
initial y size// parent window handle// window menu handle// 
program instance handleCreateWindowExA(0L, 
wndclass.lpszClassName, "NcFTPBatch", 0x00000000L | 0x00C00000L |
 0x00080000L | 0x00020000L, 100, 100,450, 100, ((void *)0), 
((void *)0), hInstance, ((void *)0)); // creation parameters
if (hWnd == NULL) {
...

Получается, что в коде написано следующее:

hWnd = // длинный комментарий
if (hWnd == NULL) {
...

Этот код некорректен, о чем и выдаст сообщение PVS-Studio. Конечно, это недоработка PVS-Studio и со временем мы это исправим.

Отметим также, что Visual C++ успешно компилирует этот код, так как алгоритмы, используемые в нем для компиляции и для генерации препроцессированных *.i-файлов различны.

3) Недоработка внутри PVS-Studio

В редких случаях PVS-Studio неспособен разобрать сложный шаблонный код.

Какова не была бы причина выдачи сообщения V001, это сообщение не критично. Как правило, неполный разбор файла не несущественен с точки зрения анализа. PVS-Studio просто пропускает функцию/класс с ошибкой и продолжает анализ файла. Непроанализированным остается совсем небольшой участок кода.

V002. Some diagnostic messages may contain incorrect line number.

Иногда анализатор кода может выдавать ошибку "Some diagnostic messages may contain incorrect line number". Это происходит в двух случаях:

  • Использование многострочных #define-макросов на системе Microsoft Visual Studio 2005 без Service Pack1.
  • Использование многострочных #pragma директив на всех поддерживаемых версиях Microsoft Visual Studio.

Любой анализатор кода работает только с препроцессированными файлами. То есть с теми файлами, в которых раскрыты all defines все макросы (#define) и подставлены все включаемые файлы (#include). При этом в препроцессированном файле содержится информация о том, какие файлы куда подставились и в какие позиции. То есть в пропроцессированных файлах содержится информация о номерах строк.

Препроцессирование выполняется в любом случае. Для пользователя эта процедура выглядит прозрачно. Иногда препроцессор является частью анализатора кода, а иногда (как в случае с PVS-Studio) используется внешний препроцессор. В PVS-Studio используется препроцессор от Microsoft Visual Studio. Анализатор запускает для каждого обрабатываемого Си/Си++ файла компилятор командной строки cl.exe и генерирует с его помощью препроцессированный файл с расширением "i".

В препроцессоре от Microsoft Visual Studio 2005 есть ошибка. Если выполнять препроцессирование из командной строки (как делается в PVS-Studio), то при наличии в коде программы многострочных макросов информация о номерах строк сбивается. Это может привести к некорректному позиционированию анализатора кода в файле. То есть анализатор кода найдет реальную проблему, но строку, в которой эта проблема обнаружена, укажет неверно.

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

int _tmain(int argc, _TCHAR* argv[])
{
  int a = 0;
  int b = 1;
  size_t c = 2;
  assert(a ==
    b);
  a++;   // Анализатор покажет ошибку в этой безобидной строке.
  c = a; // Хотя реально диагностическое сообщение должно
         // указывать на строку "c = a;".
  return 0;
}

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

Если тот же самый код записать с assert в одну строку, то проблемы не будет:

int _tmain(int argc, _TCHAR* argv[])
{
  int a = 0;
  int b = 1;
  size_t c = 2;
  assert (a == b);
  a++;
  c = a; // Диагностическое сообщение ссылается на
         // правильную строку.
  return 0;
}

Эта ошибка проявляется только при работе PVS-Studio на Microsoft Visual Studio 2005 без пакета обновлений Visual Studio Service Pack 1. На Visual Studio 2005 Service Pack1, а также на более старших версиях (Visual Studio 2008 и выше) этой проблемы нет. В качестве решения мы рекомендуем установить на Visual Studio 2005 пакет обновлений Visual Studio 2005 Service Pack 1. Тогда код с многострочными макросами будет обрабатываться анализатором PVS-Studio корректно.

Еще одна ситуация, при которой выдается сообщение "Some diagnostic messages may contain incorrect line number" и происходит сбой в позиционировании диагностических сообщений. Речь идет о многострочных директивах #pragma специального вида. Вот пример корректного кода:

#pragma warning(push) 
void test()
{
  int a = 0;
  size_t b = a; // PVS-Studio сообщит об ошибке здесь
}

Если же директиву #pragma записать в две строки, то анализатор PVS-Studio укажет на ошибку в неправильном месте (будет сбой на одну строку):

#pragma \
  warning(push) 
void test()
{
  int a = 0;     // PVS-Studio укажет на ошибку здесь,
  size_t b = a;  // хотя реально ошибка должна быть здесь.
}

Хотя в другом случае с многострочной директивой #pragma ошибки не будет:

#pragma warning \
  (push) 
void test()
{
  int a = 0;
  size_t b = a; // PVS-Studio сообщит об ошибке в этой строке
}

Эта ошибка не лечится установкой Service Pack 1 на Visual Studio 2005 или переходом на Visual Studio 2008. Единственная рекомендация - это либо не использовать многострочные директивы #pragma, либо использовать, но в том варианте, в котором они корректно обрабатываются.

Анализатор кода пытается обнаружить сбой с нумерацией строк в обрабатываемом файле. Этот механизм эвристический и не может гарантировать правильного определения позиционирования диагностических сообщений в коде программы. Но если удается понять, что конкретный файл содержит многострочные макросы и есть ошибка позиционирования, то и выдается сообщение: "Some diagnostic messages may contain incorrect line number".

Этот механизм работает следующим образом.

Анализатор открывает исходный Си/Си++ файл, и ищет самую последнюю лексему. Выбираются только лексемы не короче трех символов, чтобы игнорировать закрывающиеся скобки и так далее. Например, для следующего кода последней лексемой будет считаться оператор "return":

01 #include "stdafx.h"
02
03 int foo(int a)
04 {
05   assert(a >= 0 &&
06          a <= 1000);
07   int b = a + 1;
08   return b;
09 }

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

Итак, мы имеем номера строк, где расположена последняя лексема в исходном файле и в препроцессированном файле. Если эти номера строк не совпадают, то произошел сбой в нумерации строк. И в данном случае анализатор предупредит об этом пользователя сообщением "Some diagnostic messages may contain incorrect line number".

Следует учитывать, что если многострочный макрос или многострочная #pragma-директива будут находиться в файле ниже всех найденных опасных участков кода, то все номера строк для найденных ошибок будут корректны. И хотя анализатор выдаст сообщение "Some diagnostic messages may contain incorrect line number for file", это не помешает вам проанализировать выданные им диагностические сообщения.

Обратим внимание, что хотя это и не ошибка непосредственно анализатора кода PVS-Studio, тем не менее, это приводит к некорректной работе анализатора кода.

V003. Unrecognized error found...

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

Хотя сообщение V003 возникает достаточно редко, мы будем вам благодарны, если вы поможете нам исправить проблему, которая привела к этому сообщению. Для этого пришлите, пожалуйста, препроцессированный i-файл, на котором возникла проблема, на адрес support@viva64.com.

Примечание. Препроцессированный i-файл получается из исходного файла (например, file.cpp) после работы препроцессора. Для того, чтобы получить такой файл, нужно на вкладке "Common Analyzer Settings" настроек PVS-Studio установить опцию RemoveIntermediateFiles в False и заново выполнить анализ одного этого файла. После этого в папке проекта надо найти соответствующий i-файл (например, file.i).

V004. For a more precise verification, please select x64 or Itanium configuration instead of Win32.

Анализатор, выявляя проблемы 64-битного кода, всегда должен проверять именно 64-битную конфигурацию проекта. Ведь именно в 64-битной конфигурации правильно раскрываются типы данных, правильно выбираются ветки вида "#ifdef WIN64" и так далее. Пытаться выявлять проблемы 64-битного кода в 32-битной конфигурации некорректно.

Однако иногда может оказаться полезным проверить 32-битную конфигурацию проекта. Сделать это можно в том случае, когда 64-битной конфигурации пока нет, но требуется оценить предстоящий объем работ по миграции кода на 64-битную платформу. Тогда можно проверить проект в 32-битном режиме. Проверка 32-битной конфигурации вместо 64-битной покажет, сколько диагностических сообщений выдаст анализатор при проверке 64-битной конфигурации. Наши эксперименты показывают, что, конечно же, далеко не все диагностические сообщения выдаются при проверке 32-битной конфигурации. Однако порядка 95% диагностических сообщений при проверке 32-битной конфигурации проекта совпадают с сообщениями при проверке 64-битной конфигурации. Это позволяет оценить предстоящий объем работ.

Обратите внимание! Если вы исправите даже все ошибки, выявленные при проверке 32-битной конфигурации проекта, то нельзя считать код полностью совместимым с 64-битными системами. Необходимо обязательно выполнять окончательную проверку в 64-битной конфигурации проекта.

Сообщение V004 выдается только один раз на каждый проект, который проверяется в режиме 32-битной конфигурации. Предупреждение относится к файлу, который будет проанализирован первым при проверке проекта. Это сделано для того, чтобы не выводить множество однотипных предупреждений в отчет.

V005. Cannot determine active configuration for project. Please check projects and solution configurations.

Данная проблема в работе PVS-Studio вызвана несоответствием для рассматриваемого проекта платформенных конфигураций заданных в solution файле и платформенных конфигураций в самом файле данного проекта. В частности, в solution файле рассматриваемого проекта могут присутствовать строки следующего вида:

{F56ECFEC-45F9-4485-8A1B-6269E0D27E49}.Release|x64.ActiveCfg = Release|x64

Однако в самом файле проекта может отсутствовать упоминание конфигурации Release|x64. Поэтому, при попытке проверки данного проекта PVS-Studio не может найти конфигурацию Release|x64. В такой ситуации ожидается, что среда Visual Studio должна добавить в автоматически сгенерированный solution-файл строку следующего вида:

{F56ECFEC-45F9-4485-8A1B-6269E0D27E49}.Release|x64.ActiveCfg = Release|Win32

В данном автоматически сгенерированном файле активной конфигурации solution файла (Release|x64.ActiveCfg) для рассматриваемого проекта приравнивается одна из существующих конфигураций данного проекта (т.е. в данном случае Release|Win32). Подобная ситуация является ожидаемой и отрабатывается PVS-Studio корректно.

V006. File cannot be processed. Analysis aborted by timeout.

Сообщение V006 возникает, когда процесс анализатора не может обработать какой-то файл за определенное время и завершается аварийно. Это может возникать в двух случаях.

Первая возможная причина – это ошибка в анализаторе, при которой не удается разобрать тот или иной фрагмент кода. Такое случается достаточно редко, но, тем не менее, возможно. Хотя сообщение V006 возникает достаточно редко, мы будем вам благодарны, если вы поможете нам исправить проблему, которая привела к этому сообщению. Для этого пришлите, пожалуйста, препроцессированный i-файл, на котором возникла проблема, на адрес support@viva64.com.

Примечание. Препроцессированный i-файл получается из исходного файла (например, file.cpp) после работы препроцессора. Для того, чтобы получить такой файл, нужно на вкладке "Common Analyzer Settings" настроек PVS-Studio установить опцию RemoveIntermediateFiles в False и заново выполнить анализ одного этого файла. После этого в папке проекта надо найти соответствующий i-файл (например, file.i).

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

Решением может служить явное указание, что использовать нужно не все ядра. Количество используемых при анализе ядер можно задать в настройках PVS-Studio (опция ThreadCount на вкладке "Common Analyzer Settings").

V007. Verification of CLR projects is not implemented. Incorrect diagnostics are possible.

Сообщение V007 возникает, когда для анализа выбираются проекты, использующие спецификацию Microsoft C++/Common Language Infrastructure (являющуюся заменой более не поддерживаемого Microsoft расширения языка Managed Extensions for C++). Спецификация C++/CLI, в отличие от C++ Managed Extensions, может рассматриваться как самостоятельный и независимый от C++ язык со своим синтаксисом и набором ключевых слов. Поэтому при анализе C++/CLI проектов PVS-Studio может не обнаружить ошибки в файлах, содержащих специфичный для CLI синтаксис. Если отдельные файлы проекта не содержат такого синтаксиса, их анализ и нахождение в них ошибок возможно, однако PVS-Studio официально не поддерживает такой режим работы и не гарантирует проверку всех файлов, включённых в C++/CLI проекты.

V008. Unable to start the analysis on this file.

PVS-Studio не смог запустить анализ указанного файла. Данное сообщение означает, что внешний C++ препроцессор, запущенный анализатором для создания препроцессированного файла с исходным кодом, завершил свою работу с ненулевым выходным кодом ошибки. При этом std error зачастую будет содержать подробное описание произошедшей ошибки, которое также можно увидеть в окне PVS-Studio Output для данного файла.

Ошибка V008 может возникнуть по следующим причинам:

1) Код не компилируется

Если исходный C++ код по какой то причине не компилируется (например, не найден включаемый заголовочный файл), препроцессор завершает свою работу с ненулевым кодом и выдаёт в std error сообщение об ошибке вида "fatal compilation error". PVS-Studio не сможет начать анализ в случае, если C++ файл не был успешно препроцессирован. Для устранения ошибки необходимо обеспечить компилируемость проверяемого файла.

2.)Повреждён\заблокирован исполняемый файл препроцессора

Такая ситуация возможна в случаях, когда исполняемый файл препроцессора повреждён или заблокирован системным антивирусом. Окно вывода PVS-Studio может также содержать сообщения вида " The system cannot execute the specified program ". Для устранения ошибки необходимо убедиться в целостности исполняемого файла используемого препроцессора и смягчить политики безопасности системного антивирусного ПО.

3.)Заблокирован один из вспомогательных командных файлов PVS-Studio

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

V009. Intel C++ project toolset is not supported. Select Visual C++ toolset to verify this project.

Данное сообщение означает, что PVS-Studio определил присутствие интеграции компилятора Intel C++ в указанный проект Visual C++ в заданной активной сборочной конфигурации. Анализатор PVS-Studio поддерживает проверку кода только в проектах, предназначенных для компилятора Microsoft Visual C++ (cl.exe).

Хотя PVS-Studio не сможет осуществить проверку тех сборочных конфигураций, в которых используется компилятор Intel C++, проверить рассматриваемый проект можно, изменив значение поля platform toolset в настройках проекта (Properties>General>Platform Toolset) на одно из поддерживаемых анализатором PVS-Studio. На данный момент анализатор поддерживает следующие наборы платформенных инструментариев:

  • Компилятор Microsoft Visual С++ 8.0 (v80, устанавливается с MSVS 2005)
  • Компилятор Microsoft Visual С++ 9.0 (v90, устанавливается с MSVS 2008)
  • Компилятор Microsoft Visual С++ 10.0 (v100, значение по умолчанию для MSVS 2010)

Ошибка V009 относится только к проектам среды Visual Studio 2010 (расширение vcxproj), поскольку более ранний формат проектов Visual C++ (vcproj) не поддерживает работу со сторонними (т.е. отличными от Visual C++ 10.0) компиляторами через прямое задание инструментариев платформ (platform toolsets) в своих настройках.

В версиях Microsoft Visual Studio 2005 и 2008 компилятор Intel C++ интегрируется в сборочный процесс среды разработки путём добавления в активный solution собственных проектов отличного от стандартного vcproj типа. Проверка таких модифицированных Intel C++ проектов анализатором PVS-Studio не поддерживается.

V101. Implicit assignment type conversion to memsize type.

Анализатор обнаружил потенциально возможную ошибку, связанную с неявным приведением типа при выполнении оператора присваивания "=". Ошибка может заключаться в некорректном вычислении значения выражения, стоящего справа от оператора присваивания "=". Пример кода, вызывающего предупреждение:

size_t a;
unsigned b;
...
a = b; // V101

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

Первой причиной возникновения ошибки на 64-битной системе может служить изменение процесса вычисления выражений. Рассмотрим пример:

unsigned a = 10;
int b = -11;
ptrdifft c = a + b; //V101
cout << c << endl;

Данный код на 32-битной системе распечатает значение -1, а на 64-битной системе 4294967295. Данное поведение полностью согласуется с правилами приведения типов в языке Си++, но скорее всего приведет к ошибке в реальном коде.

Поясним приведенный пример. Согласно правилам языка Си++ выражение a+b имеет тип unsigned и содержит значение 0xFFFFFFFFu. На 32-битной системе тип ptrdiff_t представляет собой знаковый 32-битный тип. После присвоения 32-битной знаковой переменной значения 0xFFFFFFFFu она будет содержать значение -1. На 64-битной системе тип ptrdiff_t представляет собой знаковый 64-битный тип. Это означает, что число 0xFFFFFFFFu будет представлено как оно есть. То есть значение переменной после присваивания будет равняться 4294967295.

Ошибку можно исправить, исключив смешанное использования memsize и не memsize-типов в одном выражении. Пример исправления кода:

size_t a = 10;
ptrdiff_t b = -11;
ptrdiff_t c = a + b;
cout << c << endl;

Еще более корректным способом исправления будет отказ от смешенного использования знаковых и беззнаковых типов данных.

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

unsigned Width  = 1800;
unsigned Height = 1800;
unsigned Depth  = 1800;

Предположим, что на 64-битной системе мы решили обрабатывать массивы данных более 4 гигабайт. В этом случае показанный код приведет к выделению ошибочного объема памяти. Программист планирует выделить 5832000000 байт оперативной памяти, а вместо этого получит в свое распоряжение только 1537032704. Это происходит из-за переполнения при вычислении выражения Width * Height * Depth. К сожалению мы не можем диагностировать ошибку в строке с этим выражением, но мы можем косвенно указать на наличие ошибки, обнаружив приведение типа в строке:

size_t ArraySize = CellCount * sizeof(char); //V101

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

size_t CellCount = Width * Height * Depth;

Здесь мы по-прежнему имеем переполнение. Приведем два примера корректного исправления кода:

// 1)

unsigned Width  = 1800;
unsigned Height = 1800;
unsigned Depth  = 1800;
unsigned CellCount =
  static_cast<size_t>(Width) *
  static_cast<size_t>(Height) *
  static_cast<size_t>(Depth);

// 2)

size_t Width  = 1800;
size_t Height = 1800;
size_t Depth  = 1800;
size_t CellCount = Width * Height * Depth;

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

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

extern unsigned ArrayWidth;
unsigned CalcIndex(unsigned x, unsigned y) {
  return x + y * ArrayWidth;
}
   ...
const size_t index = CalcIndex(x, y); //V101

Анализатор сообщит о проблеме в строке: const size_t index = CalcIndex(x, y). Но ошибка заключается в неправильной реализации функции CalcIndex. Если взять функцию CalcIndex отдельно, то она абсолютно корректна. Типом выходного и входных значений является unsigned. Вычисления также происходят с участием только типов unsigned. Никаких явных или неявных приведений типов нет, и анализатор не имеет возможности распознать логическую проблему, связанную с функцией CalcIndex. Ошибка заключается в том, что неверно выбран результат, возвращаемый функцией, а возможно и входных значений. Результат функции должен иметь тип memsize.

К счастью анализатор смог обнаружить неявное приведение результата функции CalcIndex к типу size_t. Это позволяет вам проанализировать ситуацию и внести в программу необходимые изменения. Исправление ошибки, например, может выглядеть следующим образом:

extern size_t ArrayWidth;
size_t CalcIndex(size_t x, size_t y) {
  return x + y * ArrayWidth;
}
   ...
const size_t index = CalcIndex(x, y);

Если вы уверены, что код верен и размер массива никогда не приблизится к границе в 4Гб, вы можете подавить предупреждение анализатора, используя явное приведение типа:

extern unsigned ArrayWidth;
unsigned CalcIndex(unsigned x, unsigned y) {
  return x + y * ArrayWidth;
}
...
const size_t index = static_cast<size_t>(CalcIndex(x, y));

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

Рассмотрим последний пример, связанный с некорректными операциями сдвига.

ptrdiff_t SetBitN(ptrdiff_t value, unsigned bitNum) {
  ptrdiff_t mask = 1 << bitNum; //V101
  return value | mask;
}

Выражение " mask = 1 << bitNum " является опасным, так как данный код не может выставить в единицы старшие разряды 64-битной переменной mask. Если попробовать использовать функцию SetBitN для выставления, например, 33-го бита, то произойдет переполнение при выполнении операции сдвига и желаемый результат не будет достигнут.

V102. Usage of non memsize type for pointer arithmetic.

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

Рассмотрим пример.

short a16, b16, c16;
char *pointer;
...
pointer += a16 * b16 * c16;

Данный пример корректно работает с указателями, если значение выражения a16 * b16 * c16 не превышает INT_MAX (2Гб). Такой код мог всегда корректно работать на 32-битной платформе, в силу того, что программа никогда не выделяла массивов больших размеров. На 64-битной архитектуре программиста, использующего старый код для работы с массивом большего размера, ждет разочарование. Допустим, мы хотим сдвинуть значение указателя на 3000000000 байт, и по этому переменные a16, b16 and c16 имеют значения 3000, 1000 и 1000 соответственно. При вычислении выражения a16 * b16 * c16 все переменные, согласно правилам языка Си++, будут приведены к типу int, а уже затем будет произведено их умножение. В ходе выполнения умножения, произойдет переполнение, в результате которого будет получено число -1294967296. Некорректный результат выражения будет расширен до типа ptrdiff_tи произойдет вычисление указателя. В результате нас будет ожидать аварийное завершение программы, при попытке использования некорректного указателя.

Для предотвращения подобных ошибок следует использовать memsize типы. В нашем случае будет корректно заменить типы переменных a16, b16, c16 или использовать их явное приведение к типу ptrdiff_t, как показано ниже:

short a16, b16, c16;
char *pointer;
...
pointer += static_cast<ptrdiff_t>(a16) *
      static_cast<ptrdiff_t>(b16) *
      static_cast<ptrdiff_t>(c16)

Хочется отметить, что не всегда использование в арифметике с указателями не memsize типов является ошибочным. Рассмотрим следующую ситуацию:

char ch;
short a16;
int *pointer;
...
int *decodePtr = pointer + ch * a16;

Анализатор не выдает на нее сообщения, в силу ее корректности. Здесь не происходит вычислений, которые могли бы привести к переполнению и результат этого выражения всегда будет корректен как на 32-битной, так и на 64-битной платформе.

V103. Implicit type conversion from memsize type to 32-bit type.

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

Первый пример.

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

size_t Width, Height, FrameCount;
...
unsigned BufferSizeForWrite = Width * Height * FrameCount * sizeof(RGBStruct);

Раньше общий объем видеокадров в памяти никогда не мог превышать 4 Гб (на практике 2-3 Гб, в зависимости от разновидности ОС Windows). На 64-битной платформе мы получили возможность хранить в памяти намного больше кадров, и предположим, что их общий объем составляет 10 Гб. В результате при помещении результата выражения Width * Height * FrameCount * sizeof(RGBStruct) в переменную BufferSizeForWrite, мы отбросим старшие биты и будем работать с некорректным значением.

Правильным решением будет замена типа переменной BufferSizeForWrite на тип size_t.

size_t Width, Height, FrameCount;
...
size_t  BufferSizeForWrite = Width * Height * FrameCount * sizeof(RGBStruct);

Второй пример.

Сохранение в 32-битном типе результата вычитания одного указателя из другого.

char *ptr_1, *ptr_2;
...
int diff = ptr_2 -  ptr_1;

Если указатели различаются более чем на INT_MAX байт (2 Гб), то произойдет обрезание значения при присваивании. В результате переменная diff будет иметь некорректное значение. Для хранения полученного значения следует использовать тип ptrdiff_t или другой memsize тип.

char *ptr_1, *ptr_2;
...
ptrdiff_t diff = ptr_2 -  ptr_1;

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

unsigned BitCount = static_cast<unsigned>(sizeof(RGBStruct) * 8);

В том случае, если вы подозреваете наличие в своем коде некорректных явных приведений memsize типов к 32-битным типам, на которые анализатор не выдает предупреждения, то вы можете воспользоваться правилом V202.

Как было сказано ранее, анализатор информирует только о тех приведениях типов, которые могут быть причиной некорректной работы кода на 64-битной платформе. Приведенный ниже код не будет диагностироваться как ошибочный, хотя в нем происходит приведение memsize типов к типу int:

int size = sizeof(float);

V104. Implicit type conversion to memsize type in an arithmetic expression.

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

Первый пример.

Некорректные выражения сравнения. Рассмотрим код:

size_t n;
unsigned i;
// Infinite loop (n > UINT_MAX).
for (i = 0; i != n; ++i) { ... }

В примере показана ошибка, связанная с неявным приведением типа unsigned к типу size_t при выполнении операции сравнения.

На 64-битной платформе у вас может появиться возможность обрабатывать больший объем данных, и значение переменной n может превысить число UINT_MAX (4 Гб). В результате, условие i != n всегда будет истинно, что приведет к вечному циклу.

Пример исправленного кода:

size_t n;
size_t  i;
for (i = 0; i != n; ++i) { ... }

Второй пример.

char *begin, *end;
int bufLen, bufCount;
...
ptrdiff_t diff = begin - end + bufLen * bufCount;

Неявное приведение типа int к типу ptrdiff_t зачастую служит признаком ошибки. Следует обратить внимание, что приведение происходит не при выполнении оператора "=" (так как выражение begin - end + bufLen * bufCount имеет тип ptrdiff_t), а внутри этого выражения. Подвыражение begin - end согласно правилам языка С++ имеет тип ptrdiff_t, а правая bufLen * bufCount тип int. При переходе на 64-битную платформу программа может начать обрабатывать больший объем данных, в результате чего может произойти переполнение при вычислении подвыражения bufLen * bufCount.

Следует изменить тип переменных bufLen и bufCount на memsize тип или использовать явное приведение типа, как показано на примере:

char *begin, *end;
int bufLen, bufCount;
...
ptrdiff_t diff = begin - end + ptrdiff_t(bufLen) * ptrdiff_t(bufCount);

Отметим, что неявное приведение к типу memsize внутри выражений не всегда является ошибочным. Рассмотрим следующую ситуацию:

size_t value;
char c1, c2;
size_t result = value + c1 * c2;

Анализатор не выдает сообщения, хотя здесь происходит приведение типа int к size_t, так как при вычислении подвыражения c1 * c2 переполнения произойти не может.

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

int i;
size_t st;
...
st = size_t(i * i * i) * st;

V105. N operand of '?:' operation: implicit type conversion to memsize type.

Анализатор обнаружил потенциально возможную ошибку внутри арифметического выражения, связанную с неявным приведением к типу memsize. Ошибка переполнения может быть связана с изменением допустимого интервала значений переменных, входящих в выражение. Данное предупреждение практически эквивалентно предупреждению V104, за тем исключением, что неявное приведение типа возникает за счет использования операции ?:.

Приведем пример неявного приведения типов при использовании операции:

int i32;
float f = b != 1 ? sizeof(int) : i32;

В арифметическом выражении используется тернарная операция ?:, имеющая три операнда:

  • b != 1 - первый операнд;
  • sizeof(int) - второй операнд;
  • i32 - третий операнд.

Результатом выражения b != 1 ? sizeof(int) : i32 является значение типа size_t, которое затем преобразуется в значение типа float. Таким образом, неявное приведение типа осуществляется для 3-его операнда операции ?:.

Рассмотрим пример некорректного кода:

bool useDefaultVolume;
size_t defaultVolume;
unsigned width, height, depth;
...
size_t volume = useDefaultVolume ?
                        defaultVolume :
                        width * height * depth;

Предположим, мы разрабатываем приложение численного моделирования, для которого требуется трехмерная счетная область. Количество используемых счетных элементов определяется в зависимости от значения переменной useDefaultSizeи задается по умолчанию или произведением длинны, высоты и глубины счетной области. На 32-битной платформе объем уже выделенной памяти не может превышать 2-3 Гб (в зависимости от версии ОС Windows) и, соответственно, результат выражения width * height * depth всегда будет корректен. На 64-битной платформе, пользуясь возможностью работать с большим объемом памяти, количество счетных элементов может превысить значение UINT_MAX (4 Гб). В этом случае произойдет переполнение при вычислении выражения width * height * depth, так как результат этого выражения имеет тип unsigned.

Исправление кода может заключаться в изменении типа переменных width, height и depth на memsize тип, как показано ниже:

...
size_t width, height, depth;
...
size_t volume = useDefaultVolume ?
                        defaultVolume :
                        width * height * depth;

Или использовании явного приведения типов:

unsigned width, height, depth;
...
size_t volume = useDefaultVolume ?
                        defaultVolume :
         size_t(width) * size_t(height) * size_t(depth);

Дополнительно мы рекомендуем изучить описание аналогичного предупреждения V104, где можно ознакомиться с другими эффектами неявного приведения к типу memsize.

V106. Implicit type conversion N argument of function 'foo' to memsize type.

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

Первый пример.

Программа работает с большими массивами, используя контейнер CArray из библиотеки MFC. На 64-битной платформе количество элементов массиве может превысить значение MAX_INT (2Гб), что приведет к неработоспособности следующего кода:

CArray<int, int> myArray;
...
int invalidIndex = 0;
INT_PTR validIndex = 0;
while (validIndex != myArray.GetSize()) {
  myArray.SetAt(invalidIndex, 123);
  ++invalidIndex;
  ++validIndex;
}

Данный код заполняет все элементы массива myArray значением 123. Он выглядит совершенно корректно, и вы не получите от компилятора никаких предупреждений, несмотря на его неработоспособность на 64-битной архитектуре. Ошибка заключается в использовании в качестве индекса переменной invalidIndex типа int. Когда значение переменной invalidIndex превысит MAX_INT, произойдет ее переполнение, и оно получит значение равное "-1". Анализатор диагностирует данную ошибку, предупреждая, что первый аргумент функции SetAt неявно приводится к memsize типу (которым в данном случае является тип INT_PTR). Получив такое предупреждение, вы можете исправить ошибку, изменив тип "int" на более подходящий.

Данный пример показателен тем, что обвинять программиста в некачественном коде не очень честно. Дело в том, что в старой версии библиотеки MFC функция SetAt в классе CArray была объявлена следующим образом:

void SetAt(int nIndex, ARG_TYPE newElement);

А в новой:

void SetAt(INT_PTR nIndex, ARG_TYPE newElement);

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

Приведем исправленный пример:

...
INT_PTR  invalidIndex = 0;
INT_PTR validIndex = 0;
while (validIndex != myArray.GetSize()) {
  myArray.SetAt(invalidIndex, 123);
  ++invalidIndex;
  ++validIndex
}

Второй пример.

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

unsigned GetArraySize();
...
unsigned size = GetArraySize();
void *p = malloc(size);

Анализатор выдаст предупреждение на строку void *p = malloc(size);. Посмотрев определение функции malloc, мы увидим, что ее формальный аргумент, задающий размер выделяемой памяти, представлен типом size_t. В программе же в качестве фактического аргумента используется переменная size типа unsigned. Если вашей программе на 64-битной архитектуре понадобится массив более UINT_MAX байт (4Гб), то можно с уверенностью утверждать, что приведенный код неверен, так как тип unsigned не может хранить значение более UINT_MAX. Исправление программы заключается в изменении типов переменных и функций, участвующих в вычислении размера массива данных. В приведенном примере необходимо заменить тип unsigned на один из memsize типов, а также, если это необходимо модифицировать код функции GetArraySize.

...
size_t  GetArraySize();
...
size_t size = GetArraySize();
void *p = malloc(size);

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

void MyFoo(SSIZE_T index);
...
char c = 'z';
MyFoo(0);
MyFoo(c);

Если вы точно уверены, что неявное приведение типа фактического аргумента функции совершенно корректно, то для подавления предупреждения анализатора вы можете использовать явное приведение типа, как показано ниже:

typedef size_t TYear;
void MyFoo(TYear year);
int year;
...
MyFoo(static_cast<TYear>(year));

Иногда явное приведение типа может маскировать ошибку. В этом случае вы можете воспользоваться правилом V201.

V107. Implicit type conversion N argument of function 'foo' to 32-bit type.

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

Рассмотрим пример кода, в котором реализована функция поиска максимального элемента в массиве:

float FindMaxItem(float *array, int arraySize) {
  float max = -FLT_MAX;
  for (int i = 0; i != arraySize; ++i) {
    float item = *array++;
    if (max < item)
      max = item;
  }
  return max;
}
...
float *beginArray;
float *endArray;
float maxValue = FindMaxItem(beginArray, endArray - beginArray);

Данный код успешно может работать на 32-битной платформе, но не сможет обрабатывать массивы, состоящие более чем из INT_MAX (2Гб) элементов на 64-битной архитектуре. Это ограничение вызвано использованием типа int для аргумента arraySize. Обратите внимание, что код функции выглядит совершенно корректно не только с точки зрения компилятора, но и анализатора. В этой функции отсутствует приведение типов, и невозможно выявить потенциальную проблему.

Анализатор предупредит о неявном приведении memsize типа к 32-битному типу при вызове функции FindMaxItem. Попробуем разобраться, почему это происходит. Согласно правилам языка Си++, результат вычитания одного указателя из другого имеет тип ptrdiff_t. При вызове функции FindMaxItem произойдет неявное приведение типа ptrdiff_t к типу int, в результате чего произойдет потеря старших битов. Это может стать причиной некорректного поведения программы, при обработке большого объема данных.

Правильным решением будет заменить тип int на тип ptrdiff_t, так как он позволит хранить весь диапазон допустимых значений. Исправленный код:

float FindMaxItem(float *array, ptrdiff_t  arraySize) {
  float max = -FLT_MAX;
  for (ptrdiff_t  i = 0; i != arraySize; ++i) {
    float item = *array++;
    if (max < item)
      max = item;
  }
  return max;
}

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

float Arr[1000];
float maxValue =
   FindMaxItem(Arr, sizeof(Arr)/sizeof(float));

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

extern int nPenStyle
extern size_t nWidth;
extern COLORREF crColor;
...
// Call constructor CPen::CPen(int, int, COLORREF)
CPen myPen(nPenStyle, static_cast<int>(nWidth), crColor);

В том случае, если вы подозреваете наличие в своем коде некорректных явных приведений memsize типов к 32-битным типам, на которые анализатор не выдает предупреждения, то вы можете воспользоваться правилом V202.

V108. Incorrect index type: 'foo[not a memsize-type]'. Use memsize type instead.

Анализатор обнаружил потенциально возможную ошибку индексации больших массивов. Ошибка может заключаться в некорректном вычислении индекса.

Первый пример.

extern char *longString;
extern bool *isAlnum;
...
unsigned i = 0;
while (*longString) {
  isAlnum[i] = isalnum(*longString++);
  ++i;
}

Данный код совершенно корректен для 32-битной платформы, где принципиально невозможна обработка массивов более UINT_MAX байт (4 Гб). На 64-битной платформе можно обработать массив размером более 4 Гб, что иногда очень удобно. Но корректно обработать большой массив приведенным алгоритмом невозможно. Ошибка заключается в использовании для индексации массива isAlnum переменной типа unsigned. Когда мы заполним первые UINT_MAX элементов, произойдет переполнение переменной i, и она приравняется нулю. В результате мы начнем перезаписывать элементы массива isAlnum расположенные в начале, а часть элементов оставим неинициализированными.

Корректным исправлением является изменения типа переменной i на memsize тип:

...
size_t  i = 0;
while (*longString)
  isAlnum[i++] = isalnum(*longString++);

Второй пример.

class Region {
  float *array;
  int Width, Height, Depth;
  float Region::GetCell(int x, int y, int z) const;
  ...
};
float Region::GetCell(int x, int y, int z) const {
  return array[x + y * Width + z * Width * Height];
}

Для программ численного моделирования важным ресурсом является объем оперативной памяти, и возможность на 64-битной архитектуре использовать более 4 Гб памяти существенно увеличивает вычислительные возможности. В таких программах часто используют одномерные массивы, работая затем с ними как с трехмерными. Для этого существуют функции, аналогичные GetCell, обеспечивающие доступ к необходимым элементам счетной области. Но приведенный код может корректно работать с массивами, содержащими не более INT_MAX (2 Гб) элементов. Причина заключается в использовании 32-битных типов int, участвующих в вычислении индекса элемента. Если количество элементов в массиве array превысит INT_MAX (2 Гб), то произойдет переполнение и значение индекса будет вычислено некорректно. Программисты часто допускают ошибку, пытаясь исправить код следующим образом:

float Region::GetCell(int x, int y, int z) const {
  return array[static_cast<ptrdiff_t>(x) + y * Width +
                    z * Width * Height];
}

Они знают, что по правилам языка Си++ выражение для вычисления индекса будет иметь тип ptrdiff_t и надеются за счет этого избежать переполнения. К сожалению, переполнение может произойти внутри подвыражения y * Width или z * Width * Height,так как для их вычисления по-прежнему используется тип int.

Если вы хотите исправить код, не изменяя типов переменных, участвующих в выражении, то вы должны явно привести каждую переменную к memsize типу:

float Region::GetCell(int x, int y, int z) const {
  return array[ptrdiff_t(x) +
 ptrdiff_t(y) * ptrdiff_t(Width) +
 ptrdiff_t(z) * ptrdiff_t(Width) *
 ptrdiff_t(Height)];
}

Другое решение - изменить типы переменных на memsize тип:

class Region {
  float *array;
  ptrdiff_t Width, Height, Depth;
  float
    Region::GetCell(ptrdiff_t x, ptrdiff_t y, ptrdiff_t z) const;
  ...
};
float Region::GetCell(ptrdiff_t x, ptrdiff_t y, ptrdiff_t z) const
{
  return array[x + y * Width + z * Width * Height];
}

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

bool *Seconds;
int min, sec;
...
bool flag = Seconds[static_cast<size_t>(min * 60 + sec)];

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

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

char Arr[] = { '0', '1', '2', '3', '4' };
char *p = Arr + 2;
cout << p[0u + 1] << endl;
cout << p[0u - 1] << endl; //V108

Данный код исправно работает в 32-битном режиме и печатает на экране числа 3 и 1. При проверке этого кода мы получим предупреждение только на одну строку с выражением "p[0u - 1]". И это совершенно верно. Если вы скомпилируете и запустите данный пример в 64-битном режиме, то увидите, как на экране будет распечатано значение 3, после чего произойдет аварийное завершений программы.

Ошибка связана с тем, что индексация "p[0u - 1]" на 64-битной системе некорректна, о чем и предупреждает анализатор. Согласно правилам языка Си++ выражение "0u - 1" будет иметь тип unsigned и равняться 0xFFFFFFFFu. На 32-битной архитектуре сложение указателя с этим числом будет эквивалентно вычитанию единицы. А на 64-битной системе к указателю будет честно прибавлено 0xFFFFFFFFu и произойдет обращение к памяти за приделами массива.

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

Если вы уверены в корректности индексации, то вы можете воспользоваться функцией "Suppression of false alarms" или использовать фильтры. Можно использовать явное приведение типов в коде:

for (int i = 0; i != n; ++i)
  Array[static_cast<ptrdiff_t>(i)] = 0;

V109. Implicit type conversion of return value to memsize type.

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

Рассмотрим пример.

extern int Width, Height, Depth;
size_t GetIndex(int x, int y, int z) {
  return x + y * Width + z * Width * Height;
}
...
array[GetIndex(x, y, z)] = 0.0f;

В случае работы с большими массивами (более INT_MAX элементов) данный код будет вести себя некорректно, и мы будет адресоваться не к тем элементам массива array, к которым рассчитываем. Но анализатор не выдаст предупреждение на строчку array[GetIndex(x, y, z)] = 0.0f;,так как она совершенно корректна. Анализатор информирует о потенциальной ошибке внутри функции и совершенно прав, так как ошибка находится именно там и связана с арифметическим переполнением. Несмотря на то, что мы возвращаем значение типа size_t, выражение x + y * Width + z * Width * Height вычисляется с использованием типа int.

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

extern int Width, Height, Depth;
size_t GetIndex(int x, int y, int z) {
  return (size_t)(x) +
(size_t)(y) * (size_t)(Width) +
(size_t)(z) * (size_t)(Width) * (size_t)(Height);
}

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

extern size_t Width, Height, Depth;
size_t GetIndex(size_t x, size_t y, size_t z) {
  return x + y * Width + z * Width * Height;
}

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

DWORD_PTR Calc(unsigned a) {
  return (DWORD_PTR)(10 * a);
}

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

V110. Implicit type conversion of return value from memsize type to 32-bit type.

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

Рассмотрим пример.

extern char *begin, *end;
unsigned GetSize() {
  return end - begin;
}

Результат выражения end - begin имеет тип ptrdiff_t. Но поскольку функция возвращает тип unsigned, то происходит неявное приведение типа, при котором старшие биты результата теряются. Таким образом, если указатели begin и end ссылаются на начало и конец массива, по размеру большего UINT_MAX (4 Гб), то функция вернет некорректное значение.

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

extern char *begin, *end;
size_t  GetSize() {
  return end - begin;
}

В ряде случаев анализатор не выдает предупреждение на приведение типа, если оно явно корректно. Например, анализатор не выдаст предупреждение на следующий код, где результатом оператора sizeof() хотя и является тип size_t, но результат безопасно может быть помещен в тип unsigned:

unsigned GetSize() {
  return sizeof(double);
}

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

unsigned GetBitCount() {
  return static_cast<unsigned>(sizeof(TypeRGBA) * 8);
}

Если вы подозреваете наличие в своем коде некорректных явных приведений типов возвращаемых значений, на которые анализатор не выдает предупреждения, то вы можете воспользоваться правилом V202.

V111. Call of function 'foo' with variable number of arguments. N argument has memsize type.

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

Рассмотрим первый пример.

const char *invalidFormat = "%u";
size_t value = SIZE_MAX;
printf(invalidFormat, value);

Данный код не учитывает, что тип size_t не эквивалентен типу unsigned на 64-битной платформе. Это приведет к выводу на печать некорректного результата, в случае если value > UINT_MAX. Анализатор предупреждает вас, что в качестве фактического аргумента используется тип memsize. А это значит, что вам следует проверить строку invalidFormat, задающую формат вывода. Исправленный вариант может выглядеть, как показано ниже.

const char *validFormat = "%Iu";
size_t value = SIZE_MAX;
printf(validFormat, value);

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

wsprintf(szDebugMessage,
          _T("%s location %08x caused an access violation.\r\n"),
         readwrite,
         Exception->m_pAddr);

Второй пример.

char buf[9];
sprintf(buf, "%p", pointer);

Автор этого неаккуратного кода не учел, что размер указателя в будущем может стать более 32 бит. В результате данный код на 64-битной архитектуре приведет к переполнению буфера. Проанализировав код, на который выдано предупреждение V111, вы можете пойти двумя путями. Увеличить размер буфера, или переписать код с использованием безопасных конструкций.

char buf[sizeof(pointer) + 1];
sprintf(buf, "%p", pointer);
// --- or ---
std::stringstream s;
s << pointer;

Третий пример.

char buf[9];
sprintf_s(buf, sizeof(buf), "%p", pointer);

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

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

printf("%d", 10*5);
CString str;
size_t n = sizeof(float);
str.Format(StrFormat, static_cast<int>(n));

Диагностируя описанный тип ошибок, к сожалению, часто нет возможности отличить корректный код от не корректного кода. Данное предупреждение будет выдаться на многие вызовы функций с переменным количеством аргументов, даже когда вызов совершенно верен. Это связано с принципиальной опасностью использования таких конструкций языка С++. Чаше всего проблемы возникают с использованием разновидности следующих функций: printf, scanf, CString::Format. Общепринятой практикой является отказ от них и использование безопасных методик программирования. Мы настоятельно рекомендуем модифицировать код и использовать безопасные методы. Например, можно заменить printf на cout, а sprintf на boost::format или std::stringstream.

V112. Dangerous magic number N used.

Анализатор обнаружил использование опасной магической константы. Потенциальная ошибка может заключаться в использовании числовой константы в качестве специальных значений или размера memsize типа.

Рассмотрим первый пример.

size_t ArraySize = N * 4;
size_t *Array = (size_t *)malloc(ArraySize);

Программист при написании программы полагался на то, что размер size_t всегда будет равен 4 и записал вычисление размера массива как N * 4. Данный код не учитывает, что тип size_t на 64-битной системе будет занимать 8 байт и выделит меньшее количество памяти, чем необходимо. Исправление кода заключается в использовании оператора sizeof вместо константы 4.

size_t ArraySize = N * sizeof(size_t);
size_t *Array = (size_t *)malloc(ArraySize);

Второй пример.

size_t n = static_cast<size_t>(-1);
if (n == 0xffffffffu) { ... }

Иногда в качестве кода ошибки или другого специального маркера используют значение "-1", записывая его как "0xffffffff". На 64-битной платформе записанное выражение некорректно, и следует явно использовать значение "-1".

size_t n = static_cast<size_t>(-1);
if (n == static_cast<size_t>(-1)) { ... }

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

Picture 1

Следует внимательно изучить код на наличия магических констант и заменить их безопасными константами и выражениями. Для этого можно использовать оператор sizeof(), специальные значения из <limits.h>, <inttypes.h> и так далее.

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

float Color[4];

V113. Implicit type conversion from memsize to double type or vice versa.

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

Рассмотрим пример.

SIZE_T size = SIZE_MAX;
double tmp = size;
size = tmp; // x86: size == SIZE_MAX
            // x64: size != SIZE_MAX

Тип double имеет размер 64-бита, и совместим со стандартом IEEE-754 на 32-битных и 64-битных системах. Некоторые программисты используют тип double для хранения и работы с целочисленными типами.

Приведенный пример еще можно пытаться оправдывать на 32-битной системе, так как тип double имеет 52 значащих бит и способен без потери хранить 32-битное целое значение. Но при попытке сохранить в переменной типа double 64-битное целое число точное значение может быть потеряно (см. рисунок).

Picture 1068818

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

V114. Dangerous explicit type pointer conversion.

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

Рассмотрим пример. В нем присутствует явное приведение указателя на int к указателю на size_t.

int array[4] = { 1, 2, 3, 4 };
size_t *sizetPtr = (size_t *)(array);
cout << sizetPtr[1] << endl;

Как видите, результат вывода программы отличается в 32-битном и 64-битном варианте. На 32-битной системе доступ к элементам массива осуществляется корректно, так как размеры типов size_t и int совпадают, и мы видим вывод "2". На 64-битной системы мы получили в выводе "17179869187", так как именно значение 17179869187 находится в 1-ом элементе массива sizetPtr.

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

Естественно, не все явные приведения типов указателей являются опасными. В показанном далее примере результат работы не зависит он разрядности системы, так как типы enum и int имеют одинаковый размер как на 32-битной, так и на 64-битной системе. Соответственно анализатор не выдаст на данный код никаких сообщений.

int array[4] = { 1, 2, 3, 4 };
enum ENumbers { ZERO, ONE, TWO, THREE, FOUR };
ENumbers *enumPtr = (ENumbers *)(array);
cout << enumPtr[1] << endl;

V115. Memsize type is used for throw.

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

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

char *ptr1, *ptr2;
...
try {
  throw ptr2 - ptr1;
}
catch(int) {
  Foo();
}

Ошибка в том, что на 64-битной системе обработчик исключения не сработает и функция Foo() не будет вызвана. Это связано с тем, что выражение ptr2 - ptr1 имеет тип ptrdiff_t, который на 64-битной системе не совпадает с типом int.

Исправление описанной ситуации заключается в использовании корректного типа для перехвата исключения. В данном случае - ptrdiff_t, как показано ниже.

try {
  throw ptr2 - ptr1;
}
catch(ptrdiff_t) {
  Foo();
}

Более корректное исправление будет состоять в отказе от подобной практики программирования. Следует использовать специальные классы для передачи информации о возникшей ошибке.

V116. Memsize type is used for catch.

Анализатор обнаружил потенциально возможную ошибку, связанную с использованием memsize типа для перехвата исключения. Ошибка может заключаться в некорректном перехвате и обработке исключений.

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

try {
    try {
      throw UINT64(-1);
    }
    catch(size_t) {
      cout << "x64 portability issues" << endl;
    }
  }
  catch(UINT64) {
      cout << "OK" << endl;
  }

Результат работы на 32-битной системе: OK
Результат работы на 64-битной системе: x64 portability issues

Изменение поведения связано с тем, что на 64-битной системе тип size_t начинает совпадать с UINT64.

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

Более корректное исправление будет состоять в отказе от подобной практики программирования. Следует использовать специальные классы для передачи информации о возникшей ошибке.

V117. Memsize type is used in the union.

Анализатор обнаружил потенциально возможную ошибку, связанную с использованием memsize внутри объединения (union). Ошибка может возникнуть при работе с такими объединениями без учета изменения размеров memsize типов на 64-битной системе.

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

Первый пример.

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

union PtrNumUnion {
  char *m_p;
  unsigned m_n;
} u;
...
u.m_p = str;
u.m_n += delta;

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

union PtrNumUnion {
  char *m_p;
  size_t m_n; //type fixed
} u;

Второй пример.

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

union SizetToBytesUnion {
  size_t value;
  struct {
    unsigned char b0, b1, b2, b3;
  } bytes;
} u;
   
SizetToBytesUnion u;
u.value = value;
size_t zeroBitsN = TranslateTable[u.bytes.b0] +
                   TranslateTable[u.bytes.b1] +
                   TranslateTable[u.bytes.b2] +
                   TranslateTable[u.bytes.b3];

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

union SizetToBytesUnion {
  size_t value;
  unsigned char bytes[sizeof(value)];
} u;
   
SizetToBytesUnion u;
u.value = value;
size_t zeroBitsN = 0;
for (size_t i = 0; i != sizeof(bytes); ++i)
  zeroBitsN += TranslateTable[bytes[i]];

Родственным диагностическим сообщением является V122.

V118. malloc() function accepts a dangerous expression in the capacity of an argument.

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

Анализатор считает подозрительными те выражения, в которых присутствуют константные литералы кратные четырем, но отсутствует оператор sizeof().

Пример первый.

Некорректный код выделения памяти для матрицы размером 3x3 из элементов типа size_t может выглядеть следующим образом:

size_t *pMatrix = (size_t *)malloc(36); // V118

Хотя такой код мог долгое время надежно работать в 32-битной системе, использование магического числа 36 некорректно. При компиляции 64-битной версии необходимо выделить уже 72 байта памяти. Исправление заключается в использовании оператора sizeof ():

size_t *pMatrix = (size_t *)malloc(9 * sizeof(size_t));

Второй пример.

Некорректен для 64-битной системы и следующий код, основанный на предположении, что размер структуры Item равен 12 байт:

struct Item {
  int m_a;
  int m_b;
  Item *m_pParent;
};
Item *items = (Item *)malloc(GetArraySize() * 12); // V118

Исправление также заключается в использовании оператора sizeof() для корректного вычисления размера структуры:

Item *items = (Item *)malloc(GetArraySize() * szieof(Item));

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

Наличие константы в выражении, являющегося параметром для функции malloc(), вовсе не означает, что на него всегда выдается предупреждение V118. Если в выражении участвует оператор sizeof(), то такая конструкция считается безопасной. Пример кода, считающийся анализатором безопасным:

int *items = (int *)malloc(sizeof(int) * 12);

V119. More than one sizeof() operator is used in one expression.

Анализатор обнаружил опасное арифметическое выражение, содержащее в себе несколько операторов sizeof(). Подобные выражения потенциально могут содержать ошибки, связанные с некорректным вычислением размеров структур без учета выравнивания полей.

Пример:

struct MyBigStruct {
  unsigned m_numberOfPointers;
  void *m_Pointers[1];
};
size_t n2 = 1000;
void *p;
p = malloc(sizeof(unsigned) + n2 * sizeof(void *));

Для расчета размеры структуры, которая будет содержать 1000 указателей используется на первый взгляд корректное арифметическое выражение. Размеры базовых типов определяются через операторы sizeof(). Это хорошо, но недостаточно, чтобы правильно вычислить необходимый объем памяти. Дополнительно необходимо учитывать выравнивание полей.

Приведенный пример будет корректно работать в 32-битном режиме, так как размер указателей и типа unsigned совпадает. Оба типа имеют размер 4 байта. Выравниваются указатели и тип unsigned также кратно 4 байтам. И размер необходимой памяти будет вычислен корректно.

В 64-битном коде размер указателя составляет 8 байт. Также указатели выравниваются в памяти по границе 8 байт. Это приводит к тому, что после переменной m_numberOfPointers будут располагаться 4 дополнительных байта для выравнивания указателей по границе 8 байт.

Для вычисления корректного размера необходимо использовать функцию offsetof:

p = malloc(offsetof(MyBigStruct, m_Pointers) +
           n * sizeof(void *));

Во многих случаях использование нескольких операторов sizeof() в рамках одного выражения корректно и анализатор игнорирует подобные конструкции. Пример безопасных выражений с несколькими операторами sizeof:

int MyArray[] = { 1, 2, 3 };
size_t MyArraySize =
  sizeof(MyArray) / sizeof(MyArray[0]);
assert(sizeof(unsigned) < sizeof(size_t));
size_t strLen = sizeof(String) - sizeof(TCHAR);

V120. Member operator[] of object 'foo' declared with 32-bit type argument, but called with memsize type argument.

Анализатор обнаружил потенциально возможную ошибку при работе с классами, в которых имеется operator[]. Классы с перегруженным operator[] обычно представляют собой разновидность массива и аргументом operator[] является индекс запрашиваемого элемента. Если operator[] имеет формальный аргумент 32-битного типа, а в качестве фактического аргумента используется memsize-тип, то это может свидетельствовать об ошибке. Рассмотрим пример, приводящий к сообщению V120:

class MyArray {
  int m_arr[10];
public:
  int &operator[](unsigned i) { return m_arr[i]; }
} Object;
size_t k = 1;
Object[k] = 44; //V120

Данный пример не содержит ошибки, но может свидетельствовать об архитектурной недоработке. Необходимо или работать с классом MyArray используя 32-битные индексы или модифицировать operator[], чтобы он принимал аргумент типа size_t. Второй вариант предпочтительней, так как использование memsize-типов не только делает программу более надежной, но и в ряде случаев позволяет компилятору построить более эффективный код.

Родственными диагностическими сообщениями являются V108 и V302.

V121. Implicit conversion of the type of 'new' operator's argument to size_t type.

Анализатор обнаружил потенциально возможную ошибку, связанную с вызовом оператора new. В качестве аргумента оператору new передается значение не memsize-типа. Оператор new принимает значения типа size_t и передача 32-битного фактического аргумента может свидетельствовать о наличии ошибки переполнения при вычислении объема выделяемой памяти. Рассмотрим пример.

unsigned a = 5;
unsigned b = 1024;
unsigned c = 1024;
unsigned d = 1024;
char *ptr = new char[a*b*c*d]; //V121

В данном случае происходит переполнение при вычислении выражения "a*b*c*d" и в результате программа выделит меньшее количество памяти, чем планировалось. Исправление заключается в использовании типа size_t:

size_t a = 5;
size_t b = 1024;
size_t c = 1024;
size_t d = 1024;
char *ptr = new char[a*b*c*d]; //Ok

Также ошибка будет обнаружена и в следующем случае:

#define x 1024
#define y 1024
#define z 1024
#ifdef _M_X64
#define n 5
#else
#define n 1
#endif
char *p = new char[x*y*z*n]; //V121

Данная ошибка диагностируется только при условии, если в настройках анализатора опция MoreThan2Gb выставлена в true. Также ошибка не будет диагностироваться, если значение задано безопасным 32-битным константным значением. Пример безопасного кода:

char *ptr = new char[100]; 
const int size = 3*3;
char *p2 = new char[size];

Родственным диагностическим сообщением являются V106.

V122. Memsize type is used in the struct/class.

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

Необходимость просмотреть все memsize-поля может возникнуть при переносе программы, в которой присутствует сериализация структур, например в файл. Рассмотрим пример:

struct Header
{
  unsigned m_version;
  size_t m_bodyLen;
};
...
size_t size = fwrite(&header, 1, sizeof(header), file);
...

Данный код записывает в файл различное количество байт, в зависимости от того скомпилирован он в режиме Win32 или Win64. Это может нарушить совместимость формата файлов или приводить к иным ошибкам.

Автоматизировать выявление подобных ошибок представляется практически не решаемой задачей. Однако, если имеются предпосылки считать, что код содержит подобные ошибки, то разработчики один раз могут проверить все структуры, которые участвую в сериализации. Для этого и может потребоваться проверка правила V122. По умолчанию оно отключено, так как дает ложное предупреждение в более чем 99% случаев.

В приведенном выше примере сообщение V122 будет выдано на строку "size_t m_bodyLen;". Исправление кода может заключаться в использовании типов фиксированного размера:

struct Header
{
  My_UInt32 m_version;
  My_UInt32 m_bodyLen;
};
...
size_t size = fwrite(&header, 1, sizeof(header), file);
...

Рассмотрим другие примеры, где будет выдано сообщение V122:

class X
{
  int i;
  DWORD_PTR a; //V122
  DWORD_PTR b[3]; //V122
  float c[3][4];
  float *ptr; //V122
};

Родственным диагностическим сообщением является V117.

V123. Allocation of memory by the pattern "(X*)malloc(sizeof(Y))" where the sizes of X and Y types are not equal.

Анализатор обнаружил потенциально возможную ошибку, связанную с операцией выделения памяти. При вычислении размера выделяемой памяти используется оператор sizeof(X). Результат, возвращаемый функцией выделения памяти, приводится не к "(X *)", а к другому типу "(Y *)". Это может свидетельствовать о выделении недостаточного или излишнего количества памяти.

Рассмотрим первый пример:

int **ArrayOfPointers = (int **)malloc(n * sizeof(int));

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

Исправленный вариант кода:

int **ArrayOfPointers = (int **)malloc(n * sizeof(int *));

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

unsigned *p = (unsigned *)malloc(len * sizeof(size_t));

Программа, содержащая подобный код, скорее всего, будет корректно работать как в 32-битном, так и в 64-битном варианте. Но в 64-битном варианте программа выделяет больше памяти, чем ей необходимо. Корректный вариант:

unsigned *p = (unsigned *)malloc(len * sizeof(unsigned));

В ряде случаев, анализатор не выдает предупреждение, хотя типы X и Y не совпадают. Пример подобного корректного кода:

BYTE *simpleBuf = (BYTE *)malloc(n * sizeof(float));

V124. Function 'Foo' writes/reads 'N' bytes. The alignment rules and type sizes have been changed. Consider reviewing this value.

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

Picture 1

Анализатор обращает внимание на те места в коде, где размер записываемых/читаемых данных указан явным образом. Эти места программисту следует проверить. Пример кода:

size_t n = fread(buf, 1, 40, f_in);

Константа 40 может являться некорректным значением в рамках 64-битной системы. Возможно, более правильно будет написать:

size_t n = fread(buf, 1, 10 * sizeof(size_t), f_in);

V125. It is not advised to declare type 'T' as 32-bit type.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что в 64-битном коде присутствует определение зарезервированных типов. При этом они определяются как 32-битные. Пример:

typedef unsigned size_t;
typedef __int32 INT_PTR;

Подобное определение типов может привести к различным ошибкам, так как в разных частях программы и библиотеках эти типы будут иметь различный размер. Следует использовать специальные заголовочные файлы, в которых корректно определены эти типы. Например, тип size_t определен в заголовочном файле stddef.h для языка C и в файле cstddef для языка C++.

Дополнительные ресурсы:

  • База знаний. Можно заставить тип size_t быть 32-битным в 64-битной программе? http://www.viva64.com/ru/k/0021/
  • База знаний. Является ли size_t стандартным типом в языке Си++? В языке Си? http://www.viva64.com/ru/k/0022/

V126. Be advised that the size of the type 'long' varies between LLP64/LP64 data models.

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

Конечно, наличие типа 'long' в программе не является ошибкой. Однако иногда бывает полезно просмотреть все места в тексте программы, где используется этот тип. Такое может понадобиться при создании переносимого 64-битного кода, который должен функционировать в Windows и в Linux.

Windows и Linux используют различные модели данных для 64-битной архитектуры. Модель данных это соотношения размеров базовых типов данных, таких как int, float, указатель и так далее. Windows использует модель данных LLP64. В Linux используется модель данных LP64. В этих моделях различается размер типа 'long'.

В Windows (LLP64) размер типа 'long' равен 4 байта.

В Linux (LP64) размер типа 'long' равен 8 байт.

Различие размера типа 'long' может привести к несовместимости форматов файлов или ошибкам при разработке кода, выполняемого в Linux и Windows. При желании, используя PVS-Studio мы сможете просмотреть все участки кода, где используется тип 'long'.

Дополнительные ресурсы:

V127. An overflow of the 32-bit variable is possible inside a long cycle which utilizes a memsize-type loop counter.

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

int count = 0;
for (size_t i = 0; i != N; i++)
{
  if ((A[i] & MASK) != 0)
    count++;
}

Приведенный код корректно работает в 32-битной программе. Переменной типа 'int' достаточно, чтобы посчитать количество каких-то элементов в массиве. Однако в 64-битной программе количество таких элементов может превысить INT_MAX и произойдет переполнение переменной 'count'. Об этом и предупреждает анализатор, выдавая сообщение V127. Корректный вариант:

size_t count = 0;
for (size_t i = 0; i != N; i++)
{
  if ((A[i] & MASK) != 0)
    count++;
}

Также анализатор содержит ряд дополнительных проверок, чтобы сократить количество ложных срабатываний. Например, сообщение V127 не будет выдано, если мы имеем дело с коротким циклом. Пример кода, который анализатор считает безопасным:

int count = 0;
for (size_t i = 0; i < 100; i++)
{
  if ((A[i] & MASK) != 0)
    count++;
}

V201. Explicit conversion from 32-bit integer type to memsize type.

Предупреждение информирует о наличии явного приведения 32-битного целочисленного типа к memsize типу, что может маскировать одну из следующих ошибок: V101, V102, V104, V105, V106, V108, V109. Вы можете обратиться к приведенному списку предупреждений, чтобы выявить причину появления диагностирующего сообщения V204.

Ранее предупреждение V201 распространялось и на приведение 32-битных целочисленных типов к указателю. Такие приведения весьма опасны. Поэтому они были выделены в отдельное диагностическое правило V204.

Учтите, что, скорее всего, большинство предупреждений этого типа будет выдано на корректный код. Приведем несколько примеров корректного и некорректного кода, на который будет выдано данное предупреждение.

Примеры некорректного кода.

int i;
ptrdiff_t n;
...
for (i = 0; (ptrdiff_t)(i) != n; ++i) {   //V201
  ...
}

unsigned width, height, depth;
...
size_t arraySize = size_t(width * height * depth);   //V201

Примеры корректного кода.

const size_t seconds = static_cast<size_t>(60 * 60);   //V201
unsigned *array;
...
size_t sum = 0;
for (size_t i = 0; i != n; i++) {
  sum += static_cast<size_t>(array[i] / 4);   //V201
}
unsigned width, height, depth;
...
size_t arraySize =
  size_t(width) * size_t(height) * size_t(depth);    //V201

V202. Explicit conversion from memsize type to 32-bit integer type.

Оно информирует о наличии явного приведения целочисленного memsize типа к 32-битному типу, что может маскировать одну из следующих ошибок: V103, V107, V110. Вы можете обратиться к приведенному списку предупреждений, чтобы выявить причину появления диагностирующего сообщения V202.

Ранее предупреждение V202 распространялось и на приведение указателя к 32-битному целочисленному типу. Такие приведения весьма опасны. Поэтому они были выделены в отдельное диагностическое правило V205.

Учтите, что, скорее всего, большинство предупреждений этого типа будет выдано на корректный код. Приведем несколько примеров корректного и некорректного кода, на который будет выдано данное предупреждение.

Примеры некорректного кода.

size_t n;
...
for (unsigned i = 0; i != (unsigned)n; ++i) {   //V202
  ...
}

UINT_PTR width, height, depth;
...
UINT arraySize = UINT(width * height * depth);   //V202

Примеры корректного кода.

const unsigned bits =
  unsigned(sizeof(object) * 8); //V202

extern size_t nPane;
extern HICON hIcon;
 BOOL result =
  SetIcon(static_cast<int>(nPane), hIcon); //V202

V203. Explicit type conversion from memsize to double type or vice versa.

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

Эта ошибка полностью аналогична ошибке V113. Отличие заключается только в том, что используется явное приведение типов, как например, показано ниже:

SIZE_T size = SIZE_MAX;
double tmp = static_cast<double>(size);
size = static_cast<SIZE_T>(tmp); // x86: size == SIZE_T
                                 // x64: size != SIZE_T

Для ознакомления с данным видом ошибок смотри описание ошибки V113.

V204. Explicit conversion from 32-bit integer type to pointer type.

Предупреждение информирует о наличии явного приведения 32-битного целочисленного типа к указателю. Ранее подобную ситуацию можно было вывить с помощью диагностического правила V201. Однако, явное приведение типа 'int' к указателю, гораздо опаснее, чем приведение 'int' к типу 'intptr_t'. Поэтому было создано отдельное правило для поиска явного приведения типов при работе с указателями.

Пример некорректного кода.

int n;
float *ptr;
...
ptr = (float *)(n);

В 64-битной программе тип 'int' имеет размер 4 байта и не может вместить в себя указатель размером 8 байт. Приведение типа, как показано в примере, практически всегда свидетельствует о наличии ошибки.

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

Если по каким-то причинам необходимо хранить указатель в переменной целочисленного типа, то для этого следует использовать memsize-типы данных. Например: size_t, ptrdiff_t, intptr_t, uintptr_t.

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

intptr_t n;
float *ptr;
...
ptr = (float *)(n);

Есть все-таки специфическая ситуация, когда указатель допустимо хранить в 32-битных типах. Речь идет о дескрипторах (handles), которые используются в Windows для работы с различными системными объектами. Примеры таких типов: HANDLE, HWND, HMENU, HPALETTE, HBITMAP и так далее. По сути, эти типы являются указателями. Например, HANDLE объявляется в заголовочных файлах как "typedef void *HANDLE;".

Хотя дескрипторы являются 64-битными указателями, для большей совместимости (например, для возможности взаимодействия между 32-битынми и 64-битными процессами) в них используется только младшие 32-бита. Подробнее смотри "Microsoft Interface Definition Language (MIDL): 64-Bit Porting Guide" (USER and GDI handles are sign extended 32b values).

Такие указатели можно хранить в 32-битным типам данных (например, int, DWORD). Для преобразования таких указателей к 32-битным типам и обратно используются специальные функции:

void            * Handle64ToHandle( const void * POINTER_64 h ) 
void * POINTER_64 HandleToHandle64( const void *h )
long              HandleToLong    ( const void *h )
unsigned long     HandleToUlong   ( const void *h )
void            * IntToPtr        ( const int i )
void            * LongToHandle    ( const long h )
void            * LongToPtr       ( const long l )
void            * Ptr64ToPtr      ( const void * POINTER_64 p )
int               PtrToInt        ( const void *p )
long              PtrToLong       ( const void *p )
void * POINTER_64 PtrToPtr64      ( const void *p )
short             PtrToShort      ( const void *p )
unsigned int      PtrToUint       ( const void *p )
unsigned long     PtrToUlong      ( const void *p )
unsigned short    PtrToUshort     ( const void *p )
void            * UIntToPtr       ( const unsigned int ui )
void            * ULongToPtr      ( const unsigned long ul ) 

Дополнительные материалы по данной теме:

V205. Explicit conversion of pointer type to 32-bit integer type.

Предупреждение информирует о наличии явного приведения указателя к 32-битному целочисленному типу. Ранее подобную ситуацию можно было вывить с помощью диагностического правила V202. Однако, явное приведение указателя к типу 'int', гораздо опаснее, чем приведение 'intptr_t' к типу 'int'. Поэтому было создано отдельное правило для поиска явного приведения типов при работе с указателями.

Пример некорректного кода.

int n;
float *ptr;
...
n = (int)ptr;

В 64-битной программе тип 'int' имеет размер 4 байта и не может вместить в себя указатель размером 8 байт. Приведение типа, как показано в примере, практически всегда свидетельствует о наличии ошибки.

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

Если по каким-то причинам необходимо хранить указатель в переменной целочисленного типа, то для этого следует использовать memsize-типы данных. Например: size_t, ptrdiff_t, intptr_t, uintptr_t.

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

intptr_t n;
float *ptr;
...
n = (int)ptr;

Есть все-таки специфическая ситуация, когда указатель допустимо хранить в 32-битных типах. Речь идет о дескрипторах (handles), которые используются в Windows для работы с различными системными объектами. Примеры таких типов: HANDLE, HWND, HMENU, HPALETTE, HBITMAP и так далее. По сути, эти типы являются указателями. Например, HANDLE объявляется в заголовочных файлах как "typedef void *HANDLE;".

Хотя дескрипторы являются 64-битными указателями, для большей совместимости (например, для возможности взаимодействия между 32-битынми и 64-битными процессами) в них используется только младшие 32-бита. Подробнее смотри "Microsoft Interface Definition Language (MIDL): 64-Bit Porting Guide" (USER and GDI handles are sign extended 32b values).

Такие указатели можно хранить в 32-битным типам данных (например, int, DWORD). Для преобразования таких указателей к 32-битным типам и обратно используются специальные функции:

void            * Handle64ToHandle( const void * POINTER_64 h ) 
void * POINTER_64 HandleToHandle64( const void *h )
long              HandleToLong    ( const void *h )
unsigned long     HandleToUlong   ( const void *h )
void            * IntToPtr        ( const int i )
void            * LongToHandle    ( const long h )
void            * LongToPtr       ( const long l )
void            * Ptr64ToPtr      ( const void * POINTER_64 p )
int               PtrToInt        ( const void *p )
long              PtrToLong       ( const void *p )
void * POINTER_64 PtrToPtr64      ( const void *p )
short             PtrToShort      ( const void *p )
unsigned int      PtrToUint       ( const void *p )
unsigned long     PtrToUlong      ( const void *p )
unsigned short    PtrToUshort     ( const void *p )
void            * UIntToPtr       ( const unsigned int ui )
void            * ULongToPtr      ( const unsigned long ul ) 

Дополнительные материалы по данной теме:

V220. Suspicious sequence of types castings: memsize -> 32-bit integer -> memsize.

Предупреждение информирует о наличии странной последовательности приведений типа. Memsize-тип явно приводится к 32-битному целочисленному типу. А затем тут же вновь явно или неявно приводится к memsize-типу. Такая последовательность приведений приводит к потере значений старших бит. Как правило, это свидетельствует о наличии серьезной ошибки.

Рассмотрим пример:

char *p1;
char *p2;
ptrdiff_t n;
...
n = int(p1 - p2);

Здесь присутствует лишнее приведение к типу 'int'. Оно не нужно и может послужить причиной сбоя, если в 64-битной программе указатели p1 и p2 будут отстоять друг от друга больше чем на INT_MAX элементов.

Корректный вариант кода:

char *p1;
char *p2;
ptrdiff_t n;
...
n = p1 - p2;

Рассмотрим другой пример:

BOOL SetItemData(int nItem, DWORD_PTR dwData);
...
CItemData *pData = new CItemData;
...
CListCtrl::SetItemData(nItem, (DWORD)pData);

Этот код приведёт к ошибке, если объект типа CItemData будет создан за пределами четырех младших гигабайт памяти. Корректный вариант кода:

BOOL SetItemData(int nItem, DWORD_PTR dwData);
...
CItemData *pData = new CItemData;
...
CListCtrl::SetItemData(nItem, (DWORD_PTR)pData);

V301. Unexpected function overloading behavior. See N argument of function 'foo' in derived class 'derived' and base class 'base'.

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

Пример изменения поведения виртуальных функций.

class CWinApp {
  ...
  virtual void WinHelp(DWORD_PTR dwData, UINT nCmd);
  ...
};
class CSampleApp : public CWinApp {
  ...
  virtual void WinHelp(DWORD dwData, UINT nCmd);
  ...
};

Перед вами классический пример, с которым может столкнуться разработчик, переносящий свое приложение на 64-битную архитектуру. Проследим жизненный цикл разработки некоторого приложения. Пусть первоначально оно разрабатывалось под Visual Studio 6.0. когда, функция WinHelp в классе CWinApp имела следующий прототип:

virtual void WinHelp(DWORD dwData, UINT nCmd = HELP_CONTEXT);

Совершенно корректно было осуществить перекрытие виртуальной фукции в классе CSampleApp, как показано в примере. Затем проект был перенесен в Visual Studio 2005, где прототип функции в классе CWinApp претерпел изменения, заключающиеся в смене типа DWORD на тип DWORD_PTR. На 32-битной платформе такая программа продолжит совершенно корректно работать, так как здесь типы DWORD и DWORD_PTR совпадают. Неприятности проявят себя при компиляции данного кода под 64-битную платфому. Получатся две функции с одинаковыми именами, но с различными параметрами, в результате чего перестанет вызываться пользовательский код.

Анализатор позволяет обнаруживать подобные ошибки, исправление которых не представляет сложности. Достаточно изменить прототип функции в классе наследника, как показано ниже:

class CSampleApp : public CWinApp {
  ...
  virtual void WinHelp(DWORD_PTR  dwData, UINT nCmd);
  ...
};

V302. Member operator[] of 'foo' class has a 32-bit type argument. Use memsize-type here.

Анализатор обнаружил потенциально возможную ошибку при работе с классами, в которых имеется operator[]. Классы с перегруженным operator[] обычно представляют собой разновидность массива и аргументом operator[] является индекс запрашиваемого элемента. Если operator[] имеет аргумент 32-битного типа, то это может свидетельствовать об ошибке. Рассмотрим пример, приводящий к сообщению V302:

class MyArray {
  std::vector<float> m_arr;
  ...
  float &operator[](int i)  //V302
  { 
    DoSomething();
    return m_arr[i];
  } 
} A;
...
int x = 2000;
int y = 2000;
int z = 2000;
A[x * y * z] = 33;

Если класс спроектирован для работы с большим количеством аргументов, то такая реализация operator[] является некорректной, так как не позволяет обращаться к элементам с порядковыми номерами более UINT_MAX. Диагностировать ошибку в приведенном примере можно только указав на потенциально опасный operator[]. Выражение "x * y * z" не выглядит подозрительным, так как отсутствует неявное приведение типа. Когда мы исправим operator[] следующим образом:

float &operator[](ptrdiff_t i);

анализатор PVS-Studio предупредит о возможной ошибке в строке "A[x * y * z] = 33;" и мы сможем сделать код программы окончательно корректным. Пример исправленного кода:

class MyArray {
  std::vector m_arr;
  ...
  float &operator[](ptrdiff_t i)  //V302
  { 
    DoSomething();
    return m_arr[i];
  } 
} A;
...
ptrdiff_t x = 2000;
ptrdiff_t y = 2000;
ptrdiff_t z = 2000;
A[x * y * z] = 33;

Родственными диагностическими сообщениями являются V108 и V120.

V303. The function is deprecated in the Win64 system. It is safer to use the 'foo' function.

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

Рассмотрим несколько примеров устаревших функций:

EnumProcessModules

Цитата из MSDN:

To control whether a 64-bit application enumerates 32-bit modules, 64-bit modules, or both types of modules, use the EnumProcessModulesEx function.

SetWindowLong

Цитата из MSDN:

This function has been superseded by the SetWindowLongPtr function. To write code that is compatible with both 32-bit and 64-bit versions of Windows, use the SetWindowLongPtr function.

GetFileSize

Цитата из MSDN:

When lpFileSizeHigh is NULL, the results returned for large files are ambiguous, and you will not be able to determine the actual size of the file. It is recommended that you use GetFileSizeEx instead.

V501. There are identical sub-expressions to the left and to the right of the 'foo' operator.

Анализатор обнаружил фрагмент кода, который, скорее всего, содержит логическую ошибку. В тексте программы имеется оператор (<, >, <=, >=, ==, !=, &&, ||, -, /), слева и справа от которого расположены одинаковые подвыражения.

Рассмотрим пример:

if (a.x != 0 && a.x != 0)

В данном случае оператор '&&' окружен одинаковыми подвыражениями "a.x != 0", что позволяет обнаружить ошибку, допущенную по невнимательности. Корректный код, который не вызовет подозрений у анализатора, будет выгладить так:

if (a.x != 0 && a.y != 0)

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

class Foo {
  int iChilds[2];
  ...
  bool hasChilds() const { return(iChilds > 0 || iChilds > 0); }
  ...
}

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

bool hasChilds() const { return(iChilds[0] > 0 || iChilds[1] > 0);}

Анализатор выдает предупреждение не во всех случаях, когда слева и справа от оператора находятся одинаковые подвыражения.

Первое исключение относится к конструкциям, где используются оператор инкремента ++, декремента --, а также += и -=. Пример взятый из реального приложения:

do {
} while (*++scan == *++match && *++scan == *++match &&
         *++scan == *++match && *++scan == *++match &&
         *++scan == *++match && *++scan == *++match &&
         *++scan == *++match && *++scan == *++match &&
         scan < strend);

Данный код анализатор считает безопасным.

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

#if defined(_OPENMP)
#include <omp.h>
#else
#define omp_get_thread_num()   0
...
#endif
...
if (0 == omp_get_thread_num()) {

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

#define _WINVER_NT4_    0x0004
define _WINVER_95_      0x0004
...
UINT    winver = g_App.m_pPrefs->GetWindowsVersion();
if(winver == _WINVER_95_ || winver == _WINVER_NT4_)

Следует понимать, что в ряде случаев анализатор может выдать предупреждение на корректную конструкцию. Например, анализатор не учитывает побочные эффекты (side effects) при вызове функций:

if (wr.readChar() == '\0' && wr.readChar() == '\0')

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

CHECK(VDStringA() == VDStringA(), true);
CHECK(VDStringA("abc") == VDStringA("abc"), true);

Диагностическое сообщение не выдается, если сравниваются два идентичных выражения типа float или double. Такое сравнение позволяет определить, является ли значение NaN. Пример кода, реализующего подобную проверку:

bool isnan(double X) { return X != X; }

V502. Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the 'foo' operator.

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

Оператор '?:' имеет более низкий приоритет по сравнению с операторами ||, &&, |, ^, &, !=, ==, >=, <=, >, <, >>, <<, -, +, %, /, *. Это можно случайно забыть и написать ошибочный код, подобный приведенному ниже:

bool bAdd = ...;
size_t rightLen = ...;
size_t newTypeLen = rightLen + bAdd ? 1 : 0;

Забыв, что оператор '+' более приоритетный, чем оператор '?:' программист ожидает, что код будет эквивалентен: "rightLen + (bAdd ? 1 : 0)". Но на самом деле код эквивалентен выражению: "(rightLen + bAdd) ? 1 : 0".

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

1) Слева от оператора '?:' находится переменная или подвыражение имеющие тип bool.

2) Это подвыражение сравнивается/складывается/умножается/... с переменной с типом отличной от bool.

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

Рассмотрим еще некоторые примеры некорректного кода:

bool b;
int x, y, z, h;
...
x = y < b ? z : h;
x = y + (z != h) ? 1 : 2;

Очевидно, что программистом, скорее всего, задумывался следующий корректный код:

bool b;
int x, y, z, h;
...
x = y < (b ? z : h);
x = y + ((z != h) ? 1 : 2);

Если слева от оператора '?:' находится тип отличный от bool, то анализатор считает, что код написан в Си стиле (где нет bool), или с использование объектов классов и не может выявить, опасен данный код или нет.

Пример корректного кода в стиле языка Си, который анализатор также считает корректным:

int conditions1;
int conditions2;
int conditions3;
...
char x = conditions1 + conditions2 + conditions3 ? 'a' : 'b';

V503. This is a nonsensical comparison: pointer < 0.

Анализатор обнаружил фрагмент кода, содержащий бессмысленное сравнение. С большой вероятностью данный код содержит логическую ошибку. Пример кода:

class MyClass {
public:
  CObj *Find(const char *name);
  ...
} Storage;

if (Storage.Find("foo") < 0)
  ObjectNotFound();

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

class MyClass {
public:
  // Если объект не найден, то функция
  // Find возвращает значение -1.
  ptrdiff_t Find(const char *name);
  CObj *Get(ptrdiff_t  index);
  ...
} Storage;
...
ptrdiff_t index = Storage.Find("ZZ");
if (index >= 0)
  Foo(Storage.Get(index));
...
if (Storage.Find("foo") < 0)
  ObjectNotFound();

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

class MyClass {
public:
  CObj *Find(const char *name);
  ...
} Storage;

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

CObj *obj = Storage.Find("ZZ");
if (obj != nullptr)
  Foo(obj);

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

if (Storage.Find("foo") < 0)
  ObjectNotFound();

V504. It is highly probable that the semicolon ';' is missing after 'return' keyword.

Анализатор обнаружил фрагмент кода, в котором возможно пропущена точка с запятой ';'. Пример кода, который приводит к выдаче диагностического сообщения V504:

void Foo();

void Foo2(int *ptr)
{
  if (ptr == NULL)
    return
  Foo();
  ...
}

В данном коде планировалось завершить работу функции, если указатель ptr == NULL. Однако, после оператора return забыта точка с запятой ';', что приводит к вызову функции Foo(). Функция Foo() и Foo2() ничего не возвращают и поэтому данный код компилируется без ошибок и предупреждений.

Скорее всего, программист планировал написать:

void Foo();

void Foo2(int *ptr)
{
  if (ptr == NULL)
    return;
  Foo();
  ...
}

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

void Foo2(int *ptr)
{
  if (ptr == NULL)
  {
    Foo();
    return;
  }
  ...
}

Анализатор считает код безопасным, если отсутствует оператор "if" или вызов функции находится на той же строке, что и оператор "return". Такой код достаточно часто можно встретить в программах. Примеры безопасного кода:

void CPagerCtrl::RecalcSize()
{
  return
    (void)::SendMessageW((m_hWnd), (0x1400 + 2), 0, 0);
}

void Trace(unsigned int n, std::string const &s)
  { if (n) return TraceImpl(n, s); Trace0(s); }

V505. The 'alloca' function is used inside the loop. This can quickly overflow stack.

Анализатор обнаружил использование функции alloca внутри цикла. Поскольку функция alloca использует стековую память, то ее многократный вызов в теле цикла может неожиданно привести к переполнению стека.

Пример опасного кода:

for (size_t i = 0; i < n; ++i)
  if (wcscmp(strings[i], A2W(pszSrc[i])) == 0)
  { 
    ...
  }

Внутри макроса A2W используется функция _alloca. Приведет ли данный код к ошибке или нет, будет зависеть от длины обрабатываемых строк, их количества и размера доступного стека.

V506. Pointer to local variable 'X' is stored outside the scope of this variable. Such a pointer will become invalid.

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

Первый пример:

class MyClass
{
  size_t *m_p;
  void Foo() {
    size_t localVar;
    ...
    m_p = &localVar;
  }
};

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

Второй пример:

void Get(float **x)
{
  float f;
  ...
  *x = &f;
}

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

Это сообщение подобно сообщению V507.

V507. Pointer to local array 'X' is stored outside the scope of this array. Such a pointer will become invalid.

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

Первый пример:

class MyClass1
{
  int *m_p;
  void Foo()
  {
    int localArray[33];
    ...
    m_x = localArray;
  }
};

Массив localArray создается в стеке и массив localArray перестанет существовать по завершению функции Foo(). Однако указатель на этот массив будет сохранен в переменной m_p и может по неаккуратности использоваться, что приведет к ошибке.

Второй пример:

struct CVariable {
  ...
  char  name[64];
};

void CRendererContext::RiGeometryV(int n, char *tokens[])
{
  for (i=0;i<n;i++)
  {
    CVariable  var;
    if (parseVariable(&var, NULL, tokens[i])) {
      tokens[i]  =  var.name;
  }
}

В этом примере указатель на массив, находящийся в переменной типа CVariable, сохраняется во внешнем массиве. В результате массив "tokens" после завершения функции RiGeometryV будет содержать указатели на уже несуществующие объекты.

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

png_infop info_ptr = png_create_info_struct(png_ptr);
...
BYTE trans[256];
info_ptr->trans = trans;
...
png_destroy_write_struct(&png_ptr, &info_ptr);

В данном коде время жизни объекта info_ptr совпадает с временем жизни trans. Объект создается внутри png_create_info_struct (), а уничтожается внутри png_destroy_write_struct(). Анализатор не может разобрать данный случай и предполагает, что объект png_ptr поступает извне. Пример кода, где анализатор был бы прав:

void Foo()
{
  png_infop info_ptr;
  info_ptr = GetExternInfoPng();
  BYTE trans[256];
  info_ptr->trans = trans;
}

Это сообщение подобно сообщению V506.

V508. The use of 'new type(n)' pattern was detected. Probably meant: 'new type[n]'.

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

int n;
...
int *P1 = new int(n);

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

int n;
...
int *P1 = new int[n];

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

float f = 1.0f;
float *f2 = new float(f);

MyClass *p = new MyClass(33);

V509. The 'throw' operator inside the destructor should be placed within the try..catch block. Raising exception inside the destructor is illegal.

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

Анализатор обнаружил деструктор, содержащий оператор throw вне блока try..catch. Пример:

LocalStorage::~LocalStorage()
{
  ...
  if (!FooFree(m_index))
    throw Err("FooFree", GetLastError());
  ...
}

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

LocalStorage::~LocalStorage()
{
  try {
    ...
    if (!FooFree(m_index))
      throw Err("FooFree", GetLastError());
    ...
  }
  catch (...)
  {
    assert(false);
  }
}

Дополнительные материалы по данной теме:

V510. The 'Foo' function is not expected to receive class-type variable as 'N' actual argument.

Существуют функции, в описании которых невозможно указать число и типы всех допустимых параметров. Тогда список формальных параметров завершается эллипсисом (...), что означает: "и, возможно, еще несколько аргументов". Пример эллипсис функции: "int printf(const char* ...);". В качестве фактического параметра для эллипсиса могут выступать только POD типы.

POD это аббревиатура от "Plain Old Data", что можно перевести как "Простые данные в стиле Си". К POD-типам относятся:

  • все встроенные арифметические типы (включая wchar_t и bool);
  • типы, объявленные с помощью ключевого слова enum;
  • указатели;
  • POD-структуры (struct или class) и POD-объединения (union), которые удовлетворяют нижеприведенным требованиям:
    • не содержат пользовательских конструкторов, деструктора или копирующего оператора присваивания;
    • не имеют базовых классов;
    • не содержат виртуальных функций;
    • не содержат защищенных (protected) или закрытых (private) нестатических членов данных;
    • не содержат нестатических членов данных не-POD-типов (или массивов из таких типов), а также ссылок.

Если эллипсис функции в качестве параметра передается объект класса, то практически всегда свидетельствует о наличии ошибки в программе. На практике правило V510 помогает выявить ошибочный код следующего вида:

wchar_t buf[100];
std::wstring ws(L"12345");
swprintf(buf, L"%s", ws);

Вместо указателя на строку в стек попадает содержимое объекта. Такой код приведет к формированию в буфере "абракадабры" или к аварийному завершению программы.

Корректный вариант кода должен выглядеть так:

wchar_t buf[100];
std::wstring ws(L"12345");
swprintf(buf, L"%s", ws.c_str());

Из-за того, что в функции с переменным количеством аргументов можно передать все что угодно их и не рекомендуют использовать практически во всех книгах по программированию на языке Си++. Вместо этого предлагается использовать безопасные механизмы, например, boost::format.

Отметим особенность, связанную с использованием класса CString из библиотеки MFC

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

CString s;
CString arg(L"OK");
s.Format(L"Test CString: %s\n", arg);

Корректный вариант кода должен выглядеть так:

s.Format(L"Test CString: %s\n", arg.GetString());

Или как предлагается в MSDN [1] для получения указателя на строку можно использовать явный оператор приведения LPCTSTR, реализованный в классе CString. Пример корректного кода из MSDN:

CString kindOfFruit = "bananas";
int howmany = 25;
printf("You have %d %s\n", howmany, (LPCTSTR)kindOfFruit);

Однако, на самом деле первый вариант "s.Format(L"Test CString: %s\n", arg);" также является корректным, как и остальные. Подробнее эта тема обсуждается в статье "Большой брат помогает тебе" [2].

Разработчики MFC реализовали класс CString специальным образом, так, чтобы его можно было передавать в функции вида printf и Format. Сделано это достаточно хитро и кто интересуется, тот может ознакомиться с реализацией в исходных кода класса CStringT, а также познакомиться с развернутым обсуждением "Pass CString to printf?" [3].

Таким образом, анализатор делает исключение для типа CStringT и считает следующий код корректным:

CString s;
CString arg(L"OK");
s.Format(L"Test CString: %s\n", arg);

Дополнительные материалы

V511. The sizeof() operator returns size of the pointer, and not of the array, in given expression.

Есть особенность языка, о которой легко забыть и допустить ошибку. Рассмотрим фрагмент кода:

char A[100];
void Foo(char B[100])
{
}

В этом коде объект A является массивам и выражение sizeof(A) вернет значение 100.

Объект B является просто указателем. Значение 100 в квадратных скобках подсказывает программисту, что он работает с массивом из ста элементов. Но в функцию передается вовсе не массив из ста элементов, а только указатель. Таким образом выражение sizeof(B) будет возвращать значение 4 или 8 (размер указателя в 32-битной/64-битной системе).

Предупреждение V511 выдается в том случае, когда вычисляется размер указателя переданного в качестве аргумента в формате "имя_типа имя_массива[N]". Такой код с высокой вероятностью содержит ошибку. Рассмотрим пример:

void Foo(float array[3])
{
  size_t n = sizeof(array) / sizeof(array[0]);
  for (size_t i = 0; i != n; i++)
    array[i] = 1.0f;
}

Функция заполнит значением 1.0f не весь массив, а только 1 или 2 элемента, в зависимости от разрядности системы.

В Win32: sizeof(array) / sizeof(array[0]) = 4/4 = 1.

В Win64: sizeof(array) / sizeof(array[0]) = 8/4 = 2.

Для предотвращения подобных ошибок необходимо явно передавать размер массива. Корректный код:

void Foo(float *array, size_t arraySize)
{
  for (size_t i = 0; i != arraySize; i++)
    array[i] = 1.0f;
}

Другой вариант, это использовать ссылку на массив:

void Foo(float (&array)[3])
{
  size_t n = sizeof(array) / sizeof(array[0]);
  for (size_t i = 0; i != n; i++)
    array[i] = 1.0f;
}

V512. A call of the 'Foo' function will lead to a buffer overflow or underflow.

Анализатор обнаружил потенциально возможную ошибку, связанную с заполнением, копированием или сравнением буферов памяти. Ошибка может приводить к переполнению буфера (buffer overflow) или, наоборот, к его неполной обработке (buffer underflow).

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

Приведем два примера, взятых из реальных приложений.

Пример N1.

MD5Context *ctx;
...
memset(ctx, 0, sizeof(ctx));

Здесь из-за опечатки очищается не вся структура, а только ее часть. Ошибка в том, что вычисляется размер указателя, а не структуры MD5Context. Корректный вариант кода:

MD5Context *ctx;
...
memset(ctx, 0, sizeof(*ctx));

Пример N2.

#define CONT_MAP_MAX 50
int _iContMap[CONT_MAP_MAX];
memset(_iContMap, -1, CONT_MAP_MAX);

В данном примере также неверно указан размер заполняемого буфера. Корректный вариант:

#define CONT_MAP_MAX 50
int _iContMap[CONT_MAP_MAX];
memset(_iContMap, -1, CONT_MAP_MAX * sizeof(int));

V513. Use _beginthreadex/_endthreadex functions instead of CreateThread/ExitThread functions.

В программе обнаружено использование функции CreateThread или ExitThread. Если в параллельных потоках используются функции CRT (C run-time library), то вместо CreateThread/ ExitThread следует вызывать функции _beginthreadex/_endthreadex.

Приведем выдержки из 6-ой главы книги Джеффри Рихтера "Windows для профессионалов: создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows" / Пер. с англ. - 4-е изд.

CreateThread - это Windows-функция, создающая поток. Но никогда не вызывайте ее, если Вы пишете код на С/С++. Вместо нее Вы должны использовать функцию _beginthreadex из библиотеки Visual С++.

Чтобы многопоточные программы, использующие библиотеку С/С++ (CRT), работали корректно, требуется создать специальную структуру данных и связать ее с каждым потоком, из которого вызываются библиотечные функции. Более того, они должны знать, что, когда Вы к ним обращаетесь, нужно просматривать этот блок данных в вызывающем потоке чтобы не повредить данные в каком-нибудь другом потоке.

Так откуда же система знает, что при создании нового потока надо создать и этот блок данных? Ответ очень прост - не знает и знать не хочет Вся ответственность - исключительно на Вас. Если Вы пользуетесь небезопасными в многопоточной среде функциями, то должны создавать потоки библиотечной функцией _beginthreadex, а не Windows-функцией CreateThread .

Заметьте, что функция _beginthreadex существует только в многопоточных версиях библиотеки С/С++. Связав проект с однопоточной библиотекой, Вы получите от компоновщика сообщение об ошибке "unresolved external symbol". Конечно, это сделано специально, потому что однопоточная библиотека не может корректно работать в многопоточном приложении. Также обратите внимание на то, что при создании нового проекта Visual Studio по умолчанию выбирает однопоточную библиотеку. Этот вариант не самый безопасный, и для многопоточных приложений Вы должны сами выбрать одну из многопоточных версий библиотеки С/С++.

Соответственно, для уничтожения потока, созданного с помощью функции _beginthreadex, необходимо использовать функцию _endthreadex.

Дополнительные материалы по данной теме:

V514. Dividing sizeof a pointer by another value. There is a probability of logical error presence.

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

Рассмотрим пример:

const size_t StrLen = 16;
LPTSTR dest = new TCHAR[StrLen];
TCHAR src[StrLen] = _T("PVS-Studio V514");
_tcsncpy(dest, src, sizeof(dest)/sizeof(dest[0]));

В выражении "sizeof(dest)/sizeof(dest[0])" происходит деление размера указателя на размер элемента, на который ссылается указатель. В результате мы можем получить различное количество скопированных байтов в зависимости от размера указателя и типа TCHAR, но не то количество, что планировал программист.

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

const size_t StrLen = 16;
LPTSTR dest = new TCHAR[StrLen];
TCHAR src[StrLen] = _T("PVS-Studio V514");
_tcsncpy_s(dest, StrLen, src, StrLen);

V515. The 'delete' operator is applied to non-pointer.

В коде оператор delete применяется не к указателю, а к объекту класса. С большой вероятностью это является ошибкой.

Рассмотрим пример кода:

CString str;
...
delete str;

Оператор 'delete' можно применить к объекту типа CString,так как класс CString может быть автоматически приведен к указателю. Подобный код может привести к исключению или неопределенному поведению программы.

Корректный код, возможно должен был выглядеть так:

CString *pstr = new CString;
...
delete pstr;

В некоторых случаях применение оператора 'delete' к объектам класса не является ошибкой. Подобный код, например, можно встретить при работе с классом QT::QBasicAtomicPointer. Анализатор игнорирует вызов операnора 'delete' для объектов этого типа. Если Вы знаете другие подобные классы, применение к которым оператора 'delete' является стандартной практикой, то сообщите нам о них. Мы добавим их в исключения.

V516. Consider inspecting an odd expression. Non-null function pointer is compared to null.

В коде имеется конструкция сравнения ненулевого указателя на функцию с нулем. Скорее всего, это означает, что в коде присутствует опечатка - забыты круглые скобки.

Рассмотрим пример:

int Foo();
void Use()
{
  if (Foo == 0)
  {
    //...
  }
}

Условие "Foo == 0" не имеет смысла. Адрес функции 'Foo' всегда не равен нуля, а следовательно результатом сравнение всегда будет значение 'false'. В рассматриваемом коде случайно пропущены круглые скобки. Корректный вариант кода:

if (Foo() == 0)
{
  //...
}

Если в коде явно написано взятие адреса функции, то такой код считается корректным. Пример:

int Foo();
void Use()
{
  if (&Foo != NULL)
    //...
}

V517. The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence.

Анализатор обнаружил потенциально возможную ошибку в конструкции, состоящей из условных операторов. Рассмотрим пример:

if (a == 1)
  Foo1();
else if (a == 2)
  Foo2();
else if (a == 1)
  Foo3();

В данном примере функции 'Foo3()' никогда не получит управления. Вероятно, мы имеем дело с логической ошибкой и корректный код должен выглядеть так:

if (a == 1)
  Foo1();
else if (a == 2)
  Foo2();
else if (a == 3)
  Foo3()

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

If (radius < THRESH * 5)
  *yOut = THRESH * 10 / radius;
else if (radius < THRESH * 5)
  *yOut = -3.0f / (THRESH * 5.0f) * (radius - THRESH * 5.0f) + 3.0f;
else
   *yOut = 0.0f;

Трудно сказать, как должно выглядеть корректное условие сравнения, но наличие в коде ошибки очевидно.

V518. The 'malloc' function allocates strange amount of memory calculated by 'strlen(expr)'. Perhaps the correct variant is strlen(expr) + 1.

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

Рассмотрим пример:

char *p = (char *)malloc(strlen(src));
strcpy(p, src);

В данном случае просто забыто про +1. Правильный код:

char *p = (char *)malloc(strlen(src) + 1);
strcpy(p, src);

Приведем другой пример некорректного кода, обнаруженный анализатором в одном из приложений:

if((t=(char *)realloc(next->name, strlen(name+1))))
{
  next->name=t;
  strcpy(next->name,name);
}

Здесь по невнимательности неправильно поставлена правая скобочка ')'. В результате мы выделим на 2 байта меньше памяти, чем необходимо. Исправленный вариант:

if((t=(char *)realloc(next->name, strlen(name)+1)))

V519. The 'x' variable is assigned values twice successively. Perhaps this is a mistake.

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

Рассмотрим пример:

A = GetA();
A = GetB();

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

A = GetA();
B = GetB();

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

A = 1;
A = A + 1;
A = Foo(A);

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

class CSize : public SIZE
{
  ...
  CSize(POINT pt) { cx = pt.x;  cx = pt.y; }

Корректный вариант должен был выглядеть так:

CSize(POINT pt) { cx = pt.x;  cy = pt.y; }

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

m_clrSample = GetSysColor(COLOR_WINDOWTEXT);
m_clrSample = RGB(60,0,0);

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

status = Foo1();
status = Foo2();
status = Foo3();

В данной ситуации можно подавить ложные срабатывания, используя комментарий "//-V519". Можно убрать из кода ничего не значащие присваивания. И последнее. Возможно этот код все же некорректен, и необходимо проверять значение переменной 'status'.

V520. The comma operator ',' in array index expression.

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

Пример подозрительного кода:

float **array_2D;
array_2D[getx() , gety()] = 0;

Скорее всего, здесь имелось в виду:

array_2D[ getx() ][ gety() ] = 0;

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

Рассмотрим пример ошибки, найденный анализатором в одном из проектов:

float **m;
TextOutput &t = ...
...
t.printf("%10.5f, %10.5f, %10.5f,\n%10.5f, %10.5f, %10.5f,\n%10.5f, %10.5f, %10.5f)",
  m[0, 0], m[0, 1], m[0, 2],
  m[1, 0], m[1, 1], m[1, 2],
  m[2, 0], m[2, 1], m[2, 2]);

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

t.printf("%10.5f, %10.5f, %10.5f,\n%10.5f, %10.5f, %10.5f,\n%10.5f, %10.5f, %10.5f)",
  m[0][0]][m[0][1]][m[0][2],
  m[1][0]][m[1][1]][m[1][2],
  m[2][0]][m[2][1]][m[2][2]);

V521. Such expressions using the ',' operator are dangerous. Make sure the expression is correct.

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

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

Рассмотрим пример:

float Foo()
{
  double A;
  A = 1,23;
  float f = 10.0f;
  return 3,f;
}

В данном коде переменной A будет присвоено значение 1, а вовсе не 1.23. Согласно правилам языка Си/Си++ выражение "A = 1,23" эквивалентно "(A = 1),23". Также функция Foo() вернет значение 10.0f, а не 3.0f. В обоих случаях ошибка связана с использованием символа запятая ',' вместо символа точки '.'.

Исправленный вариант кода:

float Foo()
{
  double A;
  A = 1.23;
  float f = 10.0f;
  return 3.f;
}

Примечание. Были случаи, когда анализатор не мог разобраться в коде и выдавал предупреждения V521 на совершенно безобидные конструкции. Обычно это связано с использованием шаблонных классов или сложных макросов. Если при работе с анализатором вы заметили подобное ложное срабатывание, то просим сообщить о нем разработчикам. Для подавления ложных срабатываний можно использовать комментарий вида "//-V521".

V522. Dereferencing of the null pointer might take place.

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

Рассмотрим несколько примеров, для которых анализатор выдает диагностическое сообщение V522:

if (pointer != 0 || pointer->m_a) { ... }
if (pointer == 0 && pointer->x()) { ... }
if (array == 0 && array[3]) { ... }
if (!pointer && pointer->x()) { ... }

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

Корректные варианты:

if (pointer != 0 && pointer->m_a) { ... }
if (pointer != 0 && pointer->x()) { ... }
if (array != 0 && array[3]) { ... }
if (pointer && pointer->x()) { ... }

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

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

if (ptag == NULL) {
  SysPrintf("SPR1 Tag BUSERR\n");
  psHu32(DMAC_STAT)|= 1<<15;
  spr1->chcr = ( spr1->chcr & 0xFFFF ) |
               ( (*ptag) & 0xFFFF0000 );   
  return;
}

Анализатор PVS-Studio предупредит, об опасности в строке "( (*ptag) & 0xFFFF0000 )". Здесь или некорректно написано условие, или вместо 'ptag' должна использоваться другая переменная.

Иногда в тестовых целях программисты сознательно используют разыменование нулевого указателя. Например, PVS-Studio будет генерировать предупреждение там, где используется вот такой макрос:

/// This generate a coredump when we need a
/// method to be compiled but not usabled.
#define elxFIXME { char * p=0; *p=0; }

Лишние предупреждения можно отключить, используя комментарий "//-V522" в тех строках, где используется макрос 'elxFIXME'. Альтернативный вариант, это написать рядом с макросом комментарий специального вида:

//-V:elxFIXME:522

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

V523. The 'then' statement is equivalent to the 'else' statement.

Анализатор обнаружил ситуацию, когда истинная и ложная ветка оператора 'if' полностью совпадают. Часто это свидетельствует о наличии логической ошибки.

Пример:

if (X)
  Foo_A();
else
  Foo_A();

Будет условие X ложно или истинно, все равно произойдет вызов функции Foo_A().

Корректный вариант кода:

if (X)
  Foo_A();
else
  Foo_B();

Пример подобной ошибки, взятый из реального приложения:

if (!_isVertical)
  Flags |= DT_BOTTOM;
else
  Flags |= DT_BOTTOM;

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

if (exp) {
} else {
}

V524. It is odd that the body of 'Foo_1' function is fully equivalent to the body of 'Foo_2' function.

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

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

class Point
{
  ...
  float GetX() { return m_x; }
  float GetY() { return m_x; }
};

Из-за допущенной опечатки две разные по смыслу функции выполняют одинаковые действия. Корректный вариант:

float GetX() { return m_x; }
float GetY() { return m_y; }

В приведенном примере идентичность тел функций GetX() и GetY() явно свидетельствует о наличии ошибки. Однако если выдавать предупреждения на все одинаковые функции, то процент ложный срабатываний будет крайне большим. Поэтому анализатор руководствуется целым рядом исключений, когда не стоит предупреждать об одинаковых телах функций. Перечислим некоторые из них:

  • Не сообщается об идентичности тел функций, если в них не используются переменные кроме аргументов. Пример: "bool IsXYZ() { return true; }".
  • В функциях используются статические объекты, а, следовательно, функции имеют различные внутренние состояние. Пример: "int Get() { static int x = 1; return x++; }"
  • Функции являются операторами приведения типа.
  • Если функции с одинаковыми телами повторяются более двух раз.
  • И так далее.

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

PolynomialMod2 Plus(const PolynomialMod2 &b) const {return Xor(b);}
PolynomialMod2 Minus(const PolynomialMod2 &b) const {return Xor(b);}

Бороться с ложными срабатываниями можно несколькими способами. Если ложные срабатывания относятся к файлам внешних библиотек, то эту библиотеку (путь до нее) можно добавить в исключения. Если предупреждения относятся к вашему коду, то вы можете использовать комментарий вида "//-V524", который приведет к подавлению предупреждений. Если ложных срабатываний много, то вы можете в настройках анализатора полностью отключить использование данной проверки. Также вы можете модифицировать код таким образом, чтобы одна функция вызывала другую с тем же самым кодом.

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

static void PreSave(void) {
  int x;
  for(x=0;x<TotalSides;x++) {
    int b;
    for(b=0; b<65500; b++)
      diskdata[x][b] ^= diskdatao[x][b];
  }
}

static void PostSave (void) {
  int x;
  for(x=0;x<TotalSides;x++) {
    int b;
    for(b=0; b<65500; b++)
      diskdata[x][b] ^= diskdatao[x][b];
  }
}

Разумно заменить этот код на следующий вариант:

static void PreSave(void) {
  int x;
  for(x=0;x<TotalSides;x++) {
    int b;
    for(b=0; b<65500; b++)
      diskdata[x][b] ^= diskdatao[x][b];
  }
}

static void PostSave (void) {
  PreSave();
}

В этом коде не была исправлена ошибка. Но после рефакторинга предупреждение V524 исчезло, а код стал проще.

V525. The code containing the collection of similar blocks. Check items X, Y, Z, ... in lines N1, N2, N3, ...

Анализатор обнаружил код, который возможно содержит опечатку. Этот код можно разделить на более мелкие и похожие между собой фрагменты кода. Хотя фрагменты кода похожи, но все же различны. Высока вероятность, что подобный код был создан с использованием подхода Copy-Paste. Сообщение V525 будет выдано в том случае, если есть подозрение, что один из элементов не исправлен в скопированном тексте. Ошибка может находиться в одной из строк, номера которых содержатся в сообщении V525.

Недостатки сообщения V525:

1) Данное диагностическое правило основано на эвристических методах и нередко дает ложное срабатывание.

2) Реализация эвристического алгоритма сложна и занимает более 1000 строк кода на Си++. Поэтому его затруднительно описать в рамках документации. Как следствие пользователю может быть трудно понять, почему выдано то или иное сообщение V525.

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

Преимущества сообщения V525:

1) Можно обнаружить ошибки, которые крайне сложно заметить при обзоре кода (code review).

Рассмотрим вначале искусственный пример:

...
float rgba[4];
rgba[0] = object.GetR();
rgba[1] = object.GetG();
rgba[2] = object.GetB();
rgba[3] = object.GetR();

Массив 'rgba' представляет собой цвет и прозрачность некоего объекта. При написании кода, заполняющего массив, вначале была написана строчка "rgba[0] = object.GetR();". Затем это строка была несколько раз скопирована и изменена. Однако в последней строке изменения сделаны не до конца и вместо функции 'GetA()' вызывается 'GetR()'. Анализатор выдает на данный код следующее предупреждение:

V525: The code containing the collection of similar blocks. Check items 'GetR', 'GetG', 'GetB', 'GetR' in lines 12, 13, 14, 15.

Просмотрев строки 12, 13, 14 и 15 можно обнаружить ошибку. Исправленный вариант кода:

rgba[3] = object.GetA(); 

Теперь рассмотрим несколько примеров, взятых из кода реальных приложений. Первый пример:

tbb[0].iBitmap = 0; 
tbb[0].idCommand = IDC_TB_EXIT; 
tbb[0].fsState = TBSTATE_ENABLED; 
tbb[0].fsStyle = BTNS_BUTTON; 
tbb[0].dwData = 0; 
tbb[0].iString = -1; 
...
tbb[6].iBitmap = 6; 
tbb[6].idCommand = IDC_TB_SETTINGS; 
tbb[6].fsState = TBSTATE_ENABLED; 
tbb[6].fsStyle = BTNS_BUTTON; 
tbb[6].dwData = 0; 
tbb[6].iString = -1;
 
tbb[7].iBitmap = 7; 
tbb[7].idCommand = IDC_TB_CALC; 
tbb[7].fsState = TBSTATE_ENABLED; 
tbb[7].fsStyle = BTNS_BUTTON; 
tbb[6].dwData = 0; 
tbb[7].iString = -1;

Фрагмент кода приведен далеко не полностью. Вырезано более чем половина. Фрагмент писался методом копирования и правки кода. Неудивительно, что в таком большом фрагменте затерялся неверный индекс. Анализатор выдает следующее диагностическое сообщение: "The code containing the collection of similar blocks. Check items '0', '1', '2', '3', '4', '5', '6', '6' in lines 589, 596, 603, 610, 617, 624, 631, 638". Просмотрев данные строки, мы можем исправить дважды повторяющийся индекс '6'. Исправленный вариант кода:

tbb[7].iBitmap = 7; 
tbb[7].idCommand = IDC_TB_CALC; 
tbb[7].fsState = TBSTATE_ENABLED; 
tbb[7].fsStyle = BTNS_BUTTON; 
tbb[7].dwData = 0; 
tbb[7].iString = -1;

Второй пример:

pPopup->EnableMenuItem(
  ID_CONTEXT_EDITTEXT,MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_CLOSEALL, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_CLOSE, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_SAVELAYOUT, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_RESIZE, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_REFRESH, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_EDITTEXT, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_SAVE, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_EDITIMAGE,MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_CLONE,MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);

Рассматривая данный код, очень сложно найти в нем ошибку. Однако ошибка здесь действительно присутствует. Дважды модифицируется состояние одного и того же пункта меню 'ID_CONTEXT_EDITTEXT'. Выделим две повторяющиеся строки:

------------------------------
pPopup->EnableMenuItem(
  ID_CONTEXT_EDITTEXT,MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
------------------------------
pPopup->EnableMenuItem(
  ID_CONTEXT_CLOSEALL, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_CLOSE, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_SAVELAYOUT, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_RESIZE, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_REFRESH, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
------------------------------
pPopup->EnableMenuItem(
  ID_CONTEXT_EDITTEXT, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
------------------------------
pPopup->EnableMenuItem(
  ID_CONTEXT_SAVE, MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_EDITIMAGE,MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);
pPopup->EnableMenuItem(
  ID_CONTEXT_CLONE,MF_GRAYED|MF_DISABLED|MF_BYCOMMAND);

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

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

switch (i) {
  case 0: f1 = 2; f2 = 3; break;
  case 1: f1 = 0; f2 = 3; break;
  case 2: f1 = 1; f2 = 3; break;
  case 3: f1 = 1; f2 = 2; break;
  case 4: f1 = 2; f2 = 0; break;
  case 5: f1 = 0; f2 = 1; break;
}

Здесь анализатору не нравится корректный столбец цифр: 2, 0, 1, 1, 2, 0. В подобных ситуациях можно воспользоваться подавлением предупреждений, вписав комментарий //-V525 в конце строки:

switch (i) {
  case 0: f1 = 2; f2 = 3; break; //-V525
  case 1: f1 = 0; f2 = 3; break;
  case 2: f1 = 1; f2 = 3; break;
  case 3: f1 = 1; f2 = 2; break;
  case 4: f1 = 2; f2 = 0; break;
  case 5: f1 = 0; f2 = 1; break;
}

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

V526. The 'strcmp' function returns 0 if corresponding strings are equal. Consider examining the condition for mistakes.

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

Анализатор обнаружил конструкцию сравнения двух строк, которую рационально записать более понятным способом. Такие функции как strcmp, strncmp, wcsncmp возвращают 0, если строки совпадает. Это может приводить к ошибкам в логике программы. Рассмотрим пример кода:

if (strcmp(s1, s2))

Это условие выполнится в том случае, если строки НЕ СОВПАДАЮТ. Возможно, вы хорошо помните, что возвращает strcmp(). Однако человек, редко работающий со строковыми функциями может подумать, что функция strcmp() возвращает значение типа 'bool'. Тогда он прочитает этот код так: "условие истинно, если строки совпадают".

Лучше не экономить на лишних символах в тексте программы и написать так:

if (strcmp(s1, s2) != 0)

Подобная запись подсказывает человеку, что функция strcmp() возвращает не тип bool, а некое числовое значение. Так снижается вероятность, что код будет неверно понят.

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

V527. It is odd that the 'zero' value is assigned to pointer. Probably meant: *ptr = zero.

Подобная ошибка возникает в двух схожих ситуациях.

1) Анализатор обнаружил потенциально возможною ошибку, связанную с тем, что указателю на тип bool присваивается значение false. Высока вероятность, что забыта операция разыменования указателя. Пример:

float Get(bool *retStatus)
{
  ...
  if (retStatus != nullptr)
    retStatus = false;
  ...
}

В данном коде забыт оператор '*'. Вместо возвращения статуса произойдет обнуление указателя retStatus. Корректный вариант кода:

if (retStatus != nullptr)
  *retStatus = false;

2) Анализатор обнаружил потенциально возможною ошибку, связанную с тем, что указателю на тип char/wchar_t присваивается значение '\0' или L'\0'. Высока вероятность, что забыта операция разыменования указателя. Пример:

char *cp;
...
cp = '\0';

Корректный вариант:

char *cp;
...
*cp = '\0';

V528. It is odd that pointer is compared with the 'zero' value. Probably meant: *ptr != zero.

Подобная ошибка возникает в двух схожих ситуациях.

1) Анализатор обнаружил потенциально возможною ошибку, связанную с тем, что указатель на тип bool сравнивается со значением false. Высока вероятность, что забыта операция разыменования указателя. Пример:

bool *pState;
...
if (pState != false)
...

В данном коде забыт оператор '*'. И получается, что мы сравниваем значение указателя pState с нулевым указателем nullptr. Корректный вариант кода:

bool *pState;
...
if (*pState != false)
...

2) Анализатор обнаружил потенциально возможною ошибку, связанную с тем, что указатель на тип char/wchar_t сравнивается со значением '\0' или L'\0'. Высока вероятность, что забыта операция разыменования указателя. Пример:

char *cp;
...
if (cp != '\0')

Корректный вариант:

char *cp;
...
if (*cp != '\0')

V529. Odd semicolon ';' after 'if/for/while' operator.

Анализатор обнаружил потенциально возможною ошибку, связанную с наличием точки с запятой ';' после оператора if, for или while. Приведем пример:

for (i = 0; i < n; i++);
{
  Foo(i);
}

Корректный вариант:

for (i = 0; i < n; i++)
{
  Foo(i);
}

Использование точки с запятой ';' сразу после оператора for или while само по себе не является ошибкой и часто встречается в коде. Поэтому анализатор отсевает многие случаи, руководствуясь рядом дополнительных факторов. Например, следующий пример кода считается анализатором безопасным:

for (depth = 0, cur = parent; cur; depth++, cur = cur->parent)
  ;

V530. The return value of function 'Foo' is required to be utilized.

Вызов некоторых функций не имеет смысла, если результат их работы не используется. Рассмотрим первый пример:

void VariantValue::Clear()
{
  m_vtype = VT_NULL;
  m_bvalue = false;
  m_ivalue = 0;
  m_fvalue = 0;
  m_svalue.empty();
  m_tvalue = 0;
}

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

Исправленный вариант кода:

void VariantValue::Clear()
{
  m_vtype = VT_NULL;
  m_bvalue = false;
  m_ivalue = 0;
  m_fvalue = 0;
  m_svalue.clear();
  m_tvalue = 0;
}

Второй пример:

void unregisterThread() {
  Guard<TaskQueue> g(_taskQueue);
  std::remove(_threads.begin(), _threads.end(),
              ThreadImpl::current());
}

Функция std::remove не удаляет элементы из контейнера. Она только сдвигает элементы и возвращает итератор на начало мусора. Пусть мы имеем контейнер vector<int>, содержащий элементы 1,2,3,1,2,3,1,2,3. Если выполнить код "remove( v.begin(), v.end(), 2 )", то контейнер будет содержать элементы 1,3,1,3,?,?,?, где ? - некий мусор. При этом функция вернет итератор на первый мусорный элемент, и если мы хотим удалить эти мусорные элементы, то должны написать код: "v.erase(remove(v.begin(), v.end(), 2), v.end())".

Как видно из объяснения, результат std::remove должен быть обязательно использован. Корректный код:

void unregisterThread() {
  Guard<TaskQueue> g(_taskQueue);
  auto trash = std::remove(_threads.begin(), _threads.end(),
                           ThreadImpl::current());
  _threads.erase(trash, _threads.end());
}

Функций, результат которых должен быть обязательно использован огромное количество. К ним можно отнести: malloc, realloc, fopen, isalpha, atof, strcmp и многие, многие другие функции. Неиспользуемый результат свидетельствует об ошибке, чаще всего связанный с допущенной опечаткой. Однако анализатор PVS-Studio предупреждает только об ошибках связанных с использованием библиотеки STL. На это имеется две причины:

1) Допустить ошибку, не используя результат такой функции как fopen() намного сложней, чем спутать std::clear() и std::empty().

2) Данная функциональность будет дублировать возможности Code Analysis for C/C++, входящий в состав некоторых редакций Visual Studio (смотри предупреждение C6031). Однако в Visual Studio эти предупреждения не реализованы для функций STL.

Если вы хотите предложить расширить список функций, поддерживаемых PVS-Studio, то обратитесь в поддержку. Мы будем благодарны за интересные примеры и советы.

V531. It is odd that a sizeof() operator is multiplied by sizeof().

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

Рассмотрим реальный пример кода:

TCHAR szTemp[256];
DWORD dwLen =
  ::LoadString(hInstDll, dwID, szTemp,
               sizeof(szTemp) * sizeof(TCHAR));

Функция LoadString в качестве последнего аргумента принимает размер буфера в символах. В Unicode версии приложения мы сообщим функции, что размер буфера больше, чем он есть нас самом деле. Это может привести к переполнению буфера. Заметим, что следующее исправление вовсе не является корректным:

TCHAR szTemp[256];
DWORD dwLen =
  ::LoadString(hInstDll, dwID, szTemp, sizeof(szTemp));

Приведем на эту темы выдержку из MSDN:

Using this function incorrectly can compromise the security of your application. Incorrect use includes specifying the wrong size in the nBufferMax parameter. For example, if lpBuffer points to a buffer szBuffer which is declared as TCHAR szBuffer[100], then sizeof(szBuffer) gives the size of the buffer in bytes, which could lead to a buffer overflow for the Unicode version of the function. Buffer overflow situations are the cause of many security problems in applications. In this case, using sizeof(szBuffer)/sizeof(TCHAR) or sizeof(szBuffer)/sizeof(szBuffer[0]) would give the proper size of the buffer.

Корректный вариант кода:

TCHAR szTemp[256];
DWORD dwLen =
  ::LoadString(hInstDll, dwID, szTemp,
               sizeof(szTemp) / sizeof(TCHAR));

Другой корректный вариант:

const size_t BUF_LEN = 256;
TCHAR szTemp[BUF_LEN];
DWORD dwLen =
  ::LoadString(hInstDll, dwID, szTemp, BUF_LEN);

V532. Consider inspecting the statement of '*pointer++' pattern. Probably meant: '(*pointer)++'.

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

Рассмотрим пример:

int *p;
...
*p++;

Выражение "*p++" выполняет следующие действия. Указатель "p" будет увеличен на единицу, но прежде этого из памяти будет извлечено значение типа "int". Это значение никак не используется, что странно. Получается, что операция разыменования "*" является лишней. Возможны следующие варианты, как следует поступить с кодом:

1) Удалить лишнее разыменование. Высказывание "*p++;" эквивалентно "p++;":

int *p;
...
p++;

2) На самом деле хотели увеличить не указатель, а значение. Тогда следует написать:

int *p;
...
(*p)++;

Если результат выражения "*p++" используется, то анализатор считает код корректным. Пример безопасного кода:

while(*src)
 *dest++ = *src++;

Рассмотрим пример взятый из реального приложения:

STDMETHODIMP CCustomAutoComplete::Next(
  ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
{
  ...
  if (pceltFetched != NULL)
    *pceltFetched++;
  ...

В данном случае забыты круглые скобки. Корректный вариант:

if (pceltFetched != NULL)
    (*pceltFetched)++;

V533. It is likely that a wrong variable is being incremented inside the 'for' operator. Consider reviewing 'X'.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что в операторе 'for' увеличивается переменная, относящаяся к внешнему циклу.

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

for (size_t i = 0; i != 5; i++)
  for (size_t j = 0; j != 5; i++)
    A[i][j] = 0;

Во внутреннем цикле происходит увеличение переменной 'i' вместо 'j'. В реальном приложении подобная ошибка может быть не так хорошо заметна. Корректный вариант кода:

for (size_t i = 0; i != 5; i++)
  for (size_t j = 0; j != 5; j++)
    A[i][j] = 0;

V533. It is likely that a wrong variable is being incremented inside the 'for' operator. Consider reviewing 'X'.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что в операторе 'for' увеличивается переменная, относящаяся к внешнему циклу.

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

for (size_t i = 0; i != 5; i++)
  for (size_t j = 0; j != 5; i++)
    A[i][j] = 0;

Во внутреннем цикле происходит увеличение переменной 'i' вместо 'j'. В реальном приложении подобная ошибка может быть не так хорошо заметна. Корректный вариант кода:

for (size_t i = 0; i != 5; i++)
  for (size_t j = 0; j != 5; j++)
    A[i][j] = 0;

V534. It is likely that a wrong variable is being compared inside the 'for' operator. Consider reviewing 'X'.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что в операторе 'for' в условии используется переменная, относящаяся к внешнему циклу.

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

for (size_t i = 0; i != 5; i++)
  for (size_t j = 0; i != 5; j++)
    A[i][j] = 0;

Во внутреннем цикле происходит сравнение 'i !+ 5' вместо 'j != 5'. В реальном приложении подобная ошибка может быть не так хорошо заметна. Корректный вариант кода:

for (size_t i = 0; i != 5; i++)
  for (size_t j = 0; j != 5; j++)
    A[i][j] = 0;

V535. The variable 'X' is being used for this loop and for the outer loop.

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

size_t i, j;
for (i = 0; i != 5; i++)
  for (i = 0; i != 5; i++)
    A[i][j] = 0;

Конечно, это искусственный пример и в реальном приложении ошибка может быть не так очевидна. Корректный вариант кода:

size_t i, j;
for (i = 0; i != 5; i++)
  for (j = 0; j != 5; j++)
    A[i][j] = 0;

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

for(c = lb; c <= ub; c++)
{
  if (!(xlb <= xlat(c) && xlat(c) <= ub))
  {
    Range * r = new Range(xlb, xlb + 1);
    for (c = lb + 1; c <= ub; c++)
      r = doUnion(
        r, new Range(xlat(c), xlat(c) + 1));
    return r;
  }
}

В этом коде внутренний цикл "for (c = lb + 1; c <= ub; c++)" организован при помощи переменной "c". Внешний цикл также использует переменную "c". Но ошибки здесь нет. После того, как выполнится внутренний цикл, сразу произойдет выход из функции при помощи оператора "return r". К сожалению, анализатор не может разобраться в подобной ситуации. Вы можете использовать другую переменную для внутреннего цикла или подавить предупреждение с помощью комментария "//-V535".

V536. Be advised that the utilized constant value is represented by an octal form.

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

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

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

inline 
void elxLuminocity(const PixelRGBf& iPixel,
                   LuminanceCell< PixelRGBf >& oCell)
{
  oCell._luminance = 0.2220f*iPixel._red +
                     0.7067f*iPixel._blue +
                     0.0713f*iPixel._green;
  oCell._pixel = iPixel;
}

inline 
void elxLuminocity(const PixelRGBi& iPixel,
                   LuminanceCell< PixelRGBi >& oCell)
{
  oCell._luminance = 2220*iPixel._red +
                     7067*iPixel._blue +
                     0713*iPixel._green;
  oCell._pixel = iPixel;
}  

Рассматривая подобный код непросто заметить ошибку, но она есть. Первая функция elxLuminocity корректна и работает со значения типа float. В коде имеются следующие константы: 0.2220f, 0.7067f, 0.0713f. Вторая функция аналогична первой, но работает с целыми значениями. Все целые значения умножены на 10000. Вот эти значения: 2220, 7067, 0713. Ошибка в том, что последняя константа "0713" задана в восьмеричной системе счисления и имеет значение вовсе не 713, а 459. Исправленный вариант кода:

oCell._luminance = 2220*iPixel._red +
                   7067*iPixel._blue +
                   713*iPixel._green; 

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

static unsigned short bytebit[8] = {
  01, 02, 04, 010, 020, 040, 0100, 0200 };

V537. Consider reviewing the correctness of 'X' item's usage.

Анализатор обнаружил потенциально возможную опечатку в коде. Данное правило пытается эвристическим методом обнаружить ошибку схожую со следующей:

int x = static_cast<int>(GetX()) * n;
int y = static_cast<int>(GetX()) * n;

Во второй строке вместо функции GetY() используется GetX(). Корректный код:

int x = static_cast<int>(GetX()) * n;
int y = static_cast<int>(GetY()) * n;

Для обнаружения этого подозрительного места анализатор следовал следующей логике. Мы имеем строку, где используется имя, включающее в себя фрагмент "X". Рядом с ней есть строка, где используется имя - антипод, содержащая "Y". Но при этом во второй строке также есть и "X". Так как выполнилось это и еще некоторые условия, данную конструкцию следует считать нуждающейся в проверке программистом. Если бы, например, слева не было переменных "x" и "y" то такой код не считался бы опасным. Пример кода, на который анализатор не обратит внимания:

array[0] = GetX() / 2;
array[1] = GetX() / 2;

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

halfWidth -= borderWidth + 2;
halfHeight -= borderWidth + 2;
Анализатор предположил, что возможно второй строкой должно быть иное выражение, например: "halfHeight -= borderHeight + 2". На самом деле никакой ошибки нет. Размер границы (border) одинаков по вертикали и горизонтали. Такой константы как borderHeight просто не существует. Однако подобные высокоуровневые абстракции не доступны анализатору. Чтобы убрать предупреждение вы можете вписать в код комментарий "//-V537".
Анализатор предположил, что возможно второй строкой должно быть иное выражение, например: "halfHeight -= borderHeight + 2". На самом деле никакой ошибки нет. Размер границы (border) одинаков по вертикали и горизонтали. Такой константы как borderHeight просто не существует. Однако подобные высокоуровневые абстракции не доступны анализатору. Чтобы убрать предупреждение вы можете вписать в код комментарий "//-V537".

V538. The line contains control character 0x0B (vertical tabulation).

В тексте программы встретились управляющие ASCII символы. Таким символом может быть:

0x0B - LINE TABULATION (vertical tabulation) - Перемещает позицию печати к следующей позиции вертикальной табуляции. На терминалах этот символ обычно эквивалентен переводу строки.

Наличие подобных символов в тексте программы допустимо и такой текст успешно компилируется в Visual C++. Однако, скорее всего эти символы попали в текст программы случайно и лучше от них избавиться. Для этого есть 2 причины:

1) Если подобный управляющий символ находится в первых строках файла, то среда Visual Studio не может понять формат файла и открывает его не с помощью встроенного редактора, а в программе Notepad.

2) Некоторые внешние инструменты, работающие с текстами программ, могут некорректно обработать файлы, содержащие перечисленные управляющие символы.

Символы 0x0B не видны в редакторе Visual Studio 2010. Чтобы найти их в строке и удалить, можно открыть файл в программе Notepad или в другом редакторе, отображающим подобные управляющие символы.

V539. Consider inspecting iterators which are being passed as arguments to function 'Foo'.

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

Рассмотрим несколько примеров, демонстрирующих ситуации, когда будет выдано данное предупреждение:

Пример 1.

void X(std::vector<int> &X, std::vector<int> &Y)
{
  std::for_each (X.begin(), X.end(), SetValue);
  std::for_each (Y.begin(), X.end(), SetValue);
}

В функции происходит заполнение двух массивов некими значениями. Из-за опечатки при втором вызове функции "std::for_each" ей передаются итераторы от различных контейнеров, что приведет к ошибке на этапе исполнения программы. Корректный код:

std::for_each (X.begin(), X.end(), SetValue);
std::for_each (Y.begin(), Y.end(), SetValue);

Пример 2.

std::includes(a.begin(), a.end(), a.begin(), a.end());

Данный код странен, и скорее всего планировалось обрабатывать две различные последовательности, а не одну. Корректный вариант кода:

std::includes(a.begin(), a.end(), b.begin(), b.end());

V540. Member 'x' should point to string terminated by two 0 characters.

В Windows API есть структуры, в которых указатели на строки должны заканчиваться двойным нулем. В качестве примера можно привести член lpstrFilter в структуре OPENFILENAME.

Описание lpstrFilter в MSDN:

LPCTSTR

A buffer containing pairs of null-terminated filter strings. The last string in the buffer must be terminated by two NULL characters.

Как следует из этого описания, в конце строки мы должны обязательно написать дополнительный ноль. Пример: lpstrFilter = "All Files\0*.*\0";

Однако многие забывают о дополнительном нуле в конце строки. Пример некорректного кода обнаруженный в одном из приложений:

lofn.lpstrFilter = L"Equalizer Preset (*.feq)\0*.feq";

Подобный код приведет к тому, что в диалоге работы с файлом в поле фильтров мы можем увидеть мусор. Исправленный код:

lofn.lpstrFilter = L"Equalizer Preset (*.feq)\0*.feq\0";

Мы явно в конце строки написали 0, и еще один ноль добавит компилятор. Некоторые для большей наглядности пишут так:

lofn.lpstrFilter     = L"Equalizer Preset (*.feq)\0*.feq\0\0";

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

Есть и другие структуры, помимо OPENFILENAME, в которых можно допустить схожие ошибки. Например, двойным нулем должны заканчиваться строки lpstrGroupNames, lpstrCardNames в структурах OPENCARD_SEARCH_CRITERIA, OPENCARDNAME.

V541. It is dangerous to print the string into itself.

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

char s[100] = "test";
sprintf(s, "N = %d, S = %s", 123, s);

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

N = 123, S = test

Но на практике в буфере будет сформирована строка:

N = 123, S = N = 123, S =

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

char s1[100] = "test";
char s2[100];
sprintf(s2, "N = %d, S = %s", 123, s1);

V542. Consider inspecting an odd type cast: 'Type1' to ' Type2'.

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

Пример:

typedef unsigned char Byte;

void Process(wchar_t ch);
void Process(wchar_t *str);

void Foo(Byte *buf, size_t nCount)
{
  for (size_t i = 0; i < nCount; ++i)
  {
    Process((wchar_t *)buf[i]);
  }
}

Мы имеем функцию Process, которая умеет обрабатывать как отдельные символы, так и строки. Также мы имеем функцию 'Foo', которая на вход получает указатель на буфер. Этот буфер обрабатывается как массив символов типа wchar_t. Но код содержит ошибку и анализатор предупреждает о том, что тип 'char' явно приводится к типу ' wchar_t *'. Причина в том, что выражение "(wchar_t *)buf[i]" эквивалентно "(wchar_t *)(buf[i])". В начале из массива извлекается значение типа 'char', а затем оно превращается в указатель. Исправленный вариант кода:

Process(((wchar_t *)buf)[i]);

Однако не всегда, странные приведения типов являются ошибкой. Рассмотрим пример безопасного кода, взятого из реального приложения:

wchar_t *destStr = new wchar_t[len+1];
...
for (int j = 0 ; j < nbChar ; j++)
{
  if (Case == UPPERCASE)
    destStr[j] =
      (wchar_t)::CharUpperW((LPWSTR)destStr[j]);
  ...

Здесь присутствует явное приведение типа 'wchar_t ' к 'LPWSTR' и обратно от типа 'LPWSTR' к 'wchar_t '. Дело в том, что Windows API функция CharUpperW может работать с входным значением, и как с указателем, и как с символом. Прототип функции:

LPTSTR WINAPI CharUpperW(__inout LPWSTR lpsz);

Если старшая часть указателя равна 0, то входное значение считается символом. В противном случае функция обрабатывает строку.

Анализатор знает про поведение функции CharUpperW и считает данный код безопасным. Однако в другой подобной ситуации анализатор может выдать ложное срабатывание.

V543. It is odd that value 'X' is assigned to the variable 'Y' of HRESULT type.

Анализатор обнаружил потенциальную ошибку при работе с переменной типа HRESULT.

HRESULT - это 32-разрядное значение, разделенное на три различных поля: код серьезности ошибки, код устройства и код ошибки. Для работы со значением HRESULT служат специальные константы, такие как S_OK, E_FAIL, E_ABORT и так далее. А для проверки значений тип HRESULT предназначены такие макросы как SUCCEEDED, FAILED.

Предупреждение V543 выдается в том случае, если в переменную типа HRESULT пытаются записать значение -1, true или false. Рассмотрим пример:

HRESULT h;
...
if (bExceptionCatched)
{
  ShowPluginErrorMessage(pi, errorText);
  h = -1;
}

Запись значения "-1" некорректна. Если хочется сообщить о какой-то непонятной ошибке, то следует использовать значение 0x80004005L (Unspecified failure). Эта и аналогичные константы, описаны в "WinError.h". Корректный код:

if (bExceptionCatched)
{
  ShowPluginErrorMessage(pi, errorText);
  h = E_FAIL;
}

Дополнительные ресурсы:

V544. It is odd that the value 'X' of HRESULT type is compared with 'Y'.

Анализатор обнаружил потенциальную ошибку при работе с переменной типа HRESULT.

HRESULT- это 32-разрядное значение, разделенное на три различных поля: код серьезности ошибки, код устройства и код ошибки. Для работы со значением HRESULT служат специальные константы, такие как S_OK, E_FAIL, E_ABORT и так далее. А для проверки значений тип HRESULT предназначены такие макросы как SUCCEEDED, FAILED.

Предупреждение V544 выдается в том случае, если переменную типа HRESULT пытаются сравнить с -1, true или false. Рассмотрим пример:

HRESULT hr;
...
if (hr == -1)
{
}

Сравнение со значением "-1" некорректно. Коды ошибок могут быть различны. Например, это может быть 0x80000002L (Ran out of memory), 0x80004005L (unspecified failure), 0x80070005L (General access denied error) и так далее. Для проверки значения HRESULT в данном случае необходимо использовать макрос FAILED, объявленный в "WinError.h". Корректный вариант кода:

if (FAILED(hr))
{
}

Дополнительные ресурсы:

V545. Such conditional expression of 'if' operator is incorrect for the HRESULT type value 'Foo'. The SUCCEEDED or FAILED macro should be used instead.

Анализатор обнаружил потенциальную ошибку при работе с переменной типа HRESULT.

HRESULT - это 32-разрядное значение, разделенное на три различных поля: код серьезности ошибки, код устройства и код ошибки. Для работы со значением HRESULT служат специальные константы, такие как S_OK, E_FAIL, E_ABORT и так далее. А для проверки значений тип HRESULT предназначены такие макросы как SUCCEEDED, FAILED.

Предупреждение V545 выдается в том случае, если переменная типа HRESULT используется в операторе 'if' как переменная типа bool. Пример:

HRESULT hr;
...
if (hr)
{
}

HRESULT и тип bool это совершенно разные по смыслу типы. Показанный пример сравнения некорректен. Тип HRESULT имеет множество состояний. Это может быть 0L (S_OK), 0x80000002L (Ran out of memory), 0x80004005L (unspecified failure), и так далее. Обратите внимание, что состояние S_OK кодируется как 0.

Для проверки значения HRESULT необходимо использовать макрос SUCCEEDED или FAILED, объявленные в "WinError.h". Корректные варианты кода:

if (FAILED(hr))
{
}
if (SUCCEEDED(hr))
{
}

Дополнительные ресурсы:

V546. Member of a class is initialized by itself: 'Foo(Foo)'.

Анализатор обнаружил опечатку, когда член класса инициализируется самим собой. Рассмотрим пример конструктора:

C95(int field) : Field(Field)
{
    ...
}

Здесь имя аргумента и название члена класса отличается только одной буквой. Из-за этого допущена опечатка и члена Field останется неинициализированным. Исправленный вариант кода:

C95(int field) : Field(field)
{
    ...
}

V547. Expression is always true/false.

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

Пример кода:

LRESULT CALLBACK GridProc(HWND hWnd,
  UINT message, WPARAM wParam, LPARAM lParam)
{
  ...
  if (wParam<0)
  {
    BGHS[SelfIndex].rows = 0;
  }
  else
  {
    BGHS[SelfIndex].rows = MAX_ROWS;
  }
  ...
}

Здесь ветка "BGHS[SelfIndex].rows = 0;" никогда не будет выполнена. Дело в том, что переменная wParam имеет беззнаковый тип WPARAM, который объявлен как "typedef UINT_PTR WPARAM".

Этот код или содержит логическую ошибку, или может быть сокращен до одной строки: "BGHS[SelfIndex].rows = MAX_ROWS;".

Теперь рассмотрим пример кода, который не является ошибочным, но он потенциально опасен и имеет бессмысленное сравнение:

unsigned int a = _ttoi(LPCTSTR(str1));
if((0 > a) || (a > 255))
{
  return(FALSE);
}

Программист хотел реализовать следующий алгоритм.

1) Превратить строку в число.

2) Если число лежит вне диапазона [0..255] то вернуть статус ошибки (return FALSE).

Ошибка заключается в использовании типа 'unsigned'. Если функция _ttoi вернет отрицательное значение, то оно превратится в большое положительное значение. Например, значение "-3" превратится в 4294967293. Сравнение '0 > a' всегда вернет true. Программа корректно работает из-за того, что диапазон значений [0..255] проверяется условием 'a > 255'.

Данный фрагмент кода будет диагностирован так: "V547 Expression '0 > a' is always false. Unsigned type value is never < 0."

Этот фрагмент лучше исправить следующим образом:

int a = _ttoi(LPCTSTR(str1));
if((0 > a) || (a > 255))
{
  return(FALSE);
}

Рассмотрим один специальный случай. Анализатор выдает предупреждение:

V547 Expression 's == "Abcd"' is always false. To compare strings you should use strcmp() function.

на следующий код:

const char *s = "Abcd";
void Test()
{
  if (s == "Abcd")
    cout << "TRUE" << endl;
  else
    cout << "FALSE" << endl; 
}

Однако это не совсем верно. Этот код может все-таки распечатать "TRUE", если переменная 's' и функция Test() объявлены в одном модуле. Компилятор не плодит множество одинаковых константных строк, а использует одну. В результате, иногда кажется, что код вполне работоспособен. Однако надо понимать, что это очень плохой код и следует использовать специальные функции для сравнения.

Следующий пример:

if (lpszHelpFile != 0)
{
  pwzHelpFile = ((_lpa_ex = lpszHelpFile) == 0) ?
0 : Foo(lpszHelpFile);
  ...
}

Этот код работает вполне корректно, но он излишне запутан. Условие "((_lpa_ex = lpszHelpFile) == 0)" всегда ложно, так как указатель lpszHelpFile всегда не равен нулю. Этот код сложен для чтения, и его лучше переписать.

Упрощенный вариант кода:

if (lpszHelpFile != 0)
{
  _lpa_ex = lpszHelpFile;
  pwzHelpFile = Foo(lpszHelpFile);
  ...
}

Анализатор предупреждает далеко не про все условия, которые всегда ложны или истинны. Он диагностирует только те ситуации, где высока вероятность наличия ошибки. Рассмотрим некоторые примеры, которые анализатор считает абсолютно корректными:

// 1) Вечный цикл
while (true)
{
...
}

// 2) Развернутый в Release версии макрос
// MY_DEBUG_LOG("X=", x);
0 && ("X=", x);

// 3) assert(false);
if (error) {
  assert(false);
  return -1;
}

V548. Consider reviewing type casting. TYPE X[][] in not equivalent to TYPE **X.

Анализатор обнаружил потенциально возможную ошибку, связанную с явным приведением типа. В программе массив, объявленный например как "type Array[3][4]", приводится к типу "type **". Это приведение типа, скорее всего, не имеет смысла.

Типы "type[a][b]" и "type **" представляют собой разные структуры данных. Type[a][b] это единый участок памяти с которым можно работать как с двумерным массивом. Type ** - это массив указателей на какие-то участки памяти.

Пример:

void Foo(char **names, size_t count)
{
  for(size_t i=0; i<count; i++)
    printf("%s\n", names[i]);
}

void Foo2()
{
  char names[32][32];
  ...
  F103_((char **)names, 32); //Crash
}

Исправленный вариант:

void Foo2()
{
  char names[32][32];
  ...
  char *names_p[32];
  for(size_t i=0; i<32; i++)
    names_p[i] = names[i];
  F103_(names_p, 32); //OK
}

V549. The 'first' argument of 'Foo' function is equal to the 'second' argument.

Анализатор обнаружил потенциальную ошибку в программе, связанную с тем, что совпадают два фактических аргумента функции. Передача одного и того же значения в качестве двух аргументов для многих функций является нормальной ситуацией. Но если речь идет о таких функциях как memmove, memcpy, strstr, strncmp, то это очень подозрительная ситуация.

Пример из реального приложения:

#define str_cmp(s1, s2)  wcscmp(s1, s2)
...
v = abs(str_cmp(a->tdata, a->tdata));

Из-за опечатки функция wcscmp сравнивает строку саму с себя. Корректным вариантом кода должно было быть:

v = abs(str_cmp(a->tdata, b->tdata));

Анализатор выдаст предупреждение в случае работы с функциями: memcpy, memmove, memcmp, _memicmp, strstr, strspn, strtok, strcmp, strncmp, wcscmp, _stricmp, wcsncmp и так далее. Если вы обнаружили подобную ошибку, которая не диагностируется PVS-Studio, то просим сообщить нам имя функции, которая также не должна принимать в качестве первого и второго аргумента одинаковые значения.

V550. An odd precise comparison. It's probably better to use a comparison with defined precision: fabs(A - B) < Epsilon or fabs(A - B) > Epsilon.

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

Рассмотрим пример:

double a = 0.5;
if (a == 0.5) //OK
  x++;

double b = sin(M_PI / 6.0);
if (b == 0.5) //ERROR
  x++;

Первое сравнение 'a == 0.5' истинно. Второе сравнение 'b == 0.5' может быть как истинно, так и ложно. Результат выражения 'b == 0.5' зависит от используемого процессора, версии и настроек компилятора. Например, значение переменной 'b' было равно 0.49999999999999994 при использовании компилятора Visual C++ 2010. Более корректно этот код можно написать следующим образом:

double b = sin(M_PI / 6.0);
if (fabs(b - 0.5) < DBL_EPSILON)
  x++;

В данном случае сравнение с погрешностью DBL_EPSILON верно, так как результат функции sin() лежит в диапазоне [-1, 1]. Однако, если мы работам со значениями больше нескольких единиц, то такие погрешности как FLT_EPSILON, DBL_EPSILON будут слишком малы. И наоборот при работе со значениями типа 0.00001 эти погрешности слишком велики. Каждый раз следует выбирать погрешность адекватную диапазону возможных значений.

Возникает вопрос. Как же все-таки сравнить две переменных типа double?

double a = ...;
double b = ...;
if (a == b) // how?
{
}

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

if (fabs(a-b) <= DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
}

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

А можно ли все-таки точно сравнивать значения в формате с плавающей точкой?

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

Пример, где допустимо точное сравнение:

// -1 - значение не инициализировано.
float val = -1.0f;
if (Foo1())
  val = 123.0f;
if (val == -1.0f) //OK
{
}

В данном случае сравнение со значением "-1" допустимо, так как именно точно таким же значением мы инициализировали переменную ранее.

В рамках документации невозможно более подробно раскрыть тему сравнения float/double типов, поэтому мы отсылаем вас к внешнем источникам информации приведенных в конце.

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

Диагностическое сообщение не выдается, если сравниваются два идентичных выражения типа float или double. Такое сравнение позволяет определить, является ли значение NaN. Пример кода, реализующего подобную проверку:

bool isnan(double X) { return X != X; }

Дополнительные ресурсы:

V551. The code under this 'case' label is unreachable.

Анализатор обнаружил потенциальную ошибку связанную с тем, что одна из ветвей оператора switch() никогда не получит управление. Причина этого в том, что аргумент оператора switch() не может принять значение, которое прописано в операторе case. Рассмотрим пример:

char ch = strText[i];
switch (ch)
{
case '<':
  strHTML += "&lt;";
  bLastCharSpace = FALSE;
  nNonbreakChars++;
  break;
case '>':
  strHTML += "&gt;";
  bLastCharSpace = FALSE;
  nNonbreakChars++;
  break;
case 0xB7:
case 0xBB:
  strHTML += ch;
  strHTML += "<wbr>";
  bLastCharSpace = FALSE;
  nNonbreakChars = 0;
  break;
...
}

Здесь ветка расположенная после "case 0xB7:" и "case 0xBB:" никогда не получит управление. Переменная 'ch' имеет тип 'char', а, следовательно, диапазон её значений лежит в пределах [-128..127]. Результатом сравнения "ch == 0xB7" и "ch==0xBB" всегда будет лож (false). Чтобы код был корректен переменная 'ch' должна иметь тип 'unsigned char'. Исправленный код:

unsigned char ch = strText[i];
switch (ch)
{
...
case 0xB7:
case 0xBB:
  strHTML += ch;
  strHTML += "<wbr>";
  bLastCharSpace = FALSE;
  nNonbreakChars = 0;
  break;
...
}

V552. A bool type variable is being incremented. Perhaps another variable should be incremented instead.

Анализатор обнаружил потенциально опасную конструкция в коде, где происходит инкремент переменной типа bool:

bool bValue = false;
...
bValue++;

Во-первых, стандарт языка Си++ говорит:

The use of an operand of type bool with the postfix ++ operator is deprecated.

Это значит, что подобную конструкцию лучше не использовать.

Во-вторых лучше явно присвоить переменной значение типа true. Это более понятный код:

bValue = true;

В-третьих, возможно имеется опечатка и на самом деле хотелось увеличить другую переменную. Пример:

bool bValue = false;
int iValue = 1;
...
if (bValue)
  bValue++;

Случайно использовали не ту переменную и на самом деле должно быть:

bool bValue = false;
int iValue = 1;
...
if (bValue)
  iValue++;

V553. The length of function's body or class's declaration is more than 2000 lines long. You should consider refactoring the code.

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

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

Дополнительные материалы по данной теме:

  • Макконнелл С. Совершенный код. Мастер-класс / Пер. с англ. - М. : Издательско-торговый дом "Русская редакция"; СПб.: Питер, 2005. - 896 стр.: ил. ISBN 5-7502-0064-7. (Смотрите часть 7.4. Насколько объемным может быть метод?).

V554. Incorrect use of smart pointer.

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

Рассмотрим первый пример:

void Foo()
{
  struct A
  {
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
  };

  std::unique_ptr<A> p(new A[3]);
}

По умолчанию класс unique_ptr использует для освобождения памяти оператор 'delete'. Поэтому будет разрушен только один объект класса 'A' и на экране будет распечатан следующий текст:

A()
A()
A()
~A()

Чтобы исправить ошибку необходимо указать, что необходимо использовать оператор 'delete []'. Корректный вариант кода:

std::unique_ptr<A[]> p(new A[3]);

Теперь будет вызываться равное количество конструкторов и деструкторов и будет распечатан текст:

A()
A()
A()
~A()
~A()
~A()

Рассмотрим второй пример:

std::unique_ptr<int []> p((int *)malloc(sizeof(int) * 5));

Память выделяется с использованием функции 'malloc()', а освобождается при помощи оператора 'delete []'. Это некорректно и следует указать, что освобождать память надо используя функцию 'free()'. Корректный вариант кода может выглядеть следующим образом:

int *d =(int *)std::malloc(sizeof(int) * 5);
unique_ptr<int, void (*)(void*)> p(d, std::free);

Дополнительные материалы по данной теме:

V555. The expression of the 'A - B > 0' kind will work as 'A != B'.

Анализатор обнаружил потенциальную ошибку в выражении вида "A - B > 0". Высока вероятность, что условие написано неверно, если подвыражение "A - B" имеет беззнаковый тип.

Условие "A - B > 0" выполняется во всех случаях, когда значение 'A' не равно значению 'B'. Это значит, что вместо выражения "A - B > 0" можно написать "A != B". Но скорее всего программист задумывал совсем другое.

Рассмотрим пример:

unsigned int *B;
...
if (B[i]-70 > 0)

Программист хотел проверить, что i-тый элемент массива B больше значения 70. Это можно было записать так: "B[i] > 70". Исходя из каких-то своих соображений, программист записал эту проверку так: "B[i]-70 > 0". И допустил ошибку. Он забыл, что элементы массива 'B' имеют тип 'unsigned'. Это значит, что и выражение "B[i]-70" будет иметь тип 'unsigned'. Получается, что условие всегда истинно, за исключением случая, когда элемент 'B[i]' будет равен 70.

Поясним эту ситуацию.

Если 'B[i]' больше 70, то "B[i]-70" будет больше 0.

Если 'B[i]' меньше 70, то произойдет переполнение типа unsigned и мы получим очень большое значение. Пусть B[i] == 50. Тогда "B[i]-70" = 50u - 70u = 0xFFFFFFECu = 4294967276. Естественно, что 4294967276 > 0.

Демонстрационный пример:

unsigned A;
A = 10; cout << "A=10 " << (A-70 > 0) << endl;
A = 70; cout << "A=70 " << (A-70 > 0) << endl;
A = 90; cout << "A=90 " << (A-70 > 0) << endl;
// Будет распечатано
A=10 1
A=70 0
A=90 1

Первый вариант исправленного кода:

unsigned int *B;
...
if (B[i] > 70)

Второй вариант исправленного кода:

int *B;
...
if (B[i]-70 > 0)

Отметим, что выражение вида "A - B > 0" далеко не всегда означает наличие ошибки. Рассмотрим код, где анализатор выдаст ложное предупреждение:

// Функции  GetLength() и GetPosition() возвращают
// значение типа size_t.
while ((inStream.GetLength() - inStream.GetPosition()) > 0)
{ ... }

Здесь GetLength() всегда больше или равно GetPosition(). Поэтому код корректен. Чтобы избавиться от ложного срабатывания можно использовать комментарий //-V555 или переписать код следующим образом:

while (inStream.GetLength() != inStream.GetPosition())
{ ... }

Вот еще один случай, когда ошибки не возникнет.

__int64 A;
__uint32 B;
...
if (A - B > 0)

Здесь подвыражение "A - B" имеет знаковый тип __int64 и ошибки не возникнет. Анализатор PVS-Studio не выдает предупреждения в таких ситуациях.

V556. The values of different enum types are compared.

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

Пример:

enum ErrorTypeA { E_OK, E_FAIL };
enum ErrorTypeB { E_ERROR, E_SUCCESS };
void Foo(ErrorTypeB status) {
  if (status == E_OK)
  { ... }
}

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

void Foo(ErrorTypeB status) {
   if (status == E_ SUCCESS)
  { ... }
}

Сравнение значений различных типов перечислений (enum) не обязательно является ошибкой. Но подобный код нуждается в проверке (code review).

V557. Array overrun is possible.

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

struct IT_SAMPLE
{
  unsigned char filename[14];
  ...
};

static int it_riff_dsmf_process_sample(
  IT_SAMPLE * sample, const unsigned char * data)
{
  memcpy( sample->filename, data, 13 );
  sample->filename[ 14 ] = 0;
  ...
}

Последним элемент массива имеет индекс 13, а не 14. Поэтому корректный код должен иметь вид:

sample->filename[13] = 0;

Конечно, в подобных случаях лучше использовать не явное значение индекса, а выражение использующее оператор sizeof(). Однако следует помнить, что и в этом случае можно допустить ошибку. Рассмотрим пример:

typedef wchar_t letter;
letter    name[30];
...
name[sizeof(name) - 1] = L'\0';

На первый взгляд выражение "sizeof(name) - 1" верно. Но здесь программист забыл, что он работает с типом 'wchar_t', а не 'char'. В результате символ '\0' будет записано далеко за пределами массива. Корректный вариант:

name[sizeof(name) / sizeof(*name) - 1] = L'\0';

Для упрощения записи таких конструкций можно использовать специальный макрос:

#define str_len(arg) ((sizeof(arg) / sizeof(arg[0])) - 1)
name[str_len(name)] = L'\0';

Анализатор выявляет некоторые ошибки, когда индексом является переменная, значение которой может выйти за пределами массива. Пример:

int buff[25];
for (int i=0; i <= 25; i++)
  buff[i] = 10;

Корректный вариант:

int buff[25];
for (int i=0; i < 25; i++)
  buff[i] = 10;
Следует учитывать, что при работе с подобными диапазонами значений анализатор может ошибаться и выдавать ложные срабатывания.

V558. Function returns the pointer to temporary local object.

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

float *F()
{
  float f = 1.0;
  return &f;
}

Конечно, в таком виде ошибка вряд ли будет существовать. Рассмотрим более реальный пример кода.

int *Foo()
{
  int A[10];
  // ...
  if (err)
    return 0;
  
  int *B = new int[10];
  memcpy(B, A, sizeof(A));

  return A;
}

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

int *Foo()
{
  ...  
  int *B = new int[10];
  memcpy(B, A, sizeof(A));
  return B;
}

V559. Suspicious assignment inside the condition expression of 'if/while/for' operator.

Анализатор обнаружил ситуацию, когда в условии оператора 'if' или 'while' присутствует оператор присваивания '='. При этом переменной присваивается константное значение. Подобная конструкция часто свидетельствует о наличии ошибок. Высока вероятность, что вместо оператора '=' планировалось использовать оператор '=='.

Рассмотрим пример:

const int MAX_X = 100;
int x;
...
if (x = MAX_X)
{ ... }

В коде допущена опечатка. Вместо сравнения переменной 'x' c константной MAX_X, в коде произойдет изменение значения переменной 'x'. Корректный вариант кода:

if (x == MAX_X)
{ ... }

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

while (x = get())
{
  ...
}

V560. A part of conditional expression is always true/false.

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

Рассмотрим пример:

#define REO_INPLACEACTIVE (0x02000000L)
...
if (reObj.dwFlags && REO_INPLACEACTIVE)
  m_pRichEditOle->InPlaceDeactivate();

Программист хотел проверить состояние определенного бита в переменной dwFlags. Но из-за опечатки он написал оператор '&&', вместо оператора '&'. Корректный код:

if (reObj.dwFlags & REO_INPLACEACTIVE)
  m_pRichEditOle->InPlaceDeactivate();

Рассмотрим другой пример:

if (a = 10 || a == 20)

Случайно вместо оператора сравнения '==' написан оператор присваивания '='. С точки зрения языка Си++, это выражение будет идентично выражению вида "if (a = (10 || a == 20))".

Выражение "10 || a == 20" анализатор считает опасным, так как левая его часть представляет собой константу. Корректный код:

if (a == 10 || a == 20)

Иногда предупреждение V560 выявляет не ошибку, а просто избыточный код. Рассмотрим пример:

if (!mainmenu) {
  if (freeze || winfreeze ||
      (mainmenu && gameon) ||
      (!gameon && gamestarted))
    drawmode = normalmode;
}

Анализатор предупредит, что в подвыражении (mainmenu && gameon) переменная mainmenu всегда равна 0. То, что переменная mainmenu равна нулю, следует из вышестоящей проверки " if (!mainmenu)". Этот код может быть вполне корректен. Однако он избыточен, и лучше его упростить. Это сделает программу более простой для понимания другими разработчиками.

Упрощенный вариант кода:

if (!mainmenu) {
  if (freeze || winfreeze ||
      (!gameon && gamestarted))
    drawmode = normalmode;
}

Ряд конструкций на языке Си++ анализатор считает безопасными, даже если в них часть выражения представляется константой. Примеры некоторых ситуаций, когда анализатор не считает код опасными:

  • подвыражение содержит операторы sizeof(): if (a == b && sizeof(T) < sizeof(__int64)) {};
  • выражение находится в макросе: assert(false);
  • сравниваются две числовых константы: if (MY_DEFINE_BITS_COUNT == 4) {};
  • и так далее.

V561. It's probably better to assign value to 'foo' variable than to declare it anew.

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

Рассмотрим пример:

BOOL ret = TRUE;
if (m_hbitmap)
  BOOL ret = picture.SaveToFile(fptr);

Программист случайно объявил новую переменную 'ret', в результате чего предыдущая всегда будет иметь значение TRUE, вне зависимости от того успешно будет сохранена картина в файл или нет. Исправленный вариант кода:

BOOL ret = TRUE;
if (m_hbitmap)
  ret = picture.SaveToFile(fptr);

V562. It's odd to compare a bool type value with a value of N.

Анализатор обнаружил ситуацию, когда значение типа bool сравнивается с числом, неравным 0 или 1. Скорее всего, это свидетельствует о наличии в коде ошибки.

Рассмотрим пример:

if (0 < A < 5)

Этим кодом программист, недостаточно хорошо знакомый с языком Си++, пытался определить, лежит значение в диапазоне от 0 до 5 или нет. На самом деле, вычисления будут происходить в следующей последовательности: ((0 < A) < 5). Результат выражения "0 < A" имеет тип bool, а значит всегда меньше 5.

Корректный вариант проверки:

if (0 < A && A < 5)

Предыдущий пример, напоминает ошибку, которые делают студенты. Однако от подобных ошибок не застрахованы и профессиональные разработчики.

Рассмотрим второй пример:

if (! (fp = fopen(filename, "wb")) == -1) {
  perror("opening image file failed");
  exit(1);
}

Здесь сразу 2 ошибки разного плана. Во-первых, функция "fopen" возвращает указатель, и сравнивать возвращаемое значение следует с NULL. Программист спутал функцию "fopen" с "open", которая как раз возвращает "-1" в случае ошибки. Второй недочет в коде связан с тем, что вначале выполняется операция отрицания "!", а только затем сравнение с "-1". Значение типа bool не имеет смысла сравнивать с "-1" и именно поэтому анализатор PVS-Studio и обратил внимание на приведенный код.

Корректный вариант:

if ( (fp = fopen(filename, "wb")) == NULL) {
  perror("opening image file failed");
  exit(1);
}

V563. It is possible that this 'else' branch must apply to the previous 'if' statement.

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

Рассмотрим пример:

if (X)
  if (Y) Foo();
else
  z = 1;

Форматирование кода сбивает с толку, и кажется, что присваивание "z = 1" произойдет в том случае, если X == false. Однако ветка 'else' относится к ближайшему оператору 'if'. Другими словами приведенный код на самом деле эквивалентен следующему коду:

if (X)
{
    if (Y)
      Foo();
    else
      z = 1;
}

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

Если выдано предупреждение V563, то это может означать две вещи:

1) Код плохо отформатирован и ошибки на самом деле нет. Тогда, чтобы предупреждение V563 не выдавалось, а код был более понятен, его нужно отформатировать. Пример корректного форматирования:

if (X)
  if (Y)
    Foo();
  else
    z = 1;

2) Найдена логическая ошибка. Тогда, код можно исправить, например, так:

if (X) {
  if (Y)
    Foo();
} else {
  z = 1;
}

V564. The '&' or '|' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&&' or '||' operator.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что операторы '&' и '|' работают со значениями типа bool. Подобные выражения не обязательно являются ошибочными, но часто свидетельствуют об опечатках или об ошибках в условиях.

Рассмотрим пример:

int a, b;
#define FLAG 0x40
...
if (a & FLAG == b)
{
}

Этот пример считается классическим. Очень легко ошибиться в приоритетах операций. Кажется, что последовательность вычислений следующая - "(a & FLAG) == b". Но на самом деле, последовательность вычислений будет следующая - "a & (FLAG == b)". И скорее всего, это ошибка.

Анализатор выдаст здесь предупреждение, так как странно использовать оператор '&' для переменных типа int и bool.

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

if ((a & FLAG) == b)

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

if (a && FLAG == b)
if (a & (FLAG == b))

После таких исправлений предупреждение V564 более выдаваться не будет, а код станет более прост для чтения.

Рассмотрим другой пример:

#define SVF_CASTAI 0x00000010
if ( !ent->r.svFlags & SVF_CASTAI ) {
  ...
}

Здесь мы имеем дело с явной ошибкой. В начале будет вычислено подвыражение "!ent->r.svFlags" и получен результат true или fasle. Но это не имеет значения. Будем мы выполнять операцию "true & 0x00000010" или "false & 0x00000010", результат будет одинаков. Условие в данном примере всегда ложно.

Исправленный вариант:

if ( ! (ent->r.svFlags & SVF_CASTAI) )

Примечание. Анализатор не будет выдавать предупреждение, если слава и справа от оператора '&' или '|' находятся значения типа bool. Такой код хотя и не очень красив, но корректен. Пример кода, который анализатор считает безопасным:

bool X, Y;
...
if (X | Y)
{ ... }

V565. An empty exception handler. Silent suppression of exceptions can hide the presence of bugs in source code during testing.

Найден обработчик исключения, который ничего не делает. Пример кода:

try {
  ...
}
catch (MyExcept &)
{
}

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

Следует, как то реагировать на исключения. Например, рационально вписать хотя бы "assert(false)":

try {
  ...
}
catch (MyExcept &)
{
  assert(false);
}

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

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

CClass::~ CClass()
{
  try {
    DangerousFreeResource();
  }
  catch (...) {
  }
}

V566. The integer constant is converted to pointer. Possibly an error or a bad coding style.

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

const DWORD SHELL_VERSION = 0x4110400;
...
char *ptr = (char*) SHELL_VERSION;
...
if (ptr == (char*) SHELL_VERSION)

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

const DWORD SHELL_VERSION = 0x4110400;
...
char *ptr = (char*)(&SHELL_VERSION);
... 
if (ptr == (char*)(&SHELL_VERSION))
if (ptr == (char*)(&SHELL_VERSION))

Примечание. Чтобы сократить количество ложных срабатываний, сообщение V566 не выдается в целом ряде случаев. Например, если магическим числом являются такие значения, как: -1, 0, 0xcccccccc, 0xdeadbeef. Предупреждение не выдается, если число лежит в диапазоне от 0 до 65535 и приводится к указателю на строку. Это позволяет пропустить корректный код следующего вида:

CString sMessage( (LPCSTR)IDS_FILE_WAS_CHANGED ) ;
Подобный способ загрузки строки из ресурсов достаточно распространен, хотя конечно лучше использовать MAKEINTRESOURCE. Есть и другие исключения.

V567. Undefined behavior. The variable is modified while being used twice between sequence points.

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

Неопределённое поведение (англ. undefined behaviour) — свойство некоторых языков программирования (наиболее заметно в Cи и Си++) в определённых ситуациях выдавать результат, зависящий от реализации компилятора. Другими словами, спецификация не определяет поведение языка в любых возможных ситуациях, а говорит: "при условии А результат операции Б не определён". Допускать такую ситуацию в программе считается ошибкой, даже если на некотором компиляторе программа успешно выполняется, она не будет кроссплатформенной и может отказать на другой машине, в другой ОС и даже на других настройках компилятора.

Точка следования (англ. Sequence point) — в императивном программировании любая точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих еще отсутствуют.

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

Точки следования необходимы в ситуации, когда одна и та же переменная изменяется в выражении более одного раза. Часто в качестве примера приводят выражение i=i++, в котором происходит присваивание переменной i и её же инкремент. Какое значение примет i? Стандарт языка должен либо указать одно из возможных поведений программы как единственно допустимое, либо указать диапазон допустимых поведений, либо указать, что поведение программы в данном случае совершенно не определено. В языках Си и Си++ вычисление выражения i=i++ приводит к неопределённому поведению, поскольку это выражение не содержит внутри себя ни одной точки следования.

В Cи и Си++ определены следующие точки следования:

  • Между вычислением левого и правого операндов в операторах && (логическом И), || (логическом ИЛИ) и операторах-запятых. Например, в выражении *p++ != 0 && *q++ != 0 все побочные эффекты левого операнда *p++ != 0 проявятся до начала каких либо действий в правом.
  • Между вычислением первого, второго или третьего операндов в операторе условия. В строке a = (*p++) ? (*p++) : 0 точка находится после первого операнда *p++. При вычислении второго выражения, переменная p уже увеличена на 1.
  • В конце всего выражения. Эта категория включает в себя инструкции-выражения (a=b;), выражения в инструкциях return, управляющие выражения в круглых скобках инструкций ветвления if или switch и циклов while или do-while и все три выражения в круглых скобках цикла for.
  • Перед входом в вызываемую функцию. Порядок, в котором вычисляются аргументы, не определен, но эта точка следования гарантирует, что все ее побочные эффекты проявятся на момент входа в функцию. В выражении f(i++) + g(j++) + h(k++) каждая из трёх переменных: i, j и k, принимает новое значение перед входом в f, g и h соответственно. Однако, порядок вызова функций f(), g(), h() не определён, следовательно, не определён и порядок инкремента i, j, k. Значения j и k в теле функции f оказываются неопределенными. Следует заметить, вызов функции нескольких аргументов f(a,b,c) не является случаем применения оператора-запятой и не определяет порядок вычисления значений аргументов.
  • При возврате из функции, на момент когда возвращаемое значение будет скопировано в вызывающий контекст.(Явно описана только в стандарте С++, в отличие от С.)
  • В объявлении с инициализацией на момент завершения вычисления инициализирующего значения, например, на момент завершения вычисления (1+i++) в int a = (1+i++);.
  • В Си++ перегруженные операторы выступают в роли функций, поэтому точкой следования является вызов перегруженного оператора.

Рассмотрим теперь несколько примеров, приводящих к неопределенному поведению:

int i, j;
...
X[i]=++i;
X[i++] = i;
j = i + X[++i];
i = 6 + i++ + 2000;
j = i++ + ++i;
i = ++i + ++i;

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

while (!(m_pBitArray[m_nCurrentBitIndex >> 5] &
         Powers_of_Two_Reversed[m_nCurrentBitIndex++ & 31]))
{}
return (m_nCurrentBitIndex - BitInitial - 1);

Компилятор может вычислить вначале как левый, так и правый аргумент оператора '&'. Это значит, что переменная m_nCurrentBitIndex может быть уже увеличена на единицу при вычислении "m_pBitArray[m_nCurrentBitIndex >> 5]". А может быть ещё и не увеличена.

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

while (!(m_pBitArray[m_nCurrentBitIndex >> 5] &
         Powers_of_Two_Reversed[m_nCurrentBitIndex & 31]))
{ ++m_nCurrentBitIndex; }
return (m_nCurrentBitIndex - BitInitial);
Этот код более не содержит неоднозначностей. Заодно исчезла магическая константа "-1".

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

Вопрос:

Скачал ознакомительную версию вашей студии, прогнал свой проект и получил такое предупреждение: V567 Undefined behavior. The 'i_acc' variable is modified while being used twice between sequence points.

Код

i_acc = (++i_acc) % N_acc;

Как мне кажется, здесь нет undefined behavior, так как переменная i_acc не участвует в выражении дважды.

Ответ:

Неопределенное поведение здесь есть. Другое дело, что вероятность проявления ошибки весьма мало. Оператор '=' не является точкой следования. Это значит, что вначале компилятор может поместить значение переменной i_acc в регистр. Затем увеличить значение в регистре. После чего вычислить выражение и записать результат в переменную i_acc. После чего вновь записать в эту переменную регистр с увеличенным значением. В результате мы получим код вида:

REG = i_acc;
REG++;
i_acc = (REG) % N_acc;
i_acc = REG;

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

Дополнительные ресурсы

V568. It's odd that the argument of sizeof() operator is the expression.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что аргументом оператора sizeof() является подозрительное выражение. Подозрительные выражения можно разделить на две группы:

1. В выражении пытаются изменить какую-то переменную.

Оператор sizeof() вычисляет тип выражения и возвращает размер этого типа. Но само выражение не вычисляется. Пример подозрительного кода:

int A;
...
size_t size = sizeof(A++);

Данный код не увеличивает переменную 'A'. Если необходимо увеличить 'A' то следует переписать код следующим образом:

size_t size = sizeof(A);
A++;

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

Сложные выражение являются признаком наличия ошибки. Чаше всего эти ошибки связаны с опечатками. Пример:

SendDlgItemMessage(
  hwndDlg, RULE_INPUT_1 + i, WM_GETTEXT,
  sizeof(buff - 1), (LPARAM) input_buff);

Вместо "sizeof(buff) - 1" программист случайно написал "sizeof(buff - 1)". Корректный вариант:

SendDlgItemMessage(
  hwndDlg, RULE_INPUT_1 + i, WM_GETTEXT,
  sizeof(buff) - 1, (LPARAM) input_buff);

Другой пример опечатки в тексте программы:

memset(tcmpt->stepsizes, 0,
  sizeof(tcmpt->numstepsizes * sizeof(uint_fast16_t)));

Корректный вариант:

memset(tcmpt->stepsizes, 0,
  tcmpt->numstepsizes * sizeof(uint_fast16_t));

V569. Truncation of constant value.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что константное значение усекается при помещении в переменную. Рассмотрим пример:

int A[100];
unsigned char N = sizeof(A);

Размер массива 'A' (в Win32/Win64) составляет 400 байт. Диапазон значений типа unsigned char: 0..255. Следовательно, переменная 'N' не может хранить размер массива 'A'.

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

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

size_t N = sizeof(A);

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

unsigned char N = sizeof(A) / sizeof(*A);

V570. The variable is assigned to itself.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что переменная присваивается сама себе. Рассмотрим пример:

dst.m_a = src.m_a;
dst.m_b = dst.m_b;

Из-за опечатки, значение переменной 'dst.m_b' не изменится. Исправленный вариант кода:

dst.m_a = src.m_a;
dst.m_b = src.m_b;

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

int Foo(int foo)
{
  UNREFERENCED_PARAMETER(foo);
  return 1;
}

Макрос UNREFERENCED_PARAMETER объявлен в файле WinNT.h следующим образом:

#define UNREFERENCED_PARAMETER(P)          \
  { \
      (P) = (P); \
  } 

Однако, анализатор PVS-Studio знает про такие ситуации и не выдаст предупреждение V570 на присваивание следующего вида:

(foo) = (foo);

V571. Recurring check. This condition was already verified in previous line.

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

// Example N1:
if (A == B)
{
  if (A == B)
    ...
}

// Example N2:
if (A == B) {
} else {
  if (A == B)
    ...
}

В первом случае вторая проверка "if (A==B)" всегда истинна. Во втором случае, вторая проверка всегда ложна.

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

// Example N1:
if (A == B)
{
  if (A == C)
    ...
}

// Example N2:
if (A == B) {
} else {
  if (A == C)
    ...
}

V572. It is odd that the object which was created using 'new' operator is immediately casted to another type.

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

T_A *p = (T_A *)(new T_B());
...
delete p;

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

1) T_B не является наследником от класса T_A.

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

2) T_B наследуется от класса T_A. Класс T_A не имеет виртуальный деструктора.

В этом случае приводить T_B к T_A нельзя, так как потом невозможно корректно уничтожить созданный объект. Корректный код:

T_B *p = new T_B();
...
delete p;

3) T_B наследуется от класса T_A. Класс T_A имеет виртуальный деструктор.

В этом случае код корректен, но явное приведение типа не имеет смысла. Можно написать проще:

T_A *p = new T_B();
...
delete p;

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

DWORD CCompRemoteDriver::Open(HDRVR,
  char *, LPVIDEO_OPEN_PARMS)
{
  return (DWORD)new CCompRemote();
}

Программа для своих нужд работает с указателем, как с дескриптором. Для этого она явно приводит указатель к типу DWORD. Такой код будет корректно работать в 32-битных системах, но может привести к сбою в 64-битной программе. Можно избежать 64-битной ошибки, используя более подходящий тип данных DWORD_PTR:

DWORD_PTR CCompRemoteDriver::Open(HDRVR,
  char *, LPVIDEO_OPEN_PARMS)
{
  return (DWORD_PTR)new CCompRemote();
}

Иногда, причиной для предупреждения V572 является атавизм, оставшийся со времен, когда код был написан на языке Си. Рассмотрим пример такого кода:

struct Joint {
  ...
};
joints=(Joint*)new Joint[n]; //malloc(sizeof(Joint)*n);

Комментарий подсказывает нам, что раньше для выделения памяти использовалась функция 'malloc'. Теперь для этого используется оператор 'new'. Но удалить приведение забыли. Код корректен, но приведение типа здесь совершенно излишне. Можно написать более короткий код:

joints = new Joint[n];

V573. Uninitialized variable 'Foo' was used. The variable was used to initialize itself.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что объявляемая переменная для инициализации использует саму себя. Рассмотрим простой синтетический пример:

int X = X + 1;

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

void Class::Foo(const std::string &FileName)
{
  if (FileName.empty())
    return;
  std::string FullName = m_Dir + std::string("\\") + FullName;
  ...
}

Из-за опечатки в выражении случайно используется имя FullName, а не FileName. Корректный вариант кода:

std::string FullName = m_Dir + std::string("\\") + FileName;

V574. The pointer is used simultaneously as an array and as a pointer to single object.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, переменная используется одновременно как указатель на один объект и как массив. Рассмотрим пример ошибки, которую анализатор PVS-Studio нашёл сам в себе:

TypeInfo *factArgumentsTypeInfo =
  new (GC_QuickAlloc) TypeInfo[factArgumentsCount];
for (size_t i = 0; i != factArgumentsCount; ++i)
{
  Typeof(factArguments[i], factArgumentsTypeInfo[i]);
  factArgumentsTypeInfo->Normalize();
}

Подозрительно, что с переменной factArgumentsTypeInfo мы работаем как с массивом "factArgumentsTypeInfo[i]" и как с указателем на один объект "factArgumentsTypeInfo ->". На самом деле необходимо вызвать функцию Normalize() для всех элементов. Исправленный вариант кода:

TypeInfo *factArgumentsTypeInfo =
  new (GC_QuickAlloc) TypeInfo[factArgumentsCount];
for (size_t i = 0; i != factArgumentsCount; ++i)
{
  Typeof(factArguments[i], factArgumentsTypeInfo[i]);
  factArgumentsTypeInfo[i].Normalize();
}

V575. Function receives an odd argument.

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

Рассмотрим пример:

bool Matrix4::operator==(const Matrix4& other) const {
  if (memcmp(this, &other, sizeof(Matrix4) == 0))
    return true;
  ...

Здесь мы имеем дело с опечаткой. Круглая скобочка поставлена не там где нужно. К сожалению это плохо заметно и такая ошибка может очень долго присутствовать в коде. Из-за опечатки размер сравниваемой памяти вычисляется выражением "sizeof(Matrix4) == 0". Так как результат выражение 'false', то сравнивается ноль байт памяти. Корректный вариант:

bool Matrix4::operator==(const Matrix4& other) const {
  if (memcmp(this, &other, sizeof(Matrix4)) == 0)
    return true;
  ...

V576. Incorrect format. Consider checking the N actual argument of the 'Foo' function.

Анализатор обнаружил потенциальную ошибку при использовании функций форматного вывода (printf, sprintf, wprintf и так далее). Строка форматирования не соответствует передаваемым в функцию фактическим аргументам. Рассмотрим простой пример:

int A = 10;
double B = 20.0;
printf("%i %i\n", A, B);

Согласно строке форматирования функция 'printf' ожидает два фактических аргумента типа 'int'. Однако второй аргумент имеет значение типа 'double'. Подобное несоответствие приводит к неопределённому поведению программы. Например, к распечатке бессмысленных значений.

Корректный вариант:

int A = 10;
double B = 20.0;
printf("%i %f\n", A, B);

Ошибочных вариантов использования функции 'printf' можно привести огромное количество. Рассмотрим только несколько типовых примеров, которые чаше всего можно встретить в программах.

Распечатка адреса.

Очень часто значение указателя пытаются распечатать, используя следующий код:

int *ptr = new int[100];
printf("0x%0.8X\n", ptr);

Этот код ошибочен, поскольку будет работать только в тех системах, где размер указателя совпадает с размером типа 'int'. А, например, в Win64 этот код уже распечатает только младшую часть указателя 'ptr'. Корректный вариант кода:

int *ptr = new int[100];
printf("0x%p\n", ptr);

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

Неиспользуемые аргументы.

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

int nDOW;
#define KEY_ENABLED "Enabled"
...
wsprintf(cDowKey, "EnableDOW%d", nDOW, KEY_ENABLED);

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

wsprintf(cDowKey, "EnableDOW%d%s", nDOW, KEY_ENABLED);

Недостаточное количество аргументов.

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

char* salloc(register int nbytes)
{
    register char* p;
    p = (char*) malloc((unsigned)nbytes);
    if (p == (char *)NULL)
    {
        fprintf(stderr, "%s: out of memory\n");
        exit(1);
    }
    return (p);
}

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

Путаница с signed/unsigned

Очень часто программисты используют спецификатор печати знаковых значений (например '%i') для печати переменных типа unsigned. И наоборот. Эта ошибка, как правило, не критична и так сильно распространена, что в PVS-Studio она имеет низкий приоритет. Во многих случаях подобный код успешно работает и даёт сбой только при больших или отрицательных значениях. Рассмотрим код, который хотя не корректен, но успешно работает:

int A = 10;
printf("A = %u\n", A);
for (unsigned i = 0; i != 5; ++i)
  printf("i = %d\n", i);

Хотя здесь имеется несоответствие, это код на практике печатает корректные значения. Кончено всё равно так лучше не делать и написать корректно:

int A = 10;
printf("A = %d\n", A);
for (unsigned i = 0; i != 5; ++i)
  printf("i = %u\n", i);

Ошибка проявит себя в том случае, если в программе имеются большие или отрицательные значения. Пример:

int A = -1;
printf("A = %u", A);

Вместо строки "A = -1" программа распечатает "A = 4294967295". Корректный вариант:

printf("A = %i", A);

Дополнительные ресурсы.

V577. Label is present inside a switch(). It is possible that these are misprints and 'default:' operator should be used instead.

Анализатор обнаружил потенциальную ошибку внутри оператора switch. Используется метка с именем похожим на 'default'. Возможно это опечатка. Рассмотрим пример:

int c = 10;
int r = 0;
switch(c){
case 1:
  r = 3; break;
case 2:
  r = 7; break;
defalt:
  r = 8; break;
}

Кажется после того, как этот код отработает, значение переменной 'r' будет 8. Но на самом деле значение 'r' останется равно нулю. Дело в том, что "defalt" это метка, а не оператор "default". Исправленный вариант кода:

int c = 10;
int r = 0;
switch(c){
case 1:
  r = 3; break;
case 2:
  r = 7; break;
default:
  r = 8; break;
}

V578. An odd bitwise operation detected. Consider verifying it.

Анализатор обнаружил потенциальную ошибку в выражении, работающем с битами. Часть выражения не имеет смысла или является избыточным. Как правило, такие ошибки возникают из-за опечатки. Рассмотрим пример:

if (up & (PARAMETER_DPDU | PARAMETER_DPDU | PARAMETER_NG))

Здесь два раза используется константа PARAMETER_DPDU. В корректном коде должны использоваться две разных константы: PARAMETER_DPDU и PARAMETER_DPDV. Буква 'U' похожа на 'V' поэтому и возникла такая опечатка. Исправленный вариант:

if (up & (PARAMETER_DPDU | PARAMETER_DPDV | PARAMETER_NG))

Другой пример. Здесь ошибки нет, но код избыточен:

if (((pfds[i].dwFlags & pPFD->dwFlags) & pPFD->dwFlags)
    != pPFD->dwFlags)

Сокращенный вариант:

if ((pfds[i].dwFlags & pPFD->dwFlags) != pPFD->dwFlags)

V579. The 'Foo' function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the N argument.

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

Рассмотрим, как подобная ошибка может возникнуть в коде. Предположим, в начале имелся код следующего вида:

char buf[100];
...
memset(buf, 0, sizeof(buf));

Код корректен. Функция memset() очищает массив из 100 байт. Затем, код изменился, и буфер стал переменного размера. Код очистки буфера изменить забыли:

char *buf = new char[N];
...
memset(buf, 0, sizeof(buf));

Теперь код некорректен. Оператор sizeof() возвращает размер указателя, а не размер буфера с данными. Как результат, функция memset() очищает только часть массива.

Рассмотрим другой пример, взятый из реального приложения:

apr_size_t ap_regerror(int errcode,
  const ap_regex_t *preg, char *errbuf,
  apr_size_t errbuf_size)
{
  ...
  apr_snprintf(errbuf, sizeof errbuf,
    "%s%s%-6d", message, addmessage,
    (int)preg->re_erroffset);
  ...
}

В таком коде заметить ошибку непросто. Функция apr_snprintf() принимает в качестве аргумента указатель 'errbuf' и размер этого указателя 'sizeof errbuf'. Анализатор PVS-Studio считает этот код подозрительным и абсолютно прав. Размер буфера находится в переменной 'errbuf_size' и именно эту переменную следует использовать. Корректный код:

apr_snprintf(errbuf, errbuf_size,
  "%s%s%-6d", message, addmessage,
  (int)preg->re_erroffset);

V580. An odd explicit type casting. Consider verifying it.

Анализатор обнаружил подозрительное явное приведение типа. Это может быть ошибкой или потенциальной ошибкой.

Рассмотрим пример:

DWORD errCode = 0;
void* dwErrParams[MAX_MESSAGE_PARAMS];
dwErrParams[0] = *((void**)&errCode);

Код содержит 64-битную ошибку. Тип 'DWORD' превращается в тип 'void *' . Этот код некорректно работает в 64-битных системах, где размер указателя не совпадает с размером типа DWORD. Корректный вариант:

DWORD_PTR errCode = 0;
void* dwErrParams[MAX_MESSAGE_PARAMS];
dwErrParams[0] = (void *)errCode;

V581. The conditional expressions of the 'if' operators situated alongside each other are identical.

Анализатор обнаружил код, в котором рядом находятся два оператора 'if' с одинаковыми условиями. Это является потенциальной ошибкой или избыточным кодом.

Рассмотрим пример:

if (strlen(S_1) == SIZE)
  Foo(A);
if (strlen(S_1) == SIZE)
  Foo(B);

Содержит этот код ошибку или нет, зависит от того, что хотел сделать программист. Если во втором условии нужно вычислить длину другой строки, то это ошибка. Исправленный код:

if (strlen(S_1) == SIZE)
  Foo(A);
if (strlen(S_2) == SIZE)
  Foo(B);

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

if (strlen(S_1) == SIZE) {
  Foo(A);
  Foo(B);
}

V582. Consider reviewing the source code which operates the container.

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

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

template<class T_, int numElements > class idArray
{
public:
  int Num() const { return numElements; };
  .....
  inline const T_ & operator[]( int index ) const {
    idassert( index >= 0 ); 
    idassert( index < numElements );
    return ptr[index];
  };
  inline T_ & operator[]( int index ) {
    idassert( index >= 0 ); 
    idassert( index < numElements );
    return ptr[index];
  };
private:
  T_ ptr[numElements];
};

Класс позволяет обнаружить ошибки выхода за пределы массива при запуске DEBUG версии. При этом производительность RELESE версии не снижается. Пример некорректного кода:

idArray<int, 1024> newArray; 
newArray[-1] = 0; 
newArray[1024] = 0;

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

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

idArray<float, 16> ArrA;
idArray<float, 8> ArrB;
for (size_t i = 0; i != 16; i++)
  ArrA[i] = 1.0f;
for (size_t i = 0; i != 16; i++)
  ArrB[i] = 1.0f;

Здесь анализатор выдаст предупреждение:

V582 Consider reviewing the source code which operates the 'ArrB' container. The value of the index belongs to the range: [0..15].

Ошибка в том, что оба цикла обрабатывают 16 элементов, хотя второй массив содержит только 8 элементов. Корректный вариант:

for (size_t i = 0; i != 16; i++)
  ArrA[i] = 1.0f;
for (size_t i = 0; i != 8; i++)
  ArrB[i] = 1.0f;

Следует отметить, что передача слишком больших или маленьких индексов не всегда означает ошибку в программе. Например, оператор [] может быть реализован следующим образом:

inline T_ & operator[]( int index ) {
  if (index < 0) index = 0;
  if (index >= numElements) index = numElements - 1;
  return ptr[index];
};

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

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

V583. The '?:' operator, regardless of its conditional expression, always returns one and the same value.

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

Рассмотрим самый простой пример:

int A = B ? C : C;

В любом случае переменной A будет присвоено значение переменной C.

Рассмотрим, как подобна ошибка может выглядеть в коде реального приложения:

fovRadius[0] =
  tan(DEG2RAD((rollAngleClamped % 2 == 0 ?
  cg.refdef.fov_x : cg.refdef.fov_x) * 0.52)) * sdist;

Здесь код отформатирован. В тексте программы это одна строка, и не удивительно, что легко просмотреть опечатку. Ошибка заключается, что два раза используется член структуры "fov_x". Корректный вариант:

fovRadius[0] =
  tan(DEG2RAD((rollAngleClamped % 2 == 0 ?
  cg.refdef.fov_x : cg.refdef.fov_y) * 0.52)) * sdist;

V584. The same value is present on both sides of the operator. The expression is incorrect or it can be simplified.

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

float SizeZ;
if (SizeZ + 1 < SizeZ)

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

if (SizeZ + 1 < maxSizeZ)

Конечно, иногда программисты используют приемы, которые корректны, но выглядят странно. Анализатор PVS-Studio по возможности старается распознать такие ситуации и не выдавать диагностические сообщения. Например, анализатор считает безопасными следующие проверки:

//Тест на переполнение при суммировании
int a, b;
if (a + b < a)

//Проверка, что X не равен 0, +бесконечность или -бесконечность.
double X;
if (X * 0.5f != X)

V585. An attempt to release the memory in which the 'Foo' local variable is stored.

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

Рассмотрим пример некорректного кода:

void Foo()
{
  int *p;
  ...
  free(&p);
}

Корректный код:

void Foo()
{
  int *p;
  ...
  free(p);
}

V586. The 'Foo' function is called twice for deallocation of the same resource.

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

Рассмотрим пример некорректного кода:

float *p1 = (float *)malloc(N * sizeof(float));
float *p2 = (float *)malloc(K * sizeof(float));
...
free(p1);
free(p1);

В тексте программы имеется опечатка, из-за которой дважды освобождается одна и та же область памяти. Последствия выполнения такого кода предсказать сложно. Возможно, программа аварийно завершит свою работу. Или, возможно продолжит свою работу, но возникнет утечка памяти (memory leak).

Корректный вариант:

float *p1 = (float *)malloc(N * sizeof(float));
float *p2 = (float *)malloc(K * sizeof(float));
...
free(p1);
free(p2);

Иногда ошибка двойного освобождения ресурсов не является опасной:

vector<unsigned> m_arrStack;
...
m_arrStack.clear();
m_arrBlock.clear();
m_arrStack.clear();

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

Корректный вариант:
vector<unsigned> m_arrStack;
...
m_arrStack.clear();
m_arrBlock.clear();

V587. An odd sequence of assignments of this kind: A = B; B = A;.

Анализатор обнаружил потенциальную ошибку, связанную с бессмысленным взаимным присваиванием переменных.

Рассмотрим пример:

int A, B, C;
...
A = B;
C = 10;
B = A;

Здесь присваивание "B = A" не имеет никакого практического смысла. Возможно, это опечатка или просто лишнее действие. Корректный вариант кода:

A = B;
C = 10;
B = A_2;

Рассмотренный выше пример был искусственным. Рассмотрим, как эта ошибка может выглядеть в коде реального приложения:

// Swap; exercises counters
{
  RCPFooRef temp = f2;
  f2 = f3;
  f3 = f2;
}

Корректный вариант:

// Swap; exercises counters
{
  RCPFooRef temp = f2;
  f2 = f3;
  f3 = temp;
}

V588. The expression of the 'A =+ B' kind is utilized. Consider reviewing it, as it is possible that 'A += B' was meant.

Анализатор обнаружил потенциальную ошибку, так как в программе имеется последовательность символов '=+'. Возможно, это опечатка и следует использовать оператор '+='.

Рассмотрим пример:

size_t size, delta;
...
size=+delta;

Этот код может быть корректен. Но с большой вероятностью имеется опечатка и на самом деле, хотели использовать оператор '+='. Исправленный вариант:

size_t size, delta;
...
size+=delta;

Если код корректен, то чтобы убрать предупреждение V588 можно удалить '+' или поставить дополнительный пробел. Вариант корректного кода, где предупреждение не выдается:

size = delta;
size = +delta;

Примечание. Для поиска опечаток вида 'A =- B' используется диагностическая проверка V589. Эта проверка сделана отдельно, так как возможно большое количество ложных срабатываний, и может возникнуть желание отключить её.

V589. The expression of the 'A =- B' kind is utilized. Consider reviewing it, as it is possible that 'A -= B' was meant.

Анализатор обнаружил потенциальную ошибку, так как в программе имеется последовательность символов '=-'. Возможно, это опечатка и следует использовать оператор '-='.

Рассмотрим пример:

size_t size, delta;
...
size =- delta;

Этот код может быть корректен. Но с большой вероятностью имеется опечатка и на самом деле, хотели использовать оператор '-='. Исправленный вариант:

size_t size, delta;
...
size -= delta;

Если код корректен, то чтобы убрать предупреждение V589 можно использовать дополнительный пробел между символами '=' и '-'. Вариант корректного кода, где предупреждение не выдается:

size = -delta;

Чтобы уменьшить количество ложных срабатываний, для правила V589 действует ряд специфичных исключений. Например, анализатор не будет выдавать предупреждение, если программист не использует пробелов между переменными и операторами. Ряд примеров, код которых анализатор PVS-Studio считает безопасным:

A=-B;
int Z =- 1;
N =- N;

Примечание. Для поиска опечаток вида 'A =+ B' используется диагностическая проверка V588.

V590. Consider inspecting this expression. The expression is excessive or contains a misprint.

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

if (Aa == 10 && Aa != 3)

Условие будет выполнено в том случае, если 'Aa == 10'. Вторая часть выражения бессмысленна. Проанализировав код, можно прийти к одному из двух выводов:

1) Выражение можно упросить. Исправленный код:

if (Aa == 10)

2) Выражение содержит ошибку. Исправленный код:

if (Aa == 10 && Bb != 3)

Рассмотрим пример, как эта ошибка может выглядеть в реальном приложении:

int appliedSize, appliedSign;
...
if(appliedSize == 'b' && appliedSize != 's' && ...)
...

В выражении имеется опечатка, из-за которой дважды используется переменная appliedSize, но не используется appliedSign. Исправленный вариант:

int appliedSize, appliedSign;
...
if(appliedSize == 'b' && appliedSign != 's' && ...)
...

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

while (*pBuff == ' ' && *pBuff != '\0')
  pBuff++;

Проверка " *pBuff != '\0' " не имеет смысла. Сокращенный вариант кода:

while (*pBuff == ' ')
  pBuff++;

V591. Non-void function should return a value.

Анализатор обнаружил функцию, которая возвращает случайно значение. Это может являться ошибкой.

Рассмотрим пример:

int main (int argc, char** argv)
{
  ...
  printf("FINISH\r\n");
}

Функция main() возвращает целое число, которое принимает вызывающий процесс. Если main() нe возвращает значение явно, то вызывающий процесс получает формально неопределенное значение. Корректный вариант:

int main (int argc, char** argv)
{
  ...
  printf("FINISH\r\n");
  return retCode;
}

Более интересным и опасным случаем, является код функций, в котором неопределенное значение возвращается только иногда. Рассмотрим пример:

BOOL IsInterestingString(char *s)
{
  if (s == NULL)
    return FALSE;
  if (strlen(s) < 4)
    return;
  return (s[0] == '#') ? TRUE : FALSE;
}

В коде допущена опечатка. Если длина строки меньше 4 символов, то функция вернет неопределенное значение. Корректный вариант:

BOOL IsInterestingString(char *s)
{
  if (s == NULL)
    return FALSE;
  if (strlen(s) < 4)
    return FALSE;
  return (s[0] == '#') ? TRUE : FALSE;
}

Примечание. Анализатор старается определить ситуации, когда отсутствие возвращаемого значения не является ошибкой. Пример кода, который анализатор PVS-Studio сочтет безопасным:

int Foo()
{
  ...
  exit(10);
}

V592. The expression was enclosed by parentheses twice: ((expression)). One pair of parentheses is unnecessary or misprint is present.

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

Хочется подчеркнуть, что анализатор не ищет фрагменты кода, где два раза повторяются круглые скобки. Например, анализатор считает проверку "if ((A = B))" безопасной. Здесь дополнительные скобки используются, чтобы подавить предупреждения некоторых компиляторов. В этом выражении невозможно расставить скобки так, чтобы возникла ошибка.

Анализатор PVS-Studio пытается обнаружить те ситуации, когда изменив местонахождение одной скобки, можно изменить смысл выражения. Рассмотрим пример:

if((!osx||howmanylevels))

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

if(!(osx||howmanylevels))

Если даже выражение корректно, то всё равно лучше убрать лишние круглые скобки. На это есть две причины.

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

2) Если убрать лишние скобки, то анализатор перестанет выдавать ложное предупреждение.

V593. Consider reviewing the expression of the 'A = B == C' kind. The expression is calculated as following: 'A = (B == C)'.

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

if (handle = Foo() != -1)

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

if ((handle = Foo()) != -1)

Но приоритет оператора '!=' выше, чем приоритет оператора '='. Поэтому выражение вычистится так:

if (handle = (Foo() != -1))

Чтобы исправить ошибку, можно использовать скобки. Ещё лучше не жадничать на количестве строк кода. Текст вашей программы станет более читабелен, если написать так:

handle = Foo();
if (handle != -1)

Рассмотрим, как подобная ошибка может выглядеть на практике:

if (hr = AVIFileGetStream(pfileSilence,
           &paviSilence, typeAUDIO, 0) != AVIERR_OK)
{
  ErrMsg("Unable to load silence stream");
  return hr; 
}

Проверка в коде, что произошла ошибка, работает корректно и будет выдано сообщение "Unable to load silence stream". Беда в том, что переменная 'hr' будет хранить не код ошибки, а значение 1. Исправленный вариант кода:

if ((hr = AVIFileGetStream(pfileSilence,
           &paviSilence, typeAUDIO, 0)) != AVIERR_OK)
{
  ErrMsg("Unable to load silence stream");
  return hr; 
}

Анализатор не всегда выдает предупреждения, обнаруживая конструкцию вида "if (x = a == b)". Например, анализатор понимает, что данный код безопасен:

char *from;
char *to;
bool result;
...
if (result = from == to)
{}

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

1) Добавить дополнительные скобки. Пример: "if (x = (a == b))".

2) Использовать комментарий для подавления предупреждения. Пример: "if (x = a == b) //-V593".

V594. The pointer steps out of array's bounds.

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

int A[10];
fill(A, A + sizeof(A), 33);

Мы хотим присвоить всем элементам массива значение 33. Ошибка в том, что указатель "A + sizeof(A)" ссылается далеко за пределы массива. В результате, мы изменим больше ячеек памяти, чем планировалось. Результат этой ошибки непредсказуем.

Правильный вариант кода:

int A[10];
fill(A, A + sizeof(A) / sizeof(A[0]), 33);

V595. The pointer was utilized before it was verified against nullptr. Check lines: N1, N2.

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

Анализатор заметил в коде следующую ситуацию. В начале, указатель используется. А уже затем этот указатель проверяется на значение NULL. Это может означать одно из двух:

1) Возникнет ошибка, если указатель будет равен NULL.

2) Программа всегда работает корректно, так как указатель всегда не равен NULL. Проверка является лишней.

Рассмотрим первый вариант. Ошибка есть.

buf = Foo();
pos = buf->pos;
if (!buf) return -1;

Если указатель 'buf' окажется равен NULL, то выражение 'buf->pos ' приведёт к ошибке. Анализатор выдаст предупреждение на этот код, указав 2 строки. Первая строка - это то место, где используется указатель. Вторая строка - это то место, где указатель сравнивается со значением NULL.

Исправленный вариант кода:

buf = Foo();
if (!buf) return -1;
pos = buf->pos;
pos = buf->pos;

Рассмотрим второй вариант. Ошибки нет.

void F(MyClass *p)
{
  if (!IsOkPtr(p))
    return;
  printf("%s", p->Foo());
  if (p) p->Clear();
}

Этот код всегда работает корректно. Указатель всегда не равен NULL. Однако анализатор не разобрался в этой ситуации и выдал предупреждение. Чтобы оно исчезло, следует удалить проверку "if (p)". Она не имеет практического смысла и только может запутать программиста, читающего код.

Исправленный вариант:

void F(MyClass *p)
{
  if (!IsOkPtr(p))
    return;
  printf("%s", p->Foo());
  p->Clear();
}

В случае если анализатор PVS-Studio ошибается, то кроме изменения кода, можно использовать комментарий для подавления предупреждений. Пример: "p->Foo(); //-V595".

Примечание.

Некоторые пользователи сообщают, что анализатор выдает предупреждение V595 на корректный код, Пример:

static int Foo(int *dst, int *src)
{
  *dst = *src; // V595 !
  if (src == 0)
    return 0;
  return Foo(dst, src);
}
...
int a = 1, b = 2;
int c = Foo(&a, &b);

Да, здесь PVS-Studio выдает ложное срабатывание. Код корректен и указатель 'src' не может быть равен NULL в тот момент, когда выполняется присваивание "*dst = *src". Возможно, в дальнейшем мы реализуем исключение для подобных случаев, но пока не спешим это делать. Хотя здесь нет ошибки, анализатор выявил избыточность кода. Функцию можно сократить. При этом пропадет предупреждение V595, а код станет проще.

Улучшенный вариант:

int Foo(int *dst, int *src)
{
  assert(dst && src);
  *dst = *src;
  return Foo(dst, src);
}

V596. The object was created but it is not being used. The 'throw' keyword could be missing.

Анализатор обнаружил потенциальную ошибку, связанную с использованием класса std::exception или наследуемого от него класса. Анализатор выдает предупреждение в том случае, если создается объект типа std::exception / CException, но не используется. Пример:

if (name.empty())
  std::logic_error("Name mustn't be empty");

Ошибка заключается в том, что случайно забыто ключевое слово 'throw'. В результате данные код не генерирует исключение в случае ошибочной ситуации. Исправленный вариант кода:

if (name.empty())
  throw std::logic_error("Name mustn't be empty");

V597. The compiler could delete the 'memset' function call, which is used to flush 'Foo' buffer. The RtlSecureZeroMemory() function should be used to erase the private data.

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

Рассмотрим пример кода.

void Foo()
{
  char password[MAX_PASSWORD_LEN];
  InputPassword(password);
  ProcessPassword(password);
  memset(password, 0, sizeof(password));
}

Функция на стеке создает временный буфер, предназначенный для хранения пароля. По окончанию работы с паролем, мы хотим очистить этот буфер. Если это не сделать, пароль останется в памяти, что может привести к неприятным последствиям. На эту тему есть хорошая статья "Перезаписывать память – зачем?".

К сожалению, приведенный код может оставить буфер неочищенным. Обратите внимание, что массив 'password' очищается в конце и более не используется. Поэтому при сборке Release версии программы, компилятор, скорее всего, удалит вызов функции memset(). На это компилятор имеет полное право. Такое изменение не влияет на наблюдаемое поведение, которое описано в Стандарте как последовательность вызова функций ввода-вывода и чтения-записи volatile данных. То есть с точки зрения языка Си/Си++, если удалить вызов функции memset(), это ничего не изменит!

Для очистки буферов содержащих приватную информацию необходимо использовать специальную функцию RtlSecureZeroMemory. Исправленный вариант кода:

void Foo()
{
  char password[MAX_PASSWORD_LEN];
  InputPassword(password);
  ProcessPassword(password);
  RtlSecureZeroMemory(password, sizeof(password));
}

Кажется, что на практике, компилятор не может удалить вызов такой важной функции memset(). Может возникнуть впечатление, что речь идет о редких экзотических компиляторах. Нет, это не так. Возьмем для примера компилятор Visual C++ 10, входящий в состав Visual Studio 2010.

Рассмотрим две функции.

void F1()
{
  TCHAR buf[100];
  _stprintf(buf, _T("Test: %d"), 123);
  MessageBox(NULL, buf, NULL, MB_OK);
  memset(buf, 0, sizeof(buf));
}

void F2()
{
  TCHAR buf[100];
  _stprintf(buf, _T("Test: %d"), 123);
  MessageBox(NULL, buf, NULL, MB_OK);
  RtlSecureZeroMemory(buf, sizeof(buf));
}

Функции отличаются тем, как они очищают буфер. Первая использует функцию memset(), а вторая RtlSecureZeroMemory(). Скомпилируем оптимизированный код, указав для компилятора Visual C++ 10 ключ "/O2". Рассмотрим получившийся ассемблерный код:

Рисунок 1. Функция memset() удалена.

Рисунок 1. Функция memset() удалена.

Рисунок 2. Функция RtlSecureZeroMemory() заполняет память нулями.

Рисунок 2. Функция RtlSecureZeroMemory() заполняет память нулями.

Как видно в ассемблерном коде, функция memset() была удалена компилятором при оптимизации. Функция RtlSecureZeroMemory() выстроилась в код, поэтому теперь массив успешно обнуляется.

V598. The 'memset/memcpy' function is used to nullify/copy the fields of 'Foo' class. Virtual method table will be damaged by this.

Анализатор обнаружил, что для работы с классом используются такие низкоуровневые функции, как memset() или memcpy(). Это недопустимо, если класс имеет таблицу виртуальных методов. Функции memset()/memcpy() могут затереть содержимое таблицы и тогда поведение программы будет непредсказуемо.

Рассмотрим пример кода.

class MyClass
{
  int A, B, C;
  char buf[100];
  MyClass();
  virtual ~MyClass() {}
};

MyClass::MyClass()
{
  memset(this, 0, sizeof(*this));
}

Обратите внимание, что в классе есть виртуальный деструктор. Это значит, что в классе присутствует таблица виртуальных функций. Программист поленился очищать члены класса по отдельности. Для очистки он использовал функцию memset(). Это привет к порче таблицы, так как функция memset() ничего про неё не знает.

Корректный код:

MyClass:: MyClass() : A(0), B(0), C(0)
{
  memset(buf, 0, sizeof(buf));
}

V599. The virtual destructor is not present, although the 'Foo' class contains virtual functions.

Анализатор обнаружил потенциальную ошибку, связанную с отсутствием в базовом классе виртуального деструктора. Чтобы анализатор выдал предупреждение V599 необходимо, чтобы выполнились следующие условия:

1) Объект класса уничтожается с помощью оператора delete.

2) Класс имеет хотя бы одну виртуальную функцию.

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

Рассмотрим пример кода.

class Father
{
public:
  Father() {}
  ~Father() {} 
  virtual void Foo() { ... }
};
 
class Son : public Father
{
public:
  int* buffer;
  Son() : Father() { buffer = new int[1024]; }
  ~Son() { delete[] buffer; }
  virtual void Foo() { ... }
};

...
Father* object = new Son();
delete object;              // Call ~Father()!!

Нижеприведённый код является некорректным и приводит к утечке памяти. В момент уничтожения объекта (delete object;) вызывается только деструктор в классе 'Father'. Чтобы вызвать деструктор класса 'Son' необходимо сделать деструктор виртуальным.

Корректный код:

class Father
{
public:
  Father() {}
  virtual ~Father() {} 
  virtual void Foo() { ... }
};

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

Вы разрабатываете библиотеку. В ней есть класс XXX, в котором есть виртуальные функции, но нет виртуального деструктора. Вы в библиотеке сами не работаете с этим классом, и анализатор PVS-Studio не предупредит об опасности. Проблема может возникнуть у программиста, использующего вашу библиотеку и наследующего свои классы от класса XXX.

Намного больше проблем позволяет выявить диагностическое предупреждение C4265: 'class' : class has virtual functions, but destructor is not virtual, реализованная в Visual C++. Это очень полезная диагностика. Но по-умолчанию она выключена. Почему эта диагностика отключена я ответить затрудняюсь. Обсуждение этой темы поднималось на сайте StackOverflow: Why is C4265 Visual C++ warning (virtual member function and no virtual destructor) off by default? К сожалению толкового объяснения дать никто не смог.

Мы предполагаем, что предупреждение C4265 дает много срабатываний в коде, где используется паттерн примесь (подмешивание). При использовании этого паттерна возникает множество интерфейсных классов. Они содержат виртуальные функции, но виртуальный деструктор в них не нужен.

Можно сказать, что диагностическое правило V599 является частным случаем C4265. Оно дает меньше ложных срабатываний, но, к сожалению, позволяет выявить меньшее количество дефектов. Если вы хотите провести более тщательный анализ своего кода, то включите предупреждение C4265.

P. S.

К сожалению, ВСЕГДА объявлять деструктор виртуальным, не является идеальной практикой программирования. Это приводит к дополнительным накладным расходам, так как в классе появляется указатель на таблицу виртуальных методов.

Дополнительная информация:

V600. Consider inspecting the condition. The 'Foo' pointer is always not equal to NULL.

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

Рассмотрим пример кода.

void Foo()
{
  short T_IND[8][13];
  ...
  if (T_IND[1][j]==0 && T_IND[5]!=0)
    T_buf[top[0]]= top[1]*T_IND[6][j];
  ...
}

Программа обрабатывает двумерный массив. Код сложен для чтения и ошибка на первый взгляд не заметна. Однако анализатор предупредит, что сравнение "T_IND[5]!=0" не имеет смысла. Указатель "T_IND[5]" всегда не равен нулю.

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

if (T_IND[1][j]==0 && T_IND[5][j]!=0)

Предупреждение V600 далеко не всегда означает наличие настоящей ошибки. Часто причиной появления V600 является неаккуратный рефакторинг. Рассмотрим наиболее распространенный случай. В начале код выглядел так:

int *p = (int *)malloc(sizeof(int) *ARRAY_SIZE);

...

if (!p)

return false;

...

free(p);

Код менялся. Стало ясно, что значение ARRAY_SIZE небольшое и массив можно создавать на стеке. В результате получился следующий код:

int p[ARRAY_SIZE];

...

if (!p)

return false;

...

Здесь выдается предупреждение V600. Однако код корректно работает. Просто получилось, что поверка "if (!p)" потеряла смысл и её можно удалить.

V601. An odd implicit type casting.

Анализатор обнаружил подозрительное неявное приведение типа. Такое приведение типа может говорить о наличии ошибки или о неаккуратности в коде.

Рассмотрим первый пример.

std::string str;
bool bstr;
...
str = false;

Любой программист удивится, увидев как переменной типа 'std::string' присваивают значение 'false'. Однако это вполне допустимая и работающая конструкция. Значение 'false' превратится в нулевой указатель. Затем переменной 'str' будет присвоена пустая строка.

Однако, скорее всего это не то, что хотел программист. Если он планировал очистить строку, то он использовал бы для этого функцию 'clear()'. Здесь программист просто ошибся и написал в коде не ту переменную.

Корректный вариант кода:

std::string str;
bool bstr;
...
bstr = false;

Рассмотрим второй пример:

bool Ret(int *p)
{
  if (!p)
    return "p1";
  ...
}

Строковый литерал "p1" превращается в значение 'true' и возвращается из функции. Это очень странный код.

Общие рекомендации по правке подобного кода дать сложно и каждый случай надо рассматривать отдельно.

V602. Consider inspecting this expression. '<' possibly should be replaced with '<<'.

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

Рассмотрим пример кода.

void Foo(unsigned nXNegYNegZNeg, unsigned nXNegYNegZPos,
         unsigned nXNegYPosZNeg, unsigned nXNegYPosZPos)
{
  unsigned m_nIVSampleDirBitmask =
    (1 << nXNegYNegZNeg) | (1 <  nXNegYNegZPos) |
    (1 << nXNegYPosZNeg) | (1 << nXNegYPosZPos);
  ...
}

Код содержит ошибку, так как случайно в выражении написан оператор '<'. Корректный вариант кода должен выглядеть так:

unsigned m_nIVSampleDirBitmask =
  (1 << nXNegYNegZNeg) | (1 << nXNegYNegZPos) |
  (1 << nXNegYPosZNeg) | (1 << nXNegYPosZPos);

Примечание.

Анализатор считает подозрительными сравнения ('<', '>'), если полученный результат используется в двоичных операциях, таких как '&', '|' или '^'. Диагностика устроена более сложно, но надеемся, что общая идея понятна. Найдя такие выражения, анализатор выдает предупреждение V602.

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

V603. The object was created but it is not being used. If you wish to call constructor, 'this->Foo::Foo(....)' should be used.

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

Рассмотрим типичный пример, взятый из реального приложения:

class CSlideBarGroup
{
public:
  CSlideBarGroup(CString strName, INT iIconIndex,
                 CListBoxST* pListBox);
  CSlideBarGroup(CSlideBarGroup& Group);
  ...
};

CSlideBarGroup::CSlideBarGroup(CSlideBarGroup& Group)
{
  CSlideBarGroup(Group.GetName(), Group.GetIconIndex(),
                 Group.GetListBox());
}

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

Происходит следующее. Создаётся новый неименованный объект типа CSlideBarGroup и тут же разрушается. В результате поля класса остаются неинициализированными.

Правильным вариантом будет создать функцию инициализации и вызывать её из конструкторов. Корректный код:

class CSlideBarGroup
{
  void Init(CString strName, INT iIconIndex,
            CListBoxST* pListBox);
public:
  CSlideBarGroup(CString strName, INT iIconIndex,
                 CListBoxST* pListBox)
  {
    Init(strName, iIconIndex, pListBox);
  }
  CSlideBarGroup(CSlideBarGroup& Group)
  {
    Init(Group.GetName(), Group.GetIconIndex(),
         Group.GetListBox());
  }
  ...
};

Если очень хочется вызвать именно конструктор, то это можно записать следующим образом:

CSlideBarGroup::CSlideBarGroup(CSlideBarGroup& Group)
{
  this->CSlideBarGroup::CSlideBarGroup(
    Group.GetName(), Group.GetIconIndex(), Group.GetListBox());
}

Другой аналогичный вариант:

CSlideBarGroup::CSlideBarGroup(CSlideBarGroup& Group)
{
  new (this) CSlideBarGroup(
    Group.GetName(), Group.GetIconIndex(),
    Group.GetListBox());
}

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

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

class SomeClass
{
  int x,y;
public:
  SomeClass() { SomeClass(0,0); }
  SomeClass(int xx, int yy) : x(xx), y(yy) {}
};

Код содержит ошибку. В конструкторе 'SomeClass() ' создается временный объект. В результате поля 'x' и 'y' остаются неинициализированными. Исправить код можно так:

class SomeClass
{
  int x,y;
public:
  SomeClass() { new (this) SomeClass(0,0); }
  SomeClass(int xx, int yy) : x(xx), y(yy) {}
};

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

Рассмотрим другой код, где явный вызов конструктора приводит к ошибке:

class Base 
{ 
public: 
 char *ptr; 
 std::vector vect; 
 Base() { ptr = new char[1000]; } 
 ~Base() { delete [] ptr; } 
}; 
 
class Derived : Base 
{ 
  Derived(Foo foo) { } 
  Derived(Bar bar) { 
     new (this) Derived(bar.foo); 
  } 
}

Когда мы вызываем конструктор "new (this) Derived(bar.foo);", объект Base уже создан и поля инициализированы. Повторный вызов конструктора приведет к двойной инициализации. В 'ptr' запишем указатель на вновь выделенный участок памяти. В результате получаем утечку памяти. К чему приведет двойная инициализация объекта типа std::vector, вообще предсказать сложно. Ясно одно. Такой код недопустим.

В заключении ещё раз хочется отметить, что лучше явно не вызывать конструктор, а создать функцию инициализации. Явный вызов конструктора требуется только в крайне редких случаях.

Явный вызов одного конструктора из другого в C++11 (делегация)

Новый стандарт позволяет вызывать одни конструкторы класса из других (так называемая делегация). Это позволяет писать конструкторы, использующие поведение других конструкторов без внесения дублирующего кода.

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

class MyClass {
    int m_x;
 public:
    MyClass(int X) : m_x(X) {}
    MyClass() : MyClass(33) {}
};

Конструктор MyClass без аргументов вызывает конструктор того же класса с целочисленным аргументом.

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

Дополнительная информация

V604. It is odd that the number of iterations in the loop equals to the size of the pointer.

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

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

char A[N];
for (size_t i=0; i < sizeof(A); ++i)
  A[i] = 0;

Затем, код программы менялся и массив 'A' стал иметь переменный размер. Код стал некорректен:

char *A = (char *)malloc(N);
for (size_t i=0; i < sizeof(A); ++i)
  A[i] = 0;

Теперь выражение "sizeof(A)" возвращает размер указателя, а не размер массива.

Корректный вариант кода:

char *A = (char *)malloc(N);
for (size_t i=0; i < N; ++i)
  A[i] = 0;

V605. Consider verifying the expression. An unsigned value is compared to the number - NN.

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

Пример кода, где будет выдано предупреждение V605:

unsigned u = ...;
if (u < -1)
{ ... }

V606. Ownerless token 'Foo'.

Анализатор обнаружил потенциальную ошибку, найдя в коде лишнюю лексему. Чаще всего, такие "потерянные" лексемы появляются в коде, когда забыли написать ключевое слово return.

Рассмотрим пример:

bool Run(int *p)
{
  if (p == NULL)
    false;
  ...
}

Здесь случайно забыли написать "return". Код компилируется, но не имеет практического смысла.

Исправленный вариант:

bool Run(int *p)
{
  if (p == NULL)
    return false;
  ...
}

V607. Ownerless expression 'Foo'.

Анализатор обнаружил потенциальную ошибку, найдя в коде лишнее выражение. Чаще всего, такие "потерянные" выражения появляются в коде, когда забыли написать ключевое слово return или в процессе неаккуратного рефакторинга кода.

Рассмотрим пример:

void Run(int &a, int b, int c)
{
  if (X)
    a = b + c;
  else
    b - c; 
}

Из-за опечатки текст программы не закончен. Код компилируется, но не имеет практического смысла.

Исправленный вариант:

void Run(int &a, int b, int c)
{
  if (X)
    a = b + c;
  else
    a = b - c; 
}

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

struct A {};
struct B : public A {};
...
void Foo(B *p)
{
  static_cast<A*>(pp);
  ...
}

Здесь выражение "static_cast<A*>(pp);" проверяет, что класс 'B' наследуется от класса 'A'. Если это не так, то произойдет ошибка компиляции.

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

void Foo(int a, int b)
{
  a, b;
}

Здесь анализатор PVS-Studio не выдаст предупреждение V607.

V608. Recurring sequence of explicit type casts.

Анализатор обнаружил повторяющиеся последовательности, состоящие из операторов явного приведения типов. Как правило, подобный код появляется из-за опечаток и не приводит к возникновению ошибок. Однако разумно проверить фрагменты кода, где анализатор выдает предупреждение V608. Возможно, ошибка всё-таки есть. Или, по крайней мере, код можно упростить.

Рассмотрим пример:

m_hIcon = AfxGetApp()->LoadStandardIcon(
  MAKEINTRESOURCE(IDI_ASTERISK));

Анализатор выдаст предупреждение: V608 "Recurring sequence of explicit type casts: (LPSTR)(ULONG_PTR)(WORD) (LPSTR)(ULONG_PTR)(WORD)."

Давайте разберемся, откуда взялись две цепочки "(LPSTR)(ULONG_PTR)(WORD)".

Константное значение IDI_ASTERISK представляет собой макрос вида:

#define IDI_ASTERISK MAKEINTRESOURCE(32516)

Это значит, что приведенный выше код эквивалентен этому:

m_hIcon = AfxGetApp()->LoadStandardIcon(
  MAKEINTRESOURCE(MAKEINTRESOURCE(32516)));

Макрос MAKEINTRESOURCE разворачивается в (LPSTR)((DWORD)((WORD)(i))). В результате получается следующая последовательность:

m_hIcon = AfxGetApp()->LoadStandardIcon(
  (LPSTR)((DWORD)((WORD)((LPSTR)((DWORD)((WORD)((32516))))))
);

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

m_hIcon = AfxGetApp()->LoadStandardIcon(IDI_ASTERISK);

V609. Divide or mod by zero.

Анализатор обнаружил ситуацию, когда может произойти деление на ноль.

Рассмотрим пример:

for (int i = -10; i != 10; ++i)
{
  Foo(X / i);
}

В процессе выполнения цикла, переменная 'i' примет значение, равное 0. В этот момент произойдёт деление на 0. Чтобы исправить ситуацию, необходимо специально обработать случай, когда итератор 'i' равен нулю.

Исправленный вариант:

for (int i = -10; i != 10; ++i)
{
  if (i != 0)
    Foo(X / i);
}

V610. Undefined behavior. Check the shift operator.

Анализатор обнаружил операцию сдвига, которая приводит к неопределенному или к неуточненному поведению (undefined behaviour/unspecified behavior).

Стандарт Си++11 описывает работу операторов сдвига следующим образом:

The shift operators << and >> group left-to-right. shift-expression << additive-expression shift-expression >> additive-expression

The operands shall be of integral or unscoped enumeration type and integral promotions are performed. 1. The type of the result is that of the promoted left operand. The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand. 2. The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. Otherwise, if E1 has a signed type and non-negative value, and E1*2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined. 3. The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

Приведем примеры, которые приводят к undefined или unspecified behavior:

int A = 1;
int B;
B = A << -3; // undefined behavior
B = A << 100; // undefined behavior
B = -1 << 5; // undefined behavior
B = -1 >> 5; // unspecified behavior

Конечно, это упрощенные примеры. В реальных программах ситуации сложнее. Рассмотрим практический пример:

SZ_RESULT
SafeReadDirectUInt64(ISzInStream *inStream, UInt64 *value)
{
  int i;
  *value = 0;
  for (i = 0; i < 8; i++)
  {
    Byte b;
    RINOK(SafeReadDirectByte(inStream, &b));
    *value |= ((UInt32)b << (8 * i));
  }
  return SZ_OK;
}

Функция пытается побайтно прочитать 64-битное значение. К сожалению, у неё это не получится, если число было больше 0x00000000FFFFFFFF. Обратите внимание на сдвиг "(UInt32)b << (8 * i)". Размер левого операнда составляет 32 бита. При этом сдвиг происходит от 0 до 56 бит. На практике это приведёт к тому, что старшая часть 64-битного значения останется заполнена нулями. Теоретически здесь вообще имеет место неопределенное поведение и результат непредсказуем.

Корректный код должен выглядеть так:

*value |= ((UInt64)b << (8 * i));

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

V611. The memory allocation and deallocation methods are incompatible.

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

Рассмотрим пример некорректного кода:

int *p = (int *)malloc(sizeof(int) * N);
...
...
delete [] p;

Исправленный вариант:

int *p = (int *)malloc(sizeof(int) * N);
...
...
free(p);

V612. An unconditional 'break/continue/return/goto' within a loop.

Анализатор обнаружил подозрительный цикл. В теле цикла используется один из следующих операторов: break, continue, return, goto. Эти операторы выполняются всегда, без каких либо условий.

Рассмотрим соответствующие примеры:

do {
  X();
  break;
} while (Foo())

for (i = 0; i < 10; i++) {
  continue;
  Foo();
}

for (i = 0; i < 10; i++) {
  x = x + 1;
  return;
}

while (*p != 0) {
  x += *p++;
  goto endloop;
}
endloop:

while (*p != 0) {
  x += *p++;
  goto endloop;
}
endloop:

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

int DvdRead(....)
{
  ....
  for (i=lsn; i<(lsn+sectors); i++){
    ....
//    switch (mode->datapattern){
//    case CdSecS2064:
      ((u32*)buf)[0] = i + 0x30000;
      memcpy_fast((u8*)buf+12, buff, 2048); 
      buf = (char*)buf + 2064; break;
//    default:
//      return 0;
//    }
  }
  ....
}

Часть строк в функции закомментировано. Беда в том, что забыли закомментировать оператор "break".

Когда комментариев не было, "break" был внутри тела "switch". Когда "switch" закомментировали, оператор "break" стал досрочно завершать цикл. В результате тело цикла выполняется только один раз.

Корректный вариант кода:

buf = (char*)buf + 2064; // break;

Следует отметить, что диагностическое правило V612 достаточно сложно. Учитывается множество ситуаций, когда использование оператора break/continue/return/goto совершенно корректно. Рассмотрим для примера несколько ситуаций, когда предупреждение V612 выводиться не будет.

1) Наличие условия.

while (*p != 0) {
  if (Foo(p))
    break;
}

2) Специальные приёмы, как правило, используемые в макросах:

do { Foo(x) return 1; } while(0);

3) Обход оператора 'continue' с помощью 'goto':

for (i = 0; i < 10; i++) {
  if (x == 7) goto skipcontinue;
  continue;
skipcontinue: Foo(x);
}

Возможны и другие приёмы, которые используются на практике, но про которые мы не знаем. Если вы заметили, что анализатор PVS-Studio выдаёт ложное предупреждение V612, просим написать нам и прислать соответствующие примеры. Мы изучим их и постараемся реализовать исключения для подобных случаев.

V613. Strange pointer arithmetic with 'malloc/new'.

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

Рассмотрим пример:

a = ((int *)(malloc(sizeof(int)*(3+5)))+2);

В выражении много лишних скобок и вероятно программист в них запутался. Давайте упростим этот код для наглядности:

a = (int *)malloc(sizeof(int)*8);
a += 2;

Очень странно, прибавлять к указателю число 2. Даже если так надо и код правильный, он очень опасен. Например, очень легко забыть, что освобождать память надо так: "free(a - 2);".

Корректный вариант кода:

a = (int *)malloc(sizeof(int)*(3+5+2));

V614. Uninitialized variable 'Foo' used.

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

Рассмотрим простейший пример:

int Aa = Get();
int Ab;
if (Ab) // Ab - uninitialized variable
  Ab = Foo();
else
  Ab = 0;

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

int Aa = Get();
int Ab;
if (Aa) // OK
  Ab = Foo();
else
  Ab = 0;

Предупреждение V614 выдается не только при использовании простых типов. Анализатор может выдавать предупреждение для переменных типа класс, которые имеют конструктор и, по сути, являются инициализированными. Однако их использование без предварительного присваивания не имеет смысла. Примером таких классов являются умные указатели и итераторы.

Рассмотрим примеры:

std::auto_ptr<CLASS> ptr;
ptr->Foo();

std::list<T>::iterator it;
*it = X;

Корректный вариант кода:

std::auto_ptr<CLASS> ptr(Get());
ptr->Foo();

std::list<T>::iterator it;
it = Get();
*it = X;

V801. Decreased performance. It is better to redefine the N function argument as a reference. Consider replacing 'const T' with 'const .. &T' / 'const .. *T'.

Анализатор обнаружил конструкцию, которую потенциально можно оптимизировать. В функцию передается объект, представляющий собой класс или структуру. Этот объект передается по значению, но при этом не модифицируется, так как имеется ключевое слово const. Возможно, рационально передавать этот объект с помощью константной ссылки в языке Си++. Или с помощью указателя в языке Си.

Пример:

bool IsA(const std::string s)
{
  return s == A;
}

При вызове этой функции произойдет вызов конструктора копирования для класса std::string. Если подобное копирование объектов происходит часто, то это может существенно снижать производительность приложения. Данный код можно легко оптимизировать, добавив ссылку:

bool IsA(const std::string &s)
{
  return s == A;
}

Дополнительные ресурсы:

V802. On 32-bit/64-bit platform, structure size can be reduced from N to K bytes by rearranging the fields according to their sizes in decreasing order.

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

Рассмотрим пример структуры, о которой анализатор сообщит как о неэффективной:

struct LiseElement {
  bool m_isActive;
  char *m_pNext;
  int m_value;
};

Данная структура в 64-битном коде займет 24 байта, что связано с выравниванием данных. Но если поменять последовательность полей, то ее размер составит всего 16 байт. Оптимизированный вариант структуры будет выглядеть так:

struct LiseElement {
  char *m_pNext;
  int m_value;
  bool m_isActive;
};

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

Отметим, что приведенная структура всегда занимает 12 байт в 32-битной программе, в какой бы последовательности не располагались поля. Поэтому, при проверке 32-битной конфигурации, сообщение V802 выдано не будет.

Естественно бывают и обратные ситуации, когда размер структуры можно оптимизировать в 32-битной конфигурации и нельзя в 64-битной. Рассмотрим пример такой структуры:

struct T_2
{
  int *m_p1;
  __int64 m_x;
  int *m_p2;
}

Эта структура в 32-битной программе из-за выравнивания будет занимать 24 байта. Если же переставить поля, как показано ниже, то она будет занимать только 16 байт.

struct T_2
{
  __int64 m_x;
  int *m_p1;
  int *m_p2;
}

В 64-битной программе расположение полей в структуре 'T_2' не имеет значения. В любом случае она займет 24 байта.

Метод сокращения объема структур достаточно прост. Достаточно расположить поля в порядке убывания их размера. В этом случае поля начнут располагаться без лишних зазоров. Например, возьмем следующую структуру размером 40 байт в 64-битной программе:

struct MyStruct
{
  int m_int;
  size_t m_size_t;
  short m_short;
  void *m_ptr;
  char m_char;
};

Простой сортировкой последовательности полей по убыванию размера:

struct MyStructOpt
{
  void *m_ptr;
  size_t m_size_t;
  int m_int;
  short m_short;
  char m_char;
};

мы получим из нее структуру размером 24 байт.

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

class MyWindow : public CWnd {
  bool m_isActive;
  size_t m_sizeX, m_ sizeY;
  char m_color[3];
  ...
};

Размер данной структуры может быть сокращен, но это не имеет практического смысла.

V803. Decreased performance. It is more effective to use the prefix form of ++it. Replace iterator++ with ++iterator.

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

Пример:

std::vector<size_t>::const_iterator it;
for (it = a.begin(); it != a.end(); it++)
{ ... }

Более быстрый вариант:

std::vector<size_t>::const_iterator it;
for (it = a.begin(); it != a.end(); ++it)
{ ... }

Префиксный оператор инкремента изменяет состояние объекта и возвращает себя в уже изменённом виде. Оператор префиксного инкремента в классе итератора для работы с std::vector может выглядеть так:

_Myt& operator++()
{ // preincrement
  ++_Myptr;
  return (*this);
}

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

_Myt operator++(int)
{ // postincrement
  _Myt _Tmp = *this;
  ++*this;
  return (_Tmp);
}

Если мы хотим только увеличить значение итератора, то получается, что префиксная форма предпочтительна. Поэтому, один из советов по микро-оптимизации программ писать "for (it = a.begin(); it != a.end(); ++it)" вместо "for (it = a.begin(); it != a.end(); it++)". В последнем случае происходит создание ненужного временного объекта, что снижает производительность.

Более подробно все это можно почитать в книге Скотта Мейерса "Наиболее эффективное использование С++. 35 новых рекомендаций по улучшению ваших программ и проектов" (Правило 6. Различайте префиксную форму операторов инкремента и декремента) [1].

Также в заметке "Есть ли практический смысл использовать для итераторов префиксный оператор инкремента ++it, вместо постфиксного it++?" [2] можно познакомиться с примерами замера скорости.

Библиографический список

  • Мейерс С. Наиболее эффективное использование С++. 35 новых рекомендаций по улучшению ваших программ и проектов: Пер. с англ. - М.: ДМК Пресс, 2000. - 304 с.: ил. (Серия "Для программистов"). ISBN 5-94074-033-2. ББК 32.973.26-018.1.
  • Андрей Карпов. Есть ли практический смысл использовать для итераторов префиксный оператор инкремента ++it, вместо постфиксного it++? http://www.viva64.com/ru/b/0093/

V804. Decreased performance. The 'Foo' function is called twice in the specified expression to calculate length of the same string.

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

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

Рассмотрим пример:

if ((strlen(directory) > 0) &&
    (directory[strlen(directory)-1] != '\\'))

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

size_t directoryLen = strlen(directory);
if ((directoryLen > 0) && (directory[directoryLen-1] != '\\'))

Иногда предупреждение V804 помогает выявить гораздо более критические ошибки. Рассмотрим пример:

if (strlen(str_1) > 4 && strlen(str_1) > 8)

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

if (strlen(str_1) > 4 && strlen(str_2) > 8)

V805. Decreased performance. It is inefficient to identify an empty string by using 'strlen(str) > 0' construct. A more efficient way is to check: str[0] != '\0'

Анализатор обнаружил конструкцию, которую потенциально можно оптимизировать. Для того чтобы определить строка пустая или нет, используется функция strlen или аналогичная ей. Пример кода:

if (strlen(strUrl) > 0)

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

if (strUrl[0] != '\0')

Иногда предупреждение V805 помогает выявить избыточный код. В одном из приложений, был найден приблизительно такой код:

string path;
...
if (strlen(path.c_str()) != 0)
if (strlen(path.c_str()) != 0)

Скорее всего, такой код появился в ходе неаккуратного рефакторинга, когда тип переменной path был заменен с простого указателя на std::string. Упрощенный и быстрый вариант:

if (!path.empty())

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

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

Рассмотрим пример:

static UINT GetSize(const std::string& rStr)
{
  return (strlen(rStr.c_str()) + 1 );
}

Этот код взят из реального приложения. Обычно такие забавные фрагменты кода получаются в процессе неаккуратного рефакторинга. Этот код медленен и более, того, он возможно вообще не нужен. Можно просто в нужных местах писать выражение "string::length() + 1".

Если всё-таки хочется сделать специальную функцию, для получения длины строки вместе с терминальным нулём, то она должна выглядеть так:

inline size_t GetSize(const std::string& rStr)
{
  return rStr.length() + 1;
}

Примечание

Следует помнить, что действия "strlen(MyString.c_str())" и "MyString.length()" не всегда дают одинаковый результат. Различия будут в том случае, если строка содержит нулевые символы помимо терминального нуля. Однако подобные ситуации можно считать плохим дизайном и сообщение V806 является поводом задуматься над рефакторингом. Даже если программист, написавший такой код, хорошо знает принципы его работы, этот код будет сложен для понимания его коллегам. Коллеги будут гадать, почему написано именно так и могут заменить вызов функции "strlen()" на "length()", внеся ошибку в программу. Следует не лениться и сделать код таким, чтобы принципы его работы были понятны стороннему программисту. Например, если в строке могут быть нулевые символы, то, скорее всего, это вовсе не строка, а массив байт. И тогда нужно использовать класс std::vector или завести свой собственный класс.

V807. Decreased performance. Consider creating a pointer/reference to avoid using the same expression repeatedly.

Анализатор обнаружил код, который потенциально можно оптимизировать. В коде присутствуют однотипные цепочки вызовов (message chains) для доступа, к какому-то объекту. Под цепочкой вызовов понимаются следующие конструкции:

  • Get(1)->m_point.x
  • X.Foo().y
  • next->next->Foo()->Z

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

Рассмотрим пример:

Some->getFoo()->doIt1();
Some->getFoo()->doIt2();

Если функция 'getFoo()' работают медленно или если этот код находится внутри цикла, то тогда код стоит переписать. Например, можно создать временный указатель:

Foo* a = Some->getFoo();
a->doIt1();
a->doIt2();

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

Однако, наличие цепочек вызовов как правило говорит о неаккуратном коде. Для улучшения такого кода можно использовать несколько вариантов рефакторинга:

V1000. Did you forget to enable the /openmp compiler option?

Анализатор обнаружил ошибку, вызванную отключенной поддержкой OpenMP. Когда опция компилятора "/openmp" отключена, все директивы OpenMP игнорируются компилятором Visual C++. Включить поддержку OpenMP можно в свойствах проекта, в разделе "Configuration Properties | C/C++ | Language".

V1001. Missing 'parallel' keyword.

Анализатор обнаружил ошибку, вызванную отсутствием ключевого слова "parallel" в директиве OpenMP. Компилятор игнорирует такие неполные директивы, в результате чего они не выполняются:

#pragma omp for
for(int i = 0; i < 4; i++)
{
      #pragma omp critical
      {
            str << "\r\nThread# " << omp_get_thread_num()
                << ": Iteration# " << i;
      }
}

Приведенный выше код будет выполняться только одним потоком, потому что директива "#pragma omp for" не является полной. Для исправления ошибки необходимо добавить в директиву ключевое слово "parallel":

#pragma omp parallel for
for(int i = 0; i < 4; i++)
{
      #pragma omp critical
      {
            str << "\r\nThread# " << omp_get_thread_num()
                << ": Iteration# " << i;
      }
}

Если директива "#pragma omp for" используется в параллельной секции кода, эта директива является корректной:

#pragma omp parallel
{
      #pragma omp for
      for(int i = 0; i < 4; i++)
      {
           #pragma omp critical
           {
            str << "\r\nThread# " << omp_get_thread_num()
                << ": Iteration# " << i;
           }
      }
}

Этот код эквивалентен предыдущему варианту корректного кода. Оба варианта не будут вызывать V1001, потому что в обоих случаях директива является полной. Рассмотрим еще один пример кода, вызывающего V1001:

#pragma omp sections
{
     #pragma omp sections
     {
          #pragma omp critical
          {
            str << "\r\nThread# " << omp_get_thread_num();
          }
     }
      #pragma omp section
     {
          #pragma omp critical
          {
                str << "\r\nThread# " << omp_get_thread_num()
          }
     }
}

Поскольку директива "#pragma omp sections" не является полной, код выполнится только одним потоком. Как и в первом примере, ошибки не возникнет, если в директиве присутствует ключевое слово "parallel", или если директива находится в параллельном коде.

V1002. Missing 'omp' keyword.

Анализатор обнаружил ошибку, вызванную отсутствием ключевого слова "omp" в директиве OpenMP. Компилятор игнорирует такие неполные директивы, в результате чего они не выполняются:

#pragma omp parallel num_threads(2)
{
       #pragma single
       {
             str << "A";
       }
}

V1003. Missing 'for' keyword. Each thread will execute the entire loop.

Анализатор обнаружил возможную ошибку, вызванную отсутствием ключевого слова "for" в директиве OpenMP "#pragma omp parallel for". Рассмотрим пример кода, в котором это может привести к неожиданному поведению:

#pragma omp parallel num_threads(2)
for(int i = 0; i < 2; i++)
{
     #pragma omp critical
     {
           str << "\r\nThread# " << omp_get_thread_num()
               << ": Iteration# " << i;
     }
}

Поскольку в директиве отсутствует ключевое слово "for", работа не будет разделена между потоками, и каждый поток выполнит весь цикл целиком. В результате тело цикла будет выполнено 4 раза вместо ожидаемых 2. Корректная версия кода имеет следующий вид:

#pragma omp parallel for num_threads(2)
for(int i = 0; i < 2; i++)
{
     #pragma omp critical
     {
           str << "\r\nThread# " << omp_get_thread_num()
               << ": Iteration# " << i;
     }
}

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

#pragma omp parallel num_threads(2)
{
        for(int i = 0; i < 2; i++)
        {
                #pragma omp critical
                {
                        str << "\r\nThread# " <<
                                omp_get_thread_num() <<
                                ": Iteration# " << i;
                }
        }
}

V1004. Nested parallelization of a 'for' loop.

Анализатор обнаружил возможную ошибку, вызванную вложенным распараллеливанием кода. Рассмотрим пример кода, в котором это может привести к неожиданному поведению:

#pragma omp parallel num_threads(2)
{
      // ...
      //  N code lines
      // ...
      #pragma omp parallel for
      for(int i = 0; i < 2; i++)
      {
            #pragma omp critical
            {
                 str << "\r\nThread# "
                     << omp_get_thread_num()
                     << ": Iteration# " << i;
            }
      }
}

Использование ключевого слова "parallel" внутри параллельной секции приводит к вложенному распараллеливанию кода. В приведенном выше коде каждый из 2 изначальных потоков будет разделен еще раз, и работа по выполнению цикла распределится на две пары потоков. В результате, даже если в вашем коде вложенный параллелизм выключен, тело цикла будет выполнено 4 раза (по одному разу в каждом потоке). Чтобы избежать этого, достаточно опустить ключевое слово "parallel" в директиве "#pragma omp parallel for":

#pragma omp parallel num_threads(2)
{
      // ...
      //  N code lines
      // ...
      #pragma omp for
      for(int i = 0; i < 2; i++)
      {
            #pragma omp critical
            {
                  str << "\r\nThread# " << omp_get_thread_num()
                      << ": Iteration# " << i;
            }
      }
}

Если описанное выше поведение является для вашего кода ожидаемым и вам нужно произвести вложенное распараллеливание, вы можете подавить V1004, используя раздел настроек Detectable Errors.

V1005. The 'ordered' directive is not present in an ordered loop.

Анализатор обнаружил ошибку, вызванную отсутствием директивы "#pragma omp ordered" в упорядоченном цикле. Рассмотрим пример некорректного кода:

#pragma omp parallel for ordered
for(int i = 0; i < 4; i++)
{
      #pragma omp critical
      {
            str << "\r\nThread# "
                << omp_get_thread_num()
                << ": Iteration# " << i;
      }
}

Если в директиве "#pragma omp parallel for" или "#pragma omp for" используется выражение "ordered", в теле цикла обязательно должна содержаться директива "#pragma omp ordered", задающая область кода, для которой итерации будут выполняться последовательно:

#pragma omp parallel for ordered
for(int i = 0; i < 4; i++)
{
     #pragma omp ordered
     {
           #pragma omp critical
           {
                str << "\r\nThread# "
                    << omp_get_thread_num()
                    << ": Iteration# " << i;
           }
     }
}

V1006. Missing omp.h header file. Use '#include <omp.h>'.

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

Отсутствие заголовочного файла в файле, где используются директивы OpenMP, например такие как "#pragma omp parallel for" не является ошибкой, но является плохим стилем.

Также, если программа использует OpenMP, то она должна импортировать vcomp.lib /vcompd.lib. В противном случае произойдет ошибка на этапе выполенния. Импорт этой библиотеки осуществляется в . Поэтому, если в проекте явно не указан импорт нужных библиотек, то #include должно присутствовать хотя бы в одной из файлов проекта.

V1101. Redefining number of threads in a parallel code.

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

#pragma omp parallel
{
      omp_set_num_threads(2);
      #pragma omp parallel
      {
      }
}

Функция omp_set_num_threads изменяет количество потоков для следующей параллельной секции. Однако, если эта функция вызвана из параллельной секции, ее поведение, согласно стандарту OpenMP 2.0, является непредсказуемым. Если вам нужно задать количество потоков для вложенной параллельной секции, вы можете использовать выражение num_threads:

#pragma omp parallel
{
      #pragma omp parallel num_threads(2)
      {
      }
}

V1102. Non-symmetrical use of set/unset functions for the following lock variable(s): foo.

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

omp_lock_t myLock;
omp_init_lock(&myLock);
#pragma omp parallel sections
{
      #pragma omp section
      {
            omp_set_lock(&myLock);
      }
      #pragma omp section
      {
            omp_set_lock(&myLock);
            omp_unset_lock(&myLock);
      }
}

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

omp_lock_t myLock;
omp_init_lock(&myLock);
#pragma omp parallel sections
{
      #pragma omp section
      {
            omp_set_lock(&myLock);
      }
      #pragma omp section
      {
            omp_set_lock(&myLock);
      }
}

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

omp_lock_t myLock;
omp_init_lock(&myLock);
#pragma omp parallel sections
{
      #pragma omp section
      {
            omp_set_lock(&myLock);
            omp_unset_lock(&myLock);
      }
      #pragma omp section
      {
            omp_set_lock(&myLock);
            omp_unset_lock(&myLock);
      }
}

V1103. Threads number dependent code. The 'omp_get_num_threads' function is used in an arithmetic expression.

Анализатор обнаружил возможную ошибку, вызванную зависимостью поведения кода от количества выполняющих его потоков. Использование числа потоков в арифметических операциях небезопасно, так как в реализации стандарта OpenMP, использующейся в Visual С++, число потоков по умолчанию равняется числу виртуальных процессоров. Рассмотрим пример некорректного кода, который должен вывести в строку все 26 букв английского алфавита:

#pragma omp parallel
{
      int lettersPerThread = 26 / omp_get_num_threads();
      int thisThreadNum = omp_get_thread_num();
      int startLetter = 'a' + thisThreadNum * lettersPerThread;
      int endLetter = 'a' + thisThreadNum * lettersPerThread +
                       lettersPerThread;
      for (int i = startLetter; i < endLetter; i++)
      {
            #pragma omp critical
            {
                  str << char(i);
            }
      }
}

При выполнении этого кода на машине с четырьмя виртуальными процессорами работа распределится на четыре потока. Поскольку 26 не делится на 4 без остатка, реально выведутся только 24 буквы из 26. Для исправления ошибки следует исключить число потоков из арифметических вычислений (в данном случае это приведет также и к упрощению кода):

#pragma omp parallel for
for (int i = 'a'; i <= 'z'; i++)
{
      #pragma omp critical
      {
            str << char(i);
      }
}

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

const int numThreads = 2;
#pragma omp parallel num_threads(numThreads)
{
      int lettersPerThread = 26 / numThreads();
      int thisThreadNum = omp_get_thread_num();
      int startLetter = 'a' + thisThreadNum * lettersPerThread;
      int endLetter = 'a' + thisThreadNum * lettersPerThread +
                       lettersPerThread;
      for (int i = startLetter; i < endLetter; i++)
      {
            #pragma omp critical
            {
                  str << char(i);
            }
      }
}

Если вам нужно подавить V1103, вы можете использовать временную переменную:

int threads = omp_get_num_threads();
int lettersPerThread = 26 / threads;

V1104. Redefining nested parallelism in a parallel code.

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

#pragma omp parallel
{
      omp_set_nested(1);
}

Согласно стандарту OpenMP 2.0, вложенный параллелизм допускается включать/выключать лишь в последовательном коде. В случае вызова функции omp_set_nested из параллельной секции результат является непредсказуемым. Корректная версия кода приведена ниже:

omp_set_nested(1);
#pragma omp parallel
{
}

V1201. Concurrent usage of a shared resource via an unprotected call of the 'foo' function.

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

#pragma omp parallel num_threads(2)
{
      printf("Hello, world!\r\n");
}

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

HellHell oo WorWlodrld

Для того, чтобы обезопасить вызов функции, работающей с общим ресурсом, можно воспользоваться критическими секциями:

#pragma omp parallel num_threads(2)
{
      #pragma omp critical
      {
            printf("Hello, world!\r\n");
      }
}

Еще одно возможное решение - использовать блокировки. Однако, стоит помнить, что блокировки работают медленнее, чем критические секции. Рассмотрим еще одну корректную версию кода:

omp_lock_t lock;
omp_init_lock(&lock)
#pragma omp parallel num_threads(2)
{
      omp_set_lock(&lock);
      printf("Hello, world!\r\n");
      omp_unset_lock(&lock);
}

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

#pragma omp parallel num_threads(2)
{
      #pragma omp single
      {
            printf("Hello, world!\r\n");
      }
}

V1202. The 'flush' directive should not be used for the 'foo' variable, because the variable has pointer type.

Анализатор обнаружил ошибку, вызванную использованием директивы "#pragma omp flush" с указателем. Применять эту директиву к указателю бессмысленно, так как данная операция не затрагивает область памяти, адрес которой хранится в переменной. Рассмотрим пример кода, производящего чтение данных с какого-либо устройства в одном потоке и выводящем эти данные в другом потоке:

int* a = new int[1];
a[0] = 0;
int flag = 0;
#pragma omp parallel sections
{
 #pragma omp section
{
  my_read_func(a);
  #pragma omp flush(a)
  flag = 1;
  #pragma omp flush(flag)
}
#pragma omp section
{
  while (flag != 1)
  {
   #pragma omp flush(flag)
  }
  #pragma omp flush(a)
  str << "\r\ndata = " << a[0];
 }
}
delete[] a;

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

int* a = new int[1];
a[0] = 0;
int flag = 0;
#pragma omp parallel sections
{
 #pragma omp section
{
  my_read_func(a);
  #pragma omp flush
  flag = 1;
  #pragma omp flush(flag)
}
#pragma omp section
{
  while (flag != 1)
  {
   #pragma omp flush(flag)
  }
  #pragma omp flush
  str << "\r\ndata = " << a[0];
 }
}
delete[] a;

V1203. Using the 'threadprivate' directive is dangerous, because it affects the entire file. Use local variables or specify access type for each parallel block explicitly instead.

Анализатор обнаружил в коде директиву "#pragma omp threadprivate". Эта директива опасна, поскольку она влияет не на локальный фрагмент кода, а на весь файл. Рассмотрим пример некорректного кода:

int threadprivate_var;
#pragma omp threadprivate(threadprivate_var)
...
threadprivate_var = 0;
#pragma omp parallel num_threads(4)
{
    #pragma omp atomic
    threadprivate_var++;
    #pragma omp barrier
}

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

threadprivate_var = 0;
int b = threadprivate_var;
#pragma omp parallel num_threads(4)
{
     #pragma omp atomic
     b++;
     #pragma omp barrier
}
threadprivate_var = b;

Однако, такое решение позволит обойти проблему лишь в отдельно взятом участке кода и не гарантирует, что ошибка никогда не возникнет в других частях того же файла позднее. Наиболее безопасное решение - отказаться от использования директивы threadprivate и создавать локальные переменные в каждом потоке, либо, если это необходимо, делать переменные, объявленные ранее, локальными для каждой конкретной параллельной секции, явно указывая режим доступа. Если вы уверены, что директива "#pragma omp threadprivate" является для вашего кода необходимой, вы можете подавить V1203, используя раздел настроек Detectable Errors.

V1204. Data race risk. Unprotected static variable declaration in a parallel code.

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

#pragma omp parallel num_threads(2)
{
    static int cachedResult = ComputeSomethingSlowly();
    int res = cachedResult;
    ...
}

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

#pragma omp parallel num_threads(2)
{
     int res;
     #pragma omp critical
     {
          static int cachedResult = ComputeSomethingSlowly();
          res = cachedResult;
     }
          ...
}

V1205. Data race risk. Unprotected concurrent operation with the 'foo' variable.

Анализатор обнаружил в коде ошибку, вызванную одновременной работой потоков с общей переменной. Рассмотрим пример некорректного кода:

int a = 0;
#pragma omp parallel for num_threads(4)
for (int i = 0; i < 100000; i++)
{
      a++;
}

Поскольку все потоки пишут в одну и ту же область памяти и читают из нее одновременнно, значение переменной после этого цикла является непредсказуемым. Чтобы обезопасить эту операцию, ее нужно поместить в критическую секцию, либо (поскольку в данном примере операция является элементарной) использовать директиву "#pragma omp atomic". Второй вариант является более предпочтительным, так как приводит к более быстрому коду:

int a = 0;
#pragma omp parallel for num_threads(4)
for (int i = 0; i < 100000; i++)
{
      #pragma omp atomic
      a++;
}

Если же тип операции не позволяет использовать директиву "#pragma omp atomic", можно воспользоваться критическими секциями или блокировками. Критические секции являются более предпочтительным решением, так как они работают быстрее:

int a = 0;
#pragma omp parallel for num_threads(4)
for (int i = 0; i < 100000; i++)
{
      #pragma omp critical
      {
            ...
      }
}

Отметим, что V1205 не возникнет, если переменная является локальной, или код гарантированно выполняется только одним потоком (например, находится в области действия директивы "#pragma omp single").

V1206. Data race risk. The value of the 'foo' variable can be changed concurrently via the 'bar' function.

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

void dangerousFunction(int &param)
{
      param += 1;
}
...
#pragma omp parallel for
for (int i = 0; i < 100000; i++)
{
      dangerousFunction(a);
}

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

void dangerousFunction2(int *param)
{
      (*param) += 1;
}
...
#pragma omp parallel for
for (int i = 0; i < 100000; i++)
{
      dangerousFunction2(&a);
}

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

#pragma omp parallel for
for (int i = 0; i < 100000; i++)
{
      #pragma omp critical
      {
            dangerousFunction2(&a);
      }
}

Отметим, что V1206 не возникнет, если переменная является локальной, или код гарантированно выполняется только одним потоком (например, находится в области действия директивы "#pragma omp single").

V1207. Data race risk. The 'foo' object can be changed concurrently by a non-const function.

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

class MyClass
{
      int a;
      public: void nonConstMethod() {a++;};
      public: int getA() const {return a;};
};
...
MyClass obj;
#pragma omp parallel for num_threads(2)
for (int i = 0; i < 100000; i++)
{
      obj.nonConstMethod();
}
str << "Result: " << obj.getA() << "\r\n";

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

...
#pragma omp parallel for num_threads(2)
for (int i = 0; i < 100000; i++)
{
      #pragma omp critical
      {
            obj.nonConstMethod();
      }
}
...

Если же вызывающий V1207 метод не изменяет состояние объекта, вы можете подавить V1207, объявив метод как константный:

public:
void сonstMethod() const
{
  ...
}

Отметим, что V1207 не возникнет, если переменная является локальной, или код гарантированно выполняется только одним потоком (например, находится в области действия директивы "#pragma omp single").

V1208. The 'foo' variable of reference type cannot be private.

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

int a = 0;
int& refvar = a;
#pragma omp parallel for num_threads(2) private(refvar)
for (int i = 0; i < 100000; i++)
{
       refvar++;
}

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

int a = 0;
#pragma omp parallel num_threads(2) firstprivate(a)
{
      for (int i = 0; i < 100000; i++)
      {
            a++;
      }
}

V1209. Warning: The 'foo' variable of pointer type should not be private.

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

int* a = new int[1];
a[0] = 0;
#pragma omp parallel firstprivate(a) num_threads(2)
{
      for (int i = 0; i < 100000; i++)
      {
            a[0]++;
      }
      ...
}
delete[] a;

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

#pragma omp parallel firstprivate(a) num_threads(2)
{
      int* a = new int[1];
      a[0] = 0;
      for (int i = 0; i < 100000; i++)
      {
            a[0]++;
      }
      ...
      delete[] a;

V1210. The 'foo' variable is marked as lastprivate but is not changed in the last section.

Анализатор обнаружил в коде возможную ошибку, вызванную тем, что переменная, объявленная как lastprivate, не изменяется в последней секции. Согласно стандарту OpenMP 2.0, переменная, объявленная как lastprivate, после выполнения цикла принимает значение из лексически последней секции. Если в последней секции переменная не изменяется, это может привести к непредсказуемым результатам. Например, следующий код вызовет ошибку во время выполнения программы (в последней секции переменная "а" не изменяется):

int a;
#pragma omp parallel sections lastprivate(a)
{
      #pragma omp section
      {
            a = 0;
            for (int i = 0; i < 100000; i++)
            {
                  a++;
            }
      }
      #pragma omp section
      {
            ...
      }
}

Если поменять код секций местами, код будет работать корректно:

int a;
#pragma omp parallel sections lastprivate(a)
{
      #pragma omp section
      {
            ...
      }
      #pragma omp section
      {
            a = 0;
            for (int i = 0; i < 100000; i++)
            {
                  a++;
            }
      }
}

V1211. The use of 'flush' directive has no sense for private 'foo' variable, and can reduce performance.

Анализатор обнаружил потенциально неэффективный код, содержащий избыточные директивы flush. Директива flush не имеет смысла для локальных переменных, а также переменных помеченных как private, threadprivate, lastprivate или firstprivate. Анализатор предупреждает о применении директивы flush к таким переменным.

Избыточное использование директивы flush не приводит к ошибке, но снижает производительность приложения. В демонстрационной программе ParallelSample, входящей в состав PVS-Studio, имеется пример, который показывает возможное замедление работы программы. Код с лишними flush работает в 20 раз медленнее, чем без них (см. код функций V1211 и V1211_correct).

Приведем пример, приводящий к выводу диагностического сообщения V1211:

#pragma omp parallel for num_threads(4) firstprivate(a)
for (int i = 0; i < n; i++)
{
  #pragma omp flush(i) // V1211
  #pragma omp flush(a) // V1211
  Array[i] = a;
}

Исправление кода состоит в удалении лишних директив flush:

#pragma omp parallel for num_threads(4) firstprivate(a)
for (int i = 0; i < n; i++)
{
  Array[i] = a;
}

Код, где директива flush имеет смысл, считается корректным и диагностическое сообщение не выдается.

Пример:

int a;
#pragma omp parallel for num_threads(4)
for (int i = 0; i < n; i++)
{
  #pragma omp flush(a)
}

V1212. Data race risk. When accessing the array 'foo' in a parallel loop, different indexes are used for writing and reading.

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

#pragma omp parallel for
for (ptrdiff_t i = 1; i < len; i++)
{
  arr[i - 1] += arr[i];;
}

В коде мы суммируем каждый элемент массива с последующим элементом. Код некорректно распараллелен, и при входных данных "1 1 1 1 1 1" мы можем получить последовательность "2 2 3 2 2 1", вместо ожидаемой "2 2 2 2 2 1".

Исправление подобного вида ошибок состоит в изменении алгоритма и/или использовании механизмов синхронизации.

Пример кода, который анализатор посчитает корректным, так как в нем нет обращений к одному массиву посредством различных индексов:

int a;
#pragma omp parallel for num_threads(4)
for (int i = 0; i < n; i++)
{
  #pragma omp flush(a)
}

V1301. The 'throw' keyword cannot be used outside of a try..catch block in a parallel section.

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

void foo1301(const char **strings, ptrdiff_t n)
{
  #pragma omp parallel for
  for (ptrdiff_t i = 0; i < n; i++)
  {
    if (!strings[i])
      throw MyException();
    DoSomething(strings[i]);
  }
}

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

void foo1301_fixed1(const char **strings, ptrdiff_t n)
{
  ptrdiff_t errCount = 0;
  #pragma omp parallel for reduction(+: errCount)
  for (ptrdiff_t i = 0; i < n; i++)
  {
    try
    {
      if (!strings[i])
        throw MyException();
      DoSomething(strings[i]);
    }
    catch (MyException &)
    {
      #pragma omp atomic
      ++errCount;
    }
  }
  if (errCount != 0)
    throw MyException();
}
void foo1301_fixed2(const char **strings, ptrdiff_t n)
{
  ptrdiff_t errCount = 0;
  #pragma omp parallel for reduction(+: errCount)
  for (ptrdiff_t i = 0; i < n; i++)
  {
    if (!strings[i])
      ++errCount;
    else
      DoSomething(strings[i]);
  }
  if (errCount != 0)
    throw MyException();
}

V1302. The 'new' operator cannot be used outside of a try..catch block in a parallel section.

Анализатор обнаружил ошибку, связанную с выбрасыванием исключения из параллельного блока. Согласно спецификации OpenMP, если вы используете исключения внутри параллельного блока, то все эти исключения должны быть обработаны внутри этого блока. Если вы используете внутри параллельного кода оператор new, то вы должны позаботиться о перехвате исключения, которое согласно стандарту языка Си++ будет сгенерировано при ошибке выделения памяти. Приведенный ниже пример приведет к некорректному поведению программы и скорее всего к ее аварийному завершению, если произойдет ошибка выделения памяти:

void foo1302(ptrdiff_t n)
{
  #pragma omp parallel for
  for (ptrdiff_t i = 0; i < n; i++)
  {
    float *array = new float[10000];
    //...
    delete [] array;
  }
}

Исправление кода состоит в обработке исключений внутри параллельного блока и передачи информации об ошибке через иные механизмы или в отказе от использования оператора ‘new’. Исправленный вариант функции:

void foo1302_fixed(ptrdiff_t n)
{
  #pragma omp parallel for
  for (ptrdiff_t i = 0; i < n; i++)
  {
    try {
      float *array = new float[10000];
      //...
      delete [] array;
    }
    catch (std::bad_alloc &) {
      // process exception
    }
  }
}

V1303. The 'foo' function which throws an exception cannot be used in a parallel section outside of a try..catch block.

Анализатор обнаружил ошибку, связанную с выбрасыванием исключения из параллельного блока. Согласно спецификации OpenMP, если вы используете исключения внутри параллельного блока, то все эти исключения должны быть обработаны внутри этого блока. Анализатор предупреждает о вызове функции, которая отмечена, как бросающая исключения в параллельном блоке и при этом не защищенная try..catch блоком. В случае выбрасывания исключения из функции ExceptionFoo приведенный ниже пример приведет к некорректному поведению программы и скорее всего к ее аварийному завершению:

void ExceptionFoo() throw(...) { }
void foo1303(ptrdiff_t n)
{
  #pragma omp parallel for
  for (ptrdiff_t i = 0; i < n; i++)
  {
    //...
    ExceptionFoo();
    //...
  }
}

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

void foo1303_fixed(ptrdiff_t n)
{
  #pragma omp parallel for
  for (ptrdiff_t i = 0; i < n; i++)
  {
    try {
      //...
      ExceptionFoo();
      //...
    }
    catch (...) {
      // process exception
    }
  }
}

Следует учитывать, что функции, не помеченные как throw(...) тоже могут генерировать исключения. Но анализатор VivaMP не считатет их вызов опасным. Это сделано, чтобы генерировать диагностические сообщения в разумных маштабах. Иначе любой код, где присуствует вызов функции будет считаться опасным. Принят следующий принцип:

void foo(); - предполагаем, что не бросает исключение

void foo() throw();- не бросает исключение

void foo() throw(...); - бросает исключение

V2001. Consider using the extended version of the FOO function here.

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

Анализатор позволяет обнаружить вызов функций, у которых существует "расширенный" аналог. Под термином "расширенные" понимаются функции, имеющие суффикс Ex. Примеры расширенных функций: VirtualAllocEx, SleepEx, GetDCEx, LoadLibraryEx, FindResourceEx.

Рассмотрим исходный код:

void foo();
void fooEx(float x);
void foo2();
...
void test()
{
  foo(); // V2001
  foo2(); // OK
}

В месте вызова функции "foo", будет выдано диагностическое сообщение V2001, так как имеется функция с тем же именем, но оканчивающаяся на "Ex". Функция "foo2" не имеет альтернативного варианта, и диагностическое сообщение выдаваться не будет.

Сообщение V2001 будет также выдано для случая:

void fooA(char *p);
void fooExA(char *p, int x);
...
void test()
{
  fooA(str); // V2001
}

Родственным диагностическим сообщением является V2002.

V2002. Consider using the 'Ptr' version of the FOO function here.

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

Анализатор позволяет обнаружить вызов функций, у которых существует 'Ptr' аналог. Имеются в виду функции, имеющие в составе своего названия суффикс Ptr. Примеры расширенных функций: SetClassLongPtr, DSA_GetItemPtr.

Рассмотрим исходный код:

void foo(int a);
void fooPtr(int a, bool b);
void foo2();
...
void test()
{
  foo(1); // V2002
  foo2(); // OK
}

В месте вызова функции "foo", будет выдано диагностическое сообщение V2002, так как имеется функция с тем же именем, но оканчивающаяся на "Ptr". Функция "foo2" не имеет альтернативного варианта, и диагностическое сообщение выдаваться не будет.

Сообщение V2002 будет также выдано для случая:

void fooA(char *p);
void fooPtrA(char *p, int x);
...
void test()
{
  fooA(str); // V2002
}

Родственным диагностическим сообщением является V2001.

V2003. Explicit conversion from 'float/double' type to signed integer type.

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

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

Примеры конструкций, на которые анализатор выдаст данное диагностическое сообщение:

float f;
double d;
long double ld;
int i;
short s;
...
i = int(f); // V2003
s = static_cast<short>(d); // V2003
i = (int)ld;  // V2003

Родственным диагностическим сообщением является V2004.

V2004. Explicit conversion from 'float/double' type to unsigned integer type.

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

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

Примеры конструкций, на которые анализатор выдаст данное диагностическое сообщение:

float f;
double d;
long double ld;
unsigned u;
size_t s;
...
u = unsigned(f); // V2004
s = static_cast<size_t>(d); // V2004
u = (unsigned)ld;  // V2004

Родственным диагностическим сообщением является V2003.

V2005. C-style explicit type casting is utilized. Consider using: static_cast/const_cast/reinterpret_cast.

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

Анализатор позволяет обнаружить явные приведения типов в программе на Си++, написанные в старом стиле языка Си. В языке Си++ более безопасно явно приводить типы с использованием операторов static_cast, const_cast и reinterpret_cast.

Диагностическое правило V2005 помогает выполнить рефакоринг кода, поменять старый стиль приведения типов на новый стиль. Иногда это позволяет выявить ошибки.

Примеры конструкций, на которые анализатор выдаст данное диагностическое сообщение:

int i;
double d;
size_t s;
void *p;
...
i = int(p); //V2005
d = (double)d; //V2005
s = (size_t)(i); //V2005

Диагностическое сообщение V2005 не выдается в трёх случаях.

1. Это программа на языке Си.

2. Осуществляется приведение к типу void. Такое приведение типа никакой опасности в себе не несёт и используется, чтобы подчеркнуть, что некий результат никак не используется. Пример:

(void)fclose(f);

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

#define FAILED(hr) ((HRESULT)(hr) < 0)
#define SRCCOPY (DWORD)0x00CC0020
#define RGB(r,g,b)\
((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))\
|(((DWORD)(BYTE)(b))<<16)))

Дополнительные ссылки

Информация о правах и торговых марках

Windows, Visual Studio, Visual C++ are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.

Other product and company names mentioned herein may be the trademarks of their respective owners.

PVS-Studio uses SourceGrid control (sourcegrid.codeplex.com). Bellow you can read Source Grid License.

SourceGrid LICENSE (MIT style)

Copyright (c) 2009 Davide Icardi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Portions of PVS-Studio are based in part of OpenC++. Bellow you can read OpenC++ Copyright Notice.

*** Copyright Notice

Copyright (c) 1995, 1996 Xerox Corporation.

All Rights Reserved.

Use and copying of this software and preparation of derivative works based upon this software are permitted. Any copy of this software or of any derivative work must include the above copyright notice of Xerox Corporation, this paragraph and the one after it. Any distribution of this software or derivative works must comply with all applicable United States export control laws.

This software is made available AS IS, and XEROX CORPORATION DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND NOTWITHSTANDING ANY OTHER PROVISION CONTAINED HEREIN, ANY LIABILITY FOR DAMAGES RESULTING FROM THE SOFTWARE OR ITS USE IS EXPRESSLY DISCLAIMED, WHETHER ARISING IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, EVEN IF XEROX CORPORATION IS ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

*** Copyright Notice

Copyright (C) 1997-2001 Shigeru Chiba, Tokyo Institute of Technology.

Permission to use, copy, distribute and modify this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation.

Shigeru Chiba makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.

*** Copyright Notice

Permission to use, copy, distribute and modify this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. Other Contributors make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.

2001-2003 (C) Copyright by Other Contributors.

PVS-Studio can use Clang as preprocessor. Read Clang/LLVM license:

==============================================================================

LLVM Release License

==============================================================================

University of Illinois/NCSA

Open Source License

Copyright (c) 2007-2011 University of Illinois at Urbana-Champaign.

All rights reserved.

Developed by:

LLVM Team

University of Illinois at Urbana-Champaign

http://llvm.org

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution.

* Neither the names of the LLVM Team, University of Illinois at Urbana-Champaign, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.

==============================================================================

The LLVM software contains code written by third parties. Such software will have its own individual LICENSE.TXT file in the directory in which it appears. This file will describe the copyrights, license, and restrictions which apply to that code.

The disclaimer of warranty in the University of Illinois Open Source License applies to all code in the LLVM Distribution, and nothing in any of the other licenses gives permission to use the names of the LLVM Team or the University of Illinois to endorse or promote products derived from this Software.

The following pieces of software have additional or alternate copyrights, licenses, and/or restrictions:

Program Directory

------- ---------

<none yet>