﻿# Мирное сосуществование PC\-Lint и VivaMP

Нам задают различные вопросы, связанные с использованием PC\-Lint, VivaMP и других статических анализаторов для проверки параллельных программ, спрашивают, являются ли они конкурентами и задают другие схожие вопросы\. Видимо это связанно с выходом новой версии PC\-Lint 9\.0, в которой заявлено о поддержке анализа параллельности \(см\. PC\-lint Manual\. Раздел: 12\. Multi\-thread support\)\. Я решил объединить обсуждения, в которых я участвовал по этому поводу и представить их в виде двух вопросов, на которые я дам развернутые ответы\.


> Поддержка VivaMP была прекращена в 2014 году\\\. По всем возникшим вопросам вы можете обратиться в нашу \[поддержку\]\(https://pvs\-studio\.ru/ru/about\-feedback/\)\\\.

Вопрос первый\.

## Можно ли использовать PC\-Lint для проверки параллельных программ?

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

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

1\) Статические анализаторы, как и компиляторы, работают с каждым \.cpp файлом отдельно\. И поэтому, если в файле A параллельно вызывается функция f\(\) из файла B, то при анализе файла B мы не знаем об этом\. Конечно, можно создать статические анализаторы \(возможно, такие и есть\), которые анализируют все файлы сразу, но это крайне сложная задача\. По крайней мере, PC\-Lint работает с каждым файлом отдельно\.

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

Возьмем пример функции \(приведенный в руководстве к PC\-Lint версии 9\.0\):

```cpp
void f()
{
  static int n = 0;
  /* ... */
}
```

Проблема состоит в том, что если функция f\(\) будет вызвана из параллельных потоков, то может возникнуть ошибка инициализации переменной 'n'\. Для диагностики данной ситуации, необходимо явно указать анализатору PC\-Lint, что функция f\(\) может вызываться параллельно\. Для этого необходимо использовать конструкции вида:

```cpp
//lint -sem(f, thread)
```

Только тогда, при диагностике кода, вы получите предупреждение: Warning 457: "Thread 'f\(void\)' has an unprotected write access to variable 'n' which is used by thread 'f\(void\)" \.

На самом деле, высказанное мною ранее утверждение, что, не предпринимая специальных действий, нельзя автоматически диагностировать ошибки в параллельном коде не совсем верно\. PC\-Lint поддерживает POSIX threads и может, например, автоматически обнаружить ошибки связанные с блокировками, если будут использоваться такие функции, как pthread\_mutex\_lock\(\) и pthread\_mutex\_unlock\(\)\. Но если механизм параллельности построен не на POSIX threads, то вам опять\-таки будет необходимо использовать специальные директивы, чтобы подсказать PC\-Lint какие функции приводят к блокировке и разблокировке:

\-sem\(_function\-name_, thread\_lock\)

\-sem\(_function\-name_, thread\_unlock\)

В этом случае можно обнаружить ошибки в коде, подобному следующему:

```cpp
//lint -sem( lock, thread_lock )
//lint -sem( unlock, thread_unlock )
extern int g();
void lock(void), unlock(void);
void f()
{
  //-------------
  lock();
  if( g() )
    return; // Warning 454
  unlock();
  //-------------
  if( g() )
  {
    lock();
    unlock();
    unlock(); // Warning 455
    return;
  }
  //-------------
  if( g() )
    lock();
  { // Warning 456
    // do something interesting
  }
}
```

Еще раз попробую дать заключительный ответ\. Анализатор PC\-Lint может эффективно находить ошибки связанные с параллельностью, если предварительно сделать ему все необходимые "подсказки"\. В противном случае он может протестировать параллельный код, считая его последовательным и тем самым не обнаружив в нем ряд ошибок\.

Из всего описанного вытекает второй вопрос\.

## Если анализатор PC\-Lint позволяет искать ошибки связанные с параллельностью, то имеет ли смысл использовать VivaMP?

Ответа два:

1\) PC\-Lint не умеет находить ошибки в программах, построенных на технологии OpenMP\.

2\) VivaMP специализированный продукт для диагностики параллельных программ, в то время как PC\-Lint хороший статический анализатор общего назначения\. Еще раз подчеркну: хороший, но общего назначения\. Анализ параллельности в нем это всего лишь одна из подсистем\. Инструмент VivaMP специально создан для диагностики параллельных программ и обладает большими возможностями в этой области\.

Можно заявить, что VivaMP и PC\-Lint не являются конкурентами\. Они дополняют друг друга\. То, что умеет диагностировать VivaMP не умеет делать PC\-Lint\. То, что умеет делать PC\-Lint, не умеет VivaMP\. Но постепенно учится :\)\. Поясню это на примерах\.

Вновь возьмем пример со статической переменной:

```cpp
void f()
{
  #pragma omp parallel num_threads(2)
  {
    static int cachedResult = ComputeSomethingSlowly();
    ...
  }
  ...
}
```

Статическая переменная объявлена внутри параллельной OpenMP секции, что приведет к ошибке инициализации\. В данном примере PC\-Lint не способен обнаружить ошибку\. Мы не можем указать, что функция f\(\) выполняется параллельно, поскольку в данном примере только часть кода функции будет распараллелена\. VivaMP же обнаружит ошибку и выдаст сообщение: V1204\. Data race risk\. Unprotected static variable declaration in a parallel code\.

Аналогичная ситуация будет в случае ошибки с блокировкой/разблокировкой\. Рассмотрим следующий код:

```cpp
void foo()
{
  ...
  #pragma omp parallel sections
  {
    #pragma omp section // V1102.
    {
      omp_set_lock(&myLock);
    }
    #pragma omp section // V1102.
    {
      omp_unset_lock(&myLock);
    }
  }
  ...
}
```

В данном случае есть две OpenMP директивы, которые в двух секциях несимметрично используют функции omp\_set\_lock и omp\_unset\_lock\. То есть приведенный код содержит две ошибки, о которых и сообщит анализатор VivaMP \(V1102\. Non\-symmetrical use of set/unset functions for the following lock variable\(s\): myLock\)\.

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

```cpp
//lint -sem(omp_set_lock, thread_lock)
//lint -sem(omp_unset_lock, thread_unlock)
void foo()
{
  ...
  {
    omp_set_lock(&myLock);
  }
  {
    omp_unset_lock(&myLock);
  }
  ...
}
```

Даже если мы укажем для PC\-Lint, что omp\_set\_lock/omp\_unset\_lock используется для блокировки/разблокировки, то для него функция foo\(\) в отличие от VivaMP будет корректна\. Есть одна блокировка и одна разблокировка\. Ошибки нет\.

Еще раз повторю, что причина в том, что PC\-Lint не анализирует директивы OpenMP заданные с помощью конструкций \#pragma, из\-за чего логика алгоритма для него представляется по\-иному\.

## Вывод

VivaMP и PC\-Lint два хороших инструмента, ориентированных на различные аспекты параллельного программирования, и отлично дополняют друг друга\.