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

Андрей Карпов
Статей: 366



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

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

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

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

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

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

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

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

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

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

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

//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)

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

//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. Но постепенно учится :). Поясню это на примерах.

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

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.

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

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 код будет выглядеть так:

//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 два хороших инструмента, ориентированных на различные аспекты параллельного программирования, и отлично дополняют друг друга.



Найдите ошибки в своем C, C++, C# и Java коде

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

goto PVS-Studio;

Андрей Карпов
Статей: 366


Найденные ошибки

Проверено проектов
336
Собрано ошибок
12 743

А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++, C#
и Java

goto PVS-Studio;