![]() PVS-Studio Static Code Analyzer for 64-bit and parallel C/C++ code
|
||||||||||||||||||||||||||||||||||||||
![]() ![]() ![]() ![]() ![]()
11.03.2010
Parallel notes N4 - continuing to study OpenMP constructs In this post we will continue to introduce you into OpenMP technology and tell you about some functions and new directives.»
02.03.2010
Parallel notes N3 - base OpenMP constructs Now we would like to start introducing you into OpenMP technology and show you the ways of using it.»
28.02.2010
In what way can C++0x standard help you eliminate 64-bit errors Programmers see in C++0x standard an opportunity to use lambda-functions and other entities I do not quite understand :).» ![]()
10.12.2009
PVS-Studio FAQ This paper contains some questions and answers about PVS-Studio code analyzer by OOO "Program Verification Systems".»
09.12.2009
VivaCore FAQ This paper contains some questions and answers about VivaCore C/C++ code analysis library by OOO "Program Verification Systems"»
23.11.2009
PVS-Studio: using the function "Mark as False Alarm"
The article describes and demonstrates by an example the use of PVS-Studio 3.40 new function "Mark as False Alarm". » ![]() |
Parallel Programming![]() An unsuccessful attempt to compare PVS-Studio (VivaMP) and Intel C/C++ ("Parallel Lint")Andrey Karpov
| |||||||||||||||||||||||||||||||||||||
#pragma omp for for(int i = 0; i < 100; i++) { ... } |
Yet, absence of the word "parallel" in couple with the word "for", for example, does not mean a mistake by itself. If "for" or "sections" directive is situated inside a parallel section defined by "parallel" directive the code will be correct:
#pragma omp parallel { #pragma omp for for(int i = 0; i < 100; i++) ... } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1001.
Intel C++ (Parallel Lint): not diagnosed.
Point 2. Forgotten "omp" directive
The error occurs when omp directive has been obviously forgotten. The error is detected even during simple compilation of code (without using Parallel Lint) but we will consider it as well in our comparison. The error relates to the class of errors made through lack of attention and leads to incorrect code behavior. An example:
#pragma single
|
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1002.
Intel C++ (Parallel Lint): diagnosed as warning #161.
Point 3. Forgotten "for" directive
Sometimes a programmer can forget to write "for" directive and it leads to execution of two loops instead of their paralleling. It is reasonable to warn the programmer that the code contains a potential error. An example of suspicious code:
#pragma omp parallel num_threads(2) for(int i = 0; i < 2; i++) ... |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1003.
Intel C++ (Parallel Lint): not diagnosed.
Point 4. Multiple loop paralleling
Code containing multiple paralleling can potentially have an error or lead to inefficient use of computation resources. If a parallel loop is used inside a parallel section, it is most likely to be redundant and reduce performance. An example of code where it is logically possible to make an internal loop non-parallel:
#pragma omp parallel for for(int i = 0; i < 100; i++) { #pragma omp parallel for for(int j = 0; j < 100; j++) ... } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1004.
Intel C++ (Parallel Lint): not diagnosed.
Point 5. Forgotten "order" directive
You should consider unsafe using "for" and "ordered" directives together if no "ordered" directive is used after them inside the loop defined by for operator. For example:
#pragma omp parallel for ordered for(int i = 0; i < 4; i++) { foo(i); } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1005.
Intel C++ (Parallel Lint): not diagnosed.
Point 6. <omp.h> header file is not included
When <omp.h> header file is not included in the file where such OpenMP directives are used as, for example, "#pragma omp parallel for", it can theoretically lead to an error and is a bad style.
If a program uses OpenMP it should import vcomp.lib/vcompd.lib. Otherwise, an error will occur at the stage of execution if you use Visual C++ compiler. Import of this library is performed in the file omp.h. That is why if import of necessary libraries is not stated explicitly in the project, #include <omp.h> must be present at least in one of the project's files; or library import must be explicitly defined in the project's settings.
In PVS-Studio, this diagnostic warning has a low priority and is active only in "Pedantic Mode". So it will not bother you for nothing.
But despite its rare occurrence, this is a real error and that's why was included into comparison. Here is an example from practice. Open parallel_lint project included into Intel C++. Compile it with Visual C++. Launch it and get an error when launching the executable as shown in Figure 14.

The cause is a forgotten #include <omp.h>. Note that if you build the project with Intel C++ no errors will occur.
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1006.
Intel C++ (Parallel Lint): not diagnosed.
Point 7. omp_set_num_threads inside a parallel section
A call of omp_set_num_threads function is incorrect inside a parallel section defined by "parallel" directive. In C++ it leads to errors during execution of the program and its crash. An example:
#pragma omp parallel
{
omp_set_num_threads(2);
}
|
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1101.
Intel C++ (Parallel Lint): not diagnosed.
Point 8. Oddness of the number of locks and unlocks
You should consider unsafe an odd use of the functions omp_set_lock, omp_set_nest_lock, omp_unset_lock and omp_unset_nest_lock inside a parallel section. An example of incorrect code:
#pragma omp parallel sections { #pragma omp section { omp_set_lock(&myLock); } } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1102.
Intel C++ (Parallel Lint): not diagnosed.
Point 9. omp_get_num_threads in arithmetic operations
omp_get_num_threads can be very dangerous when used in some arithmetic expression. An example of incorrect code:
int lettersPerThread = 26 / omp_get_num_threads();
|
The error consists in that if omp_get_num_threads returns, for example, value 4, division with a remainder will take place. As a result, some objects will remain unprocessed. You can see a more thorough example in the article "32 OpenMP traps for C++ developers" or in Parallel Sample demo-example included into PVS-Studio distribution kit.
Other expressions using omp_get_num_threads can be quite correct. Here is an example which does not cause diagnostic warnings in PVS-Studio:
bool b = omp_get_num_threads() == 2;
|
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1103.
Intel C++ (Parallel Lint): not diagnosed.
Point 10. omp_set_nested inside a parallel section
A call of omp_set_nested function is incorrect inside a parallel section defined by "parallel" directive. An example:
#pragma omp parallel
{
omp_set_nested(2);
}
|
But if omp_set_nested function is in a nested block created by "master" or "single" directives, it will be correct:
#pragma omp parallel { #pragma omp master { omp_set_nested(2); } } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1104.
Intel C++ (Parallel Lint): not diagnosed.
Point 11. Parallel use of shared resources
You should consider unsafe unprotected use of functions in parallel sections if these functions use shared resources. Examples of such functions: printf, OpenGL functions.
An example of unsafe code:
#pragma omp parallel { printf("abcd"); } |
An example of safe code when the function call is protected:
#pragma omp parallel { #pragma omp critical { printf("abcd"); } } |
Diagnosis:
PVS-Studio (VivaMP): partly diagnosed as error V1201.
Intel C++ (Parallel Lint): partly diagnosed as error #12158.
Diagnosis is partial due to the following reasons:
In VivaMP the list of unsafe functions is far from being complete and requires enlargement.
Unfortunately, Parallel Lint analyzer generates a false warning when the function call is protected. And again, there is no information about completeness of the function list.
Point 12. "flush" for a pointer
"flush" directive serves for the threads to update values of shared variables. When using flush for a pointer it is very likely that the programmer is mistaken considering that the data at the pointer's target address will be updated. Actually, it is the value of the variable containing the pointer that will be updated. More than that, OpenMP standard clearly says that the variable-argument of flush directive must not be a pointer.
An example:
int *t; ... #pragma omp flush(t) |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1202.
Intel C++ (Parallel Lint): not diagnosed.
Point 13. Using threadprivate
threadprivate directive is very dangerous and using it is rational only at a last resort. If possible avoid using this directive. To learn more about the dangers of using threadprivate see the article "32 OpenMP traps for C++ developers" [2].
An example of unsafe code:
#pragma omp threadprivate(var)
|
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1203.
Intel C++ (Parallel Lint): not diagnosed.
Point 14. Race condition. Static variables
One of the race condition errors. Initialization of a static variable inside a parallel section is forbidden without a special protection. For example:
#pragma omp parallel num_threads(2) { static int cachedResult = ComputeSomethingSlowly(); ... } |
A correct example of using protection:
#pragma omp parallel num_threads(2) { #pragma omp critical { static int cachedResult = ComputeSomethingSlowly(); ... } } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1204.
Intel C++ (Parallel Lint): diagnosed as warning #12246.
Point 15. Race condition. Shared variable
One of the race condition errors. Two or more threads modify the same shared variable which is not specially protected. For example:
int a = 0; #pragma omp parallel for num_threads(4) for (int i = 0; i < 100000; i++) { a++; } |
A correct example with protection:
int a = 0; #pragma omp parallel for num_threads(4) for (int i = 0; i < 100000; i++) { #pragma omp atomic a++; } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1204.
Intel C++ (Parallel Lint): diagnosed as warning #12246, warning #12247, warning #12248.
Point 16. Race condition. Array access
One of the race condition errors. Two or more threads try to work with the same array items using different expressions for calculating indexes. For example:
int i; int factorial[10]; factorial[0]=1; #pragma omp parallel for for (i=1; i < 10; i++) { factorial[i] = i * factorial[i-1]; } |
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): diagnosed as warning #12246.
Point 17. Race condition. Unsafe function
One of the race condition errors. Two or more threads call a function accepting as a formal argument a value by a non-constant link or non-constant pointer. When calling such a function inside a parallel section, a shared variable is passed into it. No protection is used during this process.
An example of potentially unsafe code:
void dangerousFunction(int& param); void dangerousFunction2(int* param); int a = 0; #pragma omp parallel num_threads(4) { #pragma omp for for (int i = 0; i < 100000; i++) { dangerousFunction(a); dangerousFunction2(&a); } } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1206.
Intel C++ (Parallel Lint): not diagnosed.
Point 18. Race condition. Object modification
One of the race condition errors. Two or more threads are calling a non-constant class function from a shared object. No protection is used during this process.
An example of potentially unsafe code:
MyClass obj; #pragma omp parallel for num_threads(2) for (int i = 0; i < 100000; i++) { obj.nonConstMethod(); } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1207.
Intel C++ (Parallel Lint): not diagnosed.
Point 19. Private pointers
You should consider unsafe applying directives "private", "firstprivate" and "threadprivate" to pointers (not arrays).
An example of unsafe code:
int *arr; #pragma omp parallel for private(arr) |
If a variable is a pointer, each thread gets a local copy of this pointer and as a result, all the threads work with shared memory through it. It is very likely that the code contains an error.
An example of safe code where each thread works with its own array:
int arr[4]; #pragma omp parallel for private(arr) |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1209.
Intel C++ (Parallel Lint): not diagnosed.
Point 20. Careless use of "lastprivate"
"lastprivate" directive after a parallel section assigns to a variable a value from the lexically last section or from the last loop iteration. The code where the marked variable is not modified in the last section is likely to be incorrect.
An example:
#pragma omp sections lastprivate(a) { #pragma omp section { a = 10; } #pragma omp section { } } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1210.
Intel C++ (Parallel Lint): not diagnosed.
Point 21. Senseless "flush". Local variables
You should consider senseless using "flush" directive for local variables (defined in a local section) and also variables marked as threadprivate, private, lastprivate, firstprivate.
"flush" directive is senseless for these variables for they always contain actual values. And thus reduce code performance.
An example:
int a = 1; #pragma omp parallel for private(a) for (int i = 10; i < 100; ++i) { #pragma omp flush(a); ... } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1211.
Intel C++ (Parallel Lint): not diagnosed.
Point 22. Senseless "flush". Implicit flush is present
You should consider inefficient using "flush" directive in those places where it is executed implicitly. Here are cases when "flush" directive is implicit and using it is senseless:
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): not diagnosed.
Point 23. Exceptions. Explicit throw
According to OpenMP specification, all exceptions must be processed inside a parallel section. An exception leaving the parallel section will lead to program fail and most likely to crash.
An example of incorrect code:
try { #pragma omp parallel for num_threads(4) for(int i = 0; i < 4; i++) { throw 1; } } catch (...) { } |
Correct code:
size_t errCount = 0; #pragma omp parallel for num_threads(4) reduction(+: errCount) for(int i = 0; i < 4; i++) { try { throw 1; } catch (...) { ++errCount; } } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1301.
Intel C++ (Parallel Lint): not diagnosed.
Point 24. Exceptions. new operator
According to OpenMP specification, all exceptions must be processed inside a parallel section. In case of memory allocation error "new" operator generates an exception and this must be considered when using it in parallel sections.
An example of incorrect code:
try { #pragma omp parallel for num_threads(4) for(int i = 0; i < 4; i++) { float *ptr = new (MyPlacement) float[1000]; delete [] ptr; } } catch (std::bad_alloc &) { } |
To learn more about this topic see the following blog-notes by PVS-Studio developers:
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1302.
Intel C++ (Parallel Lint): not diagnosed.
Point 25. Exceptions. Functions
According to OpenMP specification, all exceptions must be processed inside a parallel section. If inside a parallel section a function is used marked as explicitly throwing exceptions, the exception must be processed inside the parallel section.
An example of incorrect code:
void MyThrowFoo() throw(...) { throw 1; } try { #pragma omp parallel for num_threads(4) for(int i = 0; i < 4; i++) { MyThrowFoo(); } } catch (...) { } |
Diagnosis:
PVS-Studio (VivaMP): diagnosed as error V1303.
Intel C++ (Parallel Lint): not diagnosed.
Point 26. Forgotten initialization. omp_init_lock
You should consider incorrect using variables of omp_lock_t / omp_nest_lock_t type without their preliminary initialization in functions omp_init_lock / omp_init_nest_lock. By using we understand a call of omp_set_lock function etc.
An example of incorrect code:
omp_lock_t myLock;
#pragma omp parallel num_threads(2)
{
omp_set_lock(&myLock);
omp_unset_lock(&myLock);
}
|
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): not diagnosed.
Point 27. Forgotten initialization. Local variables
You should consider incorrect using variables defined in a parallel region as local with "private" and "lastprivate" directives without their preliminary initialization. For example:
int a = 0; #pragma omp parallel private(a) { a++; } |
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): diagnosed as error #12361.
Point 28. Forgotten initialization after a parallel region
You should consider incorrect using after a parallel code section those variables to which "private", "threadprivate" or "firstprivate" directives were applied. Before using them further, they must be initialized again.
An example of incorrect code:
#pragma omp parallel private(a) { ... } b = a; |
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): diagnosed as error #12352, error #12358.
Point 29. Absence of copy constructor
You should consider unsafe applying "firstprivate" and "lastprivate" directives to class instances where copy constructor is absent.
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): not diagnosed.
Point 30. Inefficient use of critical sections
You should consider inefficient using critical sections or functions of omp_set_lock class where "atomic" directive is quite enough. "atomic" directive works faster than critical sections for some atomic operations can be directly replaced with processor commands. Consequently, it is desirable that you use this directive wherever you need protection of shared memory at elementary operations. According to OpenMP specification, to such operations the following ones refer:
Here x is a scalar variable, expr is an expression with scalar types where x variable is absent, binop is a non-overloaded operator +, *, -, /, &, ^, |, << or >>. In all the other cases atomic directive must not be used (this is verified by the compiler).
On the whole, from the viewpoint of performance decrease, methods of protecting data from simultaneous writing are arranged in this order: atomic, critical, omp_set_lock.
An example of inefficient code:
#pragma omp critical
{
e++;
}
|
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): not diagnosed.
Point 31. Senseless protection of local variables
Any protection of memory from simultaneous writing reduces performance of the program, being it an atomic operation, a critical section or a lock. Consequently, in those cases when you do not need it, it is better not to use this protection.
A variable should not be protected from simultaneous writing in the following cases:
if the variable is local for the thread (and also if it participates in expressions threadprivate, firstprivate, private or lastprivate);
if addressing to the variable takes place in the code which is guaranteed to execute in only one thread (in the parallel section of master directive or single directive).
An example, where protection from writing into "p" variable is senseless:
#pragma omp parallel for private(p) for (i=1; i<10; i++) { #pragma omp critical p = 0; ... } |
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): not diagnosed.
Point 32. Redundant reduction
Sometimes, reduction directive can be used in code although the variables defined in it do not change in the parallel region. It may either indicate an error or show that you forgot about some directive or variable and did not delete it during code refactoring.
An example, where "abcde" variable is not used:
#pragma omp parallel for reduction (+:sum, abcde) for (i=1; i<999; i++) { sum = sum + a[i]; } |
Diagnosis:
PVS-Studio (VivaMP): not diagnosed.
Intel C++ (Parallel Lint): diagnosed as error #12283.
Although comparison of the two analyzers cannot be called a full one yet, still this article allows the reader to learn about many patterns of parallel OpenMP errors and methods of detecting them at the very early stages of development. The possibility to detect an error at the stage of coding is a great advantage of static analysis method for these errors are very difficult to detect at the testing stage or even fail to be detected for a long time.
The tools we have considered - PVS-Studio (VivaMP) and Intel C/C++ ("Parallel Lint") will be quite helpful for parallel program developers. What tool to choose depends on the developers. Unfortunately, this article does not give an answer to this question. For the time, we can formulate the advantages of each analyzer as follows:
Intel C/C++ ("Parallel Lint") analyzer's advantages:
PVS-Studio (VivaMP) analyzer's advantages: