Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


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

  • Промоакции
  • Оповещения
  • Спам

Вебинар: Использование статических анализаторов кода при разработке безопасного ПО - 19.12

>
>
>
V3147. Non-atomic modification of volat…
menu mobile close menu
Проверка проектов
Сообщения PVS-Studio
Диагностики общего назначения (General Analysis, C++)
Диагностики общего назначения (General Analysis, C#)
Диагностики общего назначения (General Analysis, Java)
Микрооптимизации (C++)
Диагностика 64-битных ошибок (Viva64, C++)
Реализовано по запросам пользователей (C++)
Cтандарт MISRA
Стандарт AUTOSAR
Стандарт OWASP (C++)
Стандарт OWASP (C#)
Проблемы при работе анализатора кода
Дополнительная информация
toggle menu Оглавление

V3147. Non-atomic modification of volatile variable.

11 Ноя 2019

Анализатор обнаружил неатомарное изменение 'volatile' переменной, которое может привести к состоянию гонки.

Известно, что использование модификатора 'volatile' гарантирует, что все потоки будут видеть актуальное значение соответствующей переменной. Модификатор 'volatile' используется для того, чтобы указать CLR, что все операции присвоения этой переменной и все операции чтения из неё должны быть атомарными.

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

Помимо простых операций присвоения, существуют также операции, изменяющие значение переменной перед записью. К таким операциям можно отнести:

  • var++, ‑‑var, ...
  • var += smt, var *= smt, ...
  • ...

Такая запись выглядит как одна операция, но в действительности это целая последовательность операций чтения-изменения-записи.

Рассмотрим использование 'volatile' переменной в качестве счетчика (counter++).

class Counter
{
  private volatile int counter = 0;
  ....
  public void increment()
  {
    counter++; // counter = counter + 1
  }
  ....
}

В IL коде операция инкремента раскрывается в следующие команды:

IL_0001:  ldarg.0
IL_0002:  ldarg.0
IL_0003:  volatile.
IL_0005:  ldfld      int32
modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)
VolatileTest.Test::val
IL_000a:  ldc.i4.1
IL_000b:  add
IL_000c:  volatile.
IL_000e:  stfld      int32
modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)
VolatileTest.Test::val

Здесь и кроется состояние гонки. Предположим, что 2 потока одновременно работают с одним и тем же экземпляром объекта типа Counter и выполняют инкремент переменной 'counter', изначально проинициализированной значением 10. При этом, оба потока будут работать одновременно, выполняя промежуточные действия над переменной counter, каждый на своём собственном стеке (назовём эти промежуточные значения temp1 и temp2):

[counter == 10, temp1 == 10] Поток N1 считывает значение 'counter' на свой стек. (операция ldfld в IL)

[counter == 10, temp1 == 11] Поток N1 изменяет значение temp1 на своём стеке. (операция add в IL)

[counter == 10, temp2 == 10] Поток N2 считывает значение 'counter' на свой стек. (операция ldfld в IL)

[counter == 11, temp1 == 11] Поток N1 записывает temp1 в 'counter'. (операция stfld в IL)

[counter == 11, temp2 == 11] Поток N2 изменяет значение temp2 на своём стеке. (операция add в IL)

[counter == 11, temp2 == 11] Поток N2 записывает temp2 в 'counter'. (операция stfld в IL)

Ожидалось значение переменной 'counter' равное 12 (а не 11), так как 2 потока выполнили инкремент над одной и той же переменной. Также возможна ситуация, когда потоки выполнят инкремент друг за другом, и в таком случае все будет так, как и ожидалось.

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

  • Блок 'lock'
  • Методы атомарных операций класса Interlocked из библиотеки System.Threading
  • Функциональность блокировок класса Monitor из библиотеки System.Threading

Пример корректного кода:

class Counter
{
  private volatile int counter = 0;
  ....
  public void increment()
  {
    Interlocked.Increment(ref counter);  
  }
  ....
}

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки при работе с многопоточными примитивами (интерфейсами запуска потоков на выполнение, синхронизации и обмена данными между потоками и пр.).

Данная диагностика классифицируется как: