|
|
|||
![]() PVS-Studio, статический анализатор кода для 64-битного и параллельного программирования на Си/Си++
|
|||
![]() ![]() ![]() ![]() ![]()
02.09.2010
Щупаем новый Intel Parallel Studio XE 2011 beta Вот, наконец, добрался попробовать Си++ компилятор, входящий в состав Intel Parallel Studio XE 2011 beta.»
30.08.2010
Пять дней на исправление ошибки в два символа, или миф о всемогущих технологиях при разработке программ В этом блоге нередко можно почитать о том, как тот или иной программный инструмент, или технология разработки программ помогает делать меньше ошибок, быстрее их находить, легче исправлять.»
30.08.2010
Д'Артаньян и интернет, или работа над проблемой битых ссылок Господа, хватит уже рассматривать ссылки исключительно в контексте их количества, купли/продажи и считать PR сайта, где они расположены.» ![]()
22.07.2010
Использование PVS-Studio вместе с системами continuous integration В статье показаны приемы организации работы анализатора кода PVS-Studio вместе с системами непрерывной интеграции (continuous integration).»
06.07.2010
Сравнение возможностей PVS-Studio и Visual Studio 2010 по выявлению дефектов в 64-битных программах В статье сравниваются три механизма анализа кода с точки зрения выявления 64-битных ошибок: компилятор Visual C++2010, компонент Code Analysis for C/C++ входящий в состав Visual Studio 2010 Premium/Ultimate и анализатор Viva64 входящий в состав PVS-Studio 3.60. »
29.06.2010
Коллекция примеров 64-битных ошибок в реальных программах
Статья представляет собой наиболее полную коллекцию примеров 64-битных ошибок на языках Си и Си++. » ![]() |
Параллельное программирование![]() OpenMP и статический анализ кодаАвторы: Андрей Карпов, Евгений Рыжков Дата: 10.11.2008
АннотацияВ статье рассмотрены принципы, положенные в основу реализации статического анализатора кода VivaMP. Приведенный в статье набор логических условий проверки позволяет диагностировать ряд ошибок в параллельных программах, созданных на основе технологии OpenMP. ВведениеМногие ошибки в программах, разработанных на основе технологии OpenMP, можно диагностировать с помощью статического анализа кода [1]. В данной статье приведен набор диагностических правил, выявляющих потенциально опасные места в коде, которые с высокой вероятностью содержат ошибки. Описанные ниже правила ориентированы для проверки Си и Си++ кода. Но многие из правил после небольшой модификации могут использоваться применительно и к программам на языке Fortran. Описанные в статье правила лежат в основе статического анализатора кода VivaMP, разработанного в компании ООО "Системы программной верификации" [2]. Анализатор VivaMP предназначен для проверки кода приложений на языке Си/Си++. Анализатор интегрируется в среды разработки Visual Studio 2005/2008, а также добавляет раздел документации в справочную систему MSDN. Анализатор VivaMP включен в состав продукта PVS-Studio. Данная статья пополняется и модифицируется вместе с развитием продукта PVS-Studio, На данный момент это третий вариант статьи, который соответствует PVS-Studio 3.40. Если в статье указано, что какие-то диагностические возможности не реализованы, то это относится к PVS-Studio 3.40. В дальнейших версиях анализатора данные возможности могут быть реализованы. Обратитесь к более новому варианту этой статьи или к документации по PVS-Studio. Диагностические правилаЕсли не оговорено особо, то считается, что во всех правилах директивы используются совместно с директивой "omp". То есть описание, что используется директива "omp" опускается, чтобы сократить текст правил. Обобщенное исключение AДанное исключение применяется в нескольких правилах и для сокращения их текста вынесено отдельно. Общий смысл состоит в том, что мы находимся вне параллельной секции или явно указываем какой поток используется или экранируем код блокировками. Безопасными следует считать случаи, когда выполняются одно из следующих условий для проверяемого кода:
Правило N1Опасным следует считать использование директив "for" и "sections" без директивы "parallel". Исключения: Директивы "for" или "sections" находятся внутри параллельной секции, заданной директивой "parallel". Пример опасного кода: #pragma omp for for(int i = 0; i < 100; i++) ... Пример безопасного кода: #pragma omp parallel
{
#pragma omp for
for(int i = 0; i < 100; i++)
...
}
Диагностические сообщения, выдача которых основана на данном правиле: V1001. Missing 'parallel' keyword. Правило N2Опасным следует считать использование одной из директив, относящейся к OpenMP без директивы "omp". Исключения: Использование директивы "warning". Пример опасного кода: #pragma single Пример безопасного кода: #pragma warning(disable : 4793) Диагностические сообщения, выдача которых основана на данном правиле: V1002. Missing 'omp' keyword. Правило N3Опасным следует считать использование оператора for сразу после директивы "parallel" без директивы "for". Пример опасного кода: #pragma omp parallel num_threads(2) for(int i = 0; i < 2; i++) ... Пример безопасного кода: #pragma omp parallel num_threads(2)
{
for(int i = 0; i < 2; i++)
...
}
Диагностические сообщения, выдача которых основана на данном правиле: V1003. Missing 'for' keyword. Each thread will execute the entire loop. Правило N4Опасным следует считать создание параллельного цикла с использованием директив "parallel" и "for" внутри параллельной секции созданной директивой "parallel". Пример опасного кода: #pragma omp parallel
{
#pragma omp parallel for
for(int i = 0; i < 100; i++)
...
}
Пример безопасного кода: #pragma omp parallel
{
#pragma omp for
for(int i = 0; i < 100; i++)
...
}
Диагностические сообщения, выдача которых основана на данном правиле: V1004. Nested parallelization of a 'for' loop. Правило N5Опасным следует считать совместное использование директив "for" и "ordered", если затем внутри цикла заданного оператором for не используется директива "ordered". Пример опасного кода: #pragma omp parallel for ordered
for(int i = 0; i < 4; i++)
{
foo(i);
}
Пример безопасного кода: #pragma omp parallel for ordered
for(int i = 0; i < 4; i++)
{
#pragma omp ordered
{
foo(i);
}
}
Диагностические сообщения, выдача которых основана на данном правиле: V1005. The 'ordered' directive is not present in an ordered loop. Правило N6Опасным следует считать вызов функции omp_set_num_threads внутри параллельной секции, заданной директивой "parallel". Пример опасного кода: #pragma omp parallel
{
omp_set_num_threads(2);
}
Пример безопасного кода: omp_set_num_threads(2);
#pragma omp parallel
{
...
}
Диагностические сообщения, выдача которых основана на данном правиле: V1101. Redefining number of threads in a parallel code. Правило N7Опасным следует считать нечетное использование функций omp_set_lock, omp_set_nest_lock, omp_unset_lock и omp_unset_nest_lock внутри параллельной секции Пример опасного кода: #pragma omp parallel sections
{
#pragma omp section
{
omp_set_lock(&myLock);
}
}
Пример безопасного кода: #pragma omp parallel sections
{
#pragma omp section
{
omp_set_lock(&myLock);
omp_unset_lock(&myLock);
}
}
Диагностические сообщения, выдача которых основана на данном правиле: V1102. Non-symmetrical use of set/unset functions for the following lock variable(s): %1%. Правило N8Опасным следует считать использование функции omp_get_num_threads в арифметических операциях. Исключения: Возвращаемое функцией omp_get_num_threads значение используется для сравнения или приравнивается переменной. Пример опасного кода: int lettersPerThread = 26 / omp_get_num_threads(); Пример безопасного кода: bool b = omp_get_num_threads() == 2;
switch(omp_get_num_threads())
{
...
}
Диагностические сообщения, выдача которых основана на данном правиле: V1103. Threads number dependent code. The 'omp_get_num_threads' function is used in an arithmetic expresion. Правило N9Опасным следует считать вызов функции omp_set_nested внутри параллельной секции, заданной директивой "parallel". Исключения: Функция находится во вложенном блоке, созданной директивой "master" или "single". Пример опасного кода: #pragma omp parallel
{
omp_set_nested(2);
}
Пример безопасного кода: #pragma omp parallel
{
#pragma omp master
{
omp_set_nested(2);
}
}
Диагностические сообщения, выдача которых основана на данном правиле: V1104. Redefining nested parallelism in a parallel code. Правило N10Опасным следует считать использование функций, использующих общие ресурсы. Примеры функций: printf. Исключения: Обобщенное исключение A. Пример опасного кода: #pragma omp parallel
{
printf("abcd");
}
Пример безопасного кода: #pragma omp parallel
{
#pragma omp critical
{
printf("abcd");
}
}
Диагностические сообщения, выдача которых основана на данном правиле: V1201. Concurrent usage of a shared resource via an unprotected call of the '%1%' function. Правило N11Опасным следует считать применение директивы flush к указателям Пример опасного кода: int *t; ... #pragma omp flush(t) Пример безопасного кода: int t; ... #pragma omp flush(t) Диагностические сообщения, выдача которых основана на данном правиле: V1202. The 'flush' directive should not be used for the '%1%' variable, because the variable has pointer type. Правило N12Опасным следует считать использование директивы "threadprivate". Пример опасного кода: #pragma omp threadprivate(var) Диагностические сообщения, выдача которых основана на данном правиле: 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. Правило N13Опасным следует считать инициализацию или модификацию объекта (переменной) в параллельной секции, если объект относительно этой секции является глобальным (общим для потоков). Пояснение правила: К глобальным объектам относительно параллельной секции относятся:
Последний пункт нуждается в пояснении. Приведем пример: class MyClass { public: int m_a; void IncFoo() { a++; } void Foo() { #pragma omp parallel for for (int i = 0; i < 10; i++) IncFoo(); // Variant. A } }; MyClass object_1; #pragma omp parallel for for (int i = 0; i < 10; i++) { object_1.IncFoo(); // Variant. B MyClass object_2; object_2.IncFoo(); // Variant. C } В случае варианта A мы будем считать, что члены класса общие, то есть глобальны по отношению к функции IncFoo. В результате мы обнаружим ошибку состояния гонки внутри функции IncFoo. В случае варианта B мы будем считать, что члены класса локальны и ошибки в IncFoo нет. Но будет выдано предупреждение, что параллельно вызывается не константный метод IncFoo из класса MyClass. Это поможет найти ошибку. В случае варианта C мы будем считать, что члены класса локальны и ошибки в IncFoo нет. И ошибок действительно нет. Объект может быть как простого типа тип, так и экземпляром класса. К операциям изменения объекта относится:
Исключения:
Пример опасного кода: #pragma omp parallel
{
static int st = 1; // V1204
}
void foo(int &) {}
...
int value;
MyObjectType obj;
#pragma omp parallel for
for(int i = 0; i < 33; i++)
{
++value; // V1205
foo(value); // V1206
obj.non_const_foo(); // V1207
}
Пример безопасного кода: #pragma omp parallel
{
#pragma omp critical
{
static int st = 1;
}
}
void foo(const int &) {}
...
int value;
MyObjectType obj;
#pragma omp parallel for
for(int i = 0; i < 33; i++)
{
#pragma omp atomic
++value;
foo(value);
obj.const_foo();
}
Диагностические сообщения, выдача которых основана на данном правиле: V1204. Data race risk. Unprotected static variable declaration in a parallel code. V1205. Data race risk. Unprotected concurrent operation with the '%1%' variable. V1206. Data race risk. The value of the '%1%' variable can be changed concurrently via the '%2%' function. V1207. Data race risk. The '%1%' object can be changed concurrently by a non-const function. Правило N14Опасным следует считать применение директив "private", "firstprivate" и "threadprivate" к ссылкам и указателям (не массивам). Пример опасного кода: int *arr; #pragma omp parallel for private(arr) Пример безопасного кода: int arr[4]; #pragma omp parallel for private(arr) Диагностические сообщения, выдача которых основана на данном правиле: V1208. The '%1%' variable of reference type cannot be private. V1209. Warning: The '%1%' variable of pointer type should not be private. Правило N15Опасным следует считать отсутствие модификации переменной помеченной директивой "lastprivate" в последней секции ("section "). Исключения: Переменная также не модифицируется и во всех остальных секциях. Пример опасного кода: #pragma omp sections lastprivate(a)
{
#pragma omp section
{
a = 10;
}
#pragma omp section
{
}
}
Пример безопасного кода: #pragma omp sections lastprivate(a)
{
#pragma omp section
{
a = 10;
}
#pragma omp section
{
a = 20;
}
}
Диагностические сообщения, выдача которых основана на данном правиле: V1210. The '%1%' variable is marked as lastprivate but is not changed in the last section. Правило N16Опасным следует считать использование переменной типа omp_lock_t / omp_nest_lock_t без ее предварительной инициализации в функции omp_init_lock / omp_init_nest_lock. Под использованием понимается вызов функции omp_set_lock и так далее. Пример опасного кода: omp_lock_t myLock;
#pragma omp parallel num_threads(2)
{
...
omp_set_lock(&myLock);
}
Пример безопасного кода: omp_lock_t myLock;
omp_init_lock(&myLock);
#pragma omp parallel num_threads(2)
{
...
omp_set_lock(&myLock);
}
Диагностические сообщения, выдача которых основана на данном правиле: В текущей версии VivaMP данное правило не реализовано. Правило N17Опасным следует считать использование переменных, объявленных в параллельной секции локальными с использованием директив "private" и "lastprivate" без их предварительной инициализации. Пример опасного кода: int a = 0;
#pragma omp parallel private(a)
{
a++;
}
Пример безопасного кода: int a = 0;
#pragma omp parallel private(a)
{
a = 0;
a++;
}
Диагностические сообщения, выдача которых основана на данном правиле: В текущей версии VivaMP данное правило не реализовано. Правило N18Опасным следует считать использование после параллельной секции переменных, к которым применялась директива "private", "threadprivate" или "firstprivate" без предварительной инициализации. Пример опасного кода: #pragma omp parallel private(a)
{
...
}
a++;
Пример безопасного кода: #pragma omp parallel private(a)
{
...
}
a = 10;
Диагностические сообщения, выдача которых основана на данном правиле: В текущей версии VivaMP данное правило не реализовано. Правило N19Опасным следует считать применение директив "firstprivate" и "lastprivate" к экземплярам классов, в которых отсутствует конструктор копирования. Диагностические сообщения, выдача которых основана на данном правиле: В текущей версии VivaMP данное правило не реализовано. Правило N20Неэффективным следует считать использование директивы "flush", там где оно выполняется неявно. Случаи, в которых директива "flush" присутствует неявно и в ее использовании нет смысла:
Диагностические сообщения, выдача которых основана на данном правиле: В текущей версии VivaMP данное правило не реализовано. Правило N21Неэффективным следует считать использование директивы flush для локальных переменных (объявленных в параллельной секции), а также переменных помеченных как threadprivate, private, lastprivate, firstprivate. Пример: int a = 1;
#pragma omp parallel for private(a)
for (int i = 10; i < 100; ++i) {
#pragma omp flush(a);
...
} Диагностические сообщения, выдача которых основана на данном правиле: V1211. The use of 'flush' directive has no sense for private 'NN' variable, and can reduce performance. Правило N22Неэффективным следует считать использование критических секций или функций класса omp_set_lock, там где достаточно директивы "atomic". Диагностические сообщения, выдача которых основана на данном правиле: В текущей версии VivaMP данное правило не реализовано. Правило N23Неэффективным следует считать использование директивы flush для локальных переменных (объявленных в параллельной секции), а также переменных помеченных как threadprivate, private, lastprivate, firstprivate. Директива flush не имеет для перечисленных переменных смысла, так как эти переменные всегда содержат актуальные значения. И дополнительно снижает производительность кода. Пример опасного кода: int a = 1;
#pragma omp parallel for private(a)
for (int i = 10; i < 100; ++i) {
#pragma omp flush(a);
...
}
Пример безопасного кода: int a = 1;
#pragma omp parallel for
for (int i = 10; i < 100; ++i) {
#pragma omp flush(a);
...
}
Диагностические сообщения, выдача которых основана на данном правиле: В текущей версии VivaMP данное правило не реализовано. Правило N24Согласно спецификации OpenMP все исключения должны быть обработаны внутри параллельной секции. Считается, что код генерирует исключения, если в нем: используется оператор throw; используется оператор new; вызывается функция, отмеченная как throw(...); Такой код должен быть обернут в блок try..catch внутри параллельнйо секции. Исключения: Используется оператор new, не бросающий исключения (new(std::nothrow) float[10000];). Пример опасного кода: void MyNotThrowFoo() throw() { }
...
#pragma omp parallel for num_threads(4)
for(int i = 0; i < 4; i++)
{
...
throw 1;
...
float *ptr = new float[10000];
...
MyThrowFoo();
}
Пример безопасного кода: 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;
}
}
if (errCount != 0)
throw 1;
Примечание. Конструкция nothrow new несколько обманчивая, так как возникает ощущение, что исключений здесь быть не может. Но следует учесть, что исключения могут быть сгенерированы в конструкторе создаваемых объектов. То есть, если выделяется хотя бы один std::string или сам класс выделяет память по new (без nothrow), то исключения при вызове new(nothrow) всё равно могут быть сгенерированы. Диагностика данных ошибок заключается в анализе тел конструкторов, (и тел конструкторов других объектов, содержащихся в классе) которые вызываются внутри параллельных секций. На данный момент данная функциональность в VivaMP не реализована. Диагностические сообщения, выдача которых основана на данном правиле: 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 '%1%' function which throws an exception cannot be used in a parallel section outside of a try..catch block. Правило N25Опасным следует считать отсутствие включения заголовочного файла <omp.h> в файле, где используются директивы OpenMP. Диагностические сообщения, выдача которых основана на данном правиле: V1006. Missing omp.h header file. Use '#include <omp.h>'. Правило N26Опасным следует считать наличие неиспользуемых переменных, отмеченных в директиве reduction. Это может свидетельствовать как об ошибке, так и просто о том, что какая-то директива или переменная была забыта и не удалена в процессе рефакторинга кода. Пример, где не используется переменная abcde: #pragma omp parallel for reduction (+:sum, abcde)
for (i=1; i<999; i++)
{
sum = sum + a[i];
}
Диагностика: В текущей версии VivaMP данное правило не реализовано. Правило N27Опасным следует считать незащищенный доступ в параллельной секции (образованной директивой for) к элементу массива с использованием индекса, отличного от используемого для чтения. Примечание. Ошибка может возникнуть и в случае защищенного доступа (single, critical, ...), но сейчас для простоты считаем, что в этом случае доступ к элементам массива безопасен. Примечание. Исключения: 1. Индекс является константой. Пример опасного кода: #pragma omp parallel for
for (int i=2; i < 10; i++)
array[i] = i * array[i-1]; //V1212
Пример безопасного кода: #pragma omp parallel for
for (int i=2; i < 10; i++)
{
array[i] = array [i] / 2;
array_2[i] = i * array[i-1];
}
Диагностические сообщения, выдача которых основана на данном правиле: V1212. Data race risk. When accessing the array '%1%' in a parallel loop, different indexes are used for writing and reading. ЗаключениеЕсли вы интересуетесь методологией проверки программного кода на основе статического анализа - напишите нам (support@viva64.com). Мы надеемся, что найдем общие интересы и возможности для сотрудничества! Библиографический список
| ||
|
© 2008 - 2010, ООО "СиПроВер"
300027, Россия, Тула, а/я 1800, тел. +7 (4872) 38-59-95,. офис: Россия, Тула, Кутузова 100, оф. 73. |
|||