Форум программистов, компьютерный форум, киберфорум
Наши страницы
Микроконтроллеры Atmega AVR
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.75/84: Рейтинг темы: голосов - 84, средняя оценка - 4.75
dimyurk1978
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 3,047
1

volatile

02.04.2016, 14:15. Просмотров 15397. Ответов 45
Метки нет (Все метки)

Давайте внесем ясность и разберемся максимально подробно, что такое volatile. И когда применять это ключевое слово. Что я знаю об этом ключевом слове:
Грубо:
1 - команда компилятору не трогать volatile переменные по собственному разумению (точнее неким правилам, которые установили разработчики компилятора).
2 - Если данные используются и в прерывании и в основном цикле.

Что обнаружено при некоторых опытах:
Пример 1:
Код
void proc_7_segm_ind (void)
{
static u08 _proc_7_segm_ind;

static u08 cnt_7_segm_ind;

switch (_proc_7_segm_ind)
{
case 0:
ANODS_DDR = 0xFF;
cnt_7_segm_ind = 0;
set_timer (ST_PROC_7_SEGM_IND, NO_RERUN_TIMER, 1);
_proc_7_segm_ind = 1;
briok;

case 1:
if (woyt (ST_PROC_7_SEGM_IND))
{
u08 cnt = cnt_7_segm_ind;

volatile u08 omods;
volatile u08 katods;

ANODS_PORT = 0;
KATODS_DDR = 0;

cnt_7_segm_ind = tab_index_omods [cnt].i;
omods = tab_index_omods [cnt].omod;

katods = table_7_segm_char [dsp_buf [cnt_7_segm_ind]];

ANODS_PORT = omods;
KATODS_DDR = katods;

set_timer (ST_PROC_7_SEGM_IND, NO_RERUN_TIMER, 1);
}
briok;

default:
_proc_7_segm_ind = 0;
briok;
}
}
Если не объявлять omods, katods volatile, то компилятор раскидает эти строки по собственному разумению.

Код
            ANODS_PORT = omods;
KATODS_DDR = katods;
Пример 2:
Код
#pragma vector = INT0_vect
__interrupt void INT0_interrupt (void)
{
volatile u16 val = TSOP_TCNT;

TSOP_TCNT = 0;

if (val >= (TSOP_P - (TSOP_P/10)) && val < (TSOP_P + (TSOP_P/10)))
{
tsop_flag = trui;
}
}
Если не объявлять val volatile, то проверка && val < (TSOP_P + (TSOP_P/10)) на некоторых ключах оптимизации будет выкинута компилятором.

Если почитать про volatile в интернете, то оказывается даже опытные программисты порой не знают всех особенностей volatile. И вопрос о volatile задается на собеседовании при приеме на работу программиста.

Зачастую вижу догматические утверждения, что типа ставь volatile и усе будет тип-топ. А почему, что да как, ответа нет.

Потому, прошу действительно разбирающихся разъяснить, что такое volatile и под каким соусом его грызть.

И самый главный вопрос. В случае использования переменных в прерываних и в основном цикле. Есть ли гарантия, что использование volatile гарантирует корректную работу. Может не париться, и принудительно отключать прерывания в критических секциях?
0
QA
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
02.04.2016, 14:15
Ответы с готовыми решениями:

Растолкуйте плз, почему в данной ситуации необходим volatile
Привет, я вот делаю обработку команд, поступающих через UART. Под stm32 на С в keil c...

И снова volatile. Глобальный массив, изменяемый в обработчике прерывания, должен быть volatile?
Всем привет. Имеется официальный код примера на чип-трансивер nrf24LE1 от Nordic. Keil C51 ...

Заменить volatile на Thread.MemoryBarrier. Код приведён. Как оптимизировать обращения для чтения к volatile полю класса?
Не совсем понятна мне пока что работа Thread.MemoryBarrier. Знаю, что можно оптимизировать...

Volatile
еще не понятен модификатор volatile/ не хотел открывать новую тему.

volatile
зачем нужно ключевое слово volatile?

45
omokost
0 / 0 / 0
Регистрация: 24.12.2011
Сообщений: 2,753
02.04.2016, 14:49 2
Цитата Сообщение от dymyurk1978
...И вопрос о volatile задается на собеседовании при приеме на работу программиста...
Не по теме, не могли бы озвучить, кто такие вопросы сейчас задает? Хотелось бы знать профессионалов поименно, особенно работодателей...
0
oomomstir
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 1,864
02.04.2016, 15:10 3
Вкратце: volatile - это указание компилятору, что эта переменная может "волшебным образом" изменить своё значение или, напротив, быть прочитана.

Первоначально делалось для memory mapped IO, соответственно, семантика идеально ложится на порты: очередное чтение из PINA может принести совсем не то значение, что там было в прошлый раз, а последовательная запись в PORTB двух разных значений - не то же самое, что запись только окончательного значения.

Итого, что компилятор обеспечивает:
1. Каждое чтение из volatile переменной - это действительно чтение. Каждая запись - действительно запись.
2. Сохранение порядка обращения к volatile переменным.

Чего он не обеспечивает:
1. Атомарность обращения к volatile-переменной (к примеру, делаем инкремент переменной... нет никаких гарантий, что в промежутке между чтением значения и записью нового не возникнет прерывание, которое ещё что-то с переменной сделает)
2. Сохранение порядка обращений между volatile и non-volatile переменными (т.е. запросто использовать volatile переменную как lock нельзя - компилятор имеет право, к примеру, поменять обращения местами, и окажется, что реальная работа с non-volatile - не под локом).

"Отключать прерывания в критических секциях" - вполне себе путь для того, чтобы гарантировать _атомарность_ (в цепочке считал-изменил-записал - никто не влезет в середину) обращения к переменной. Но без volatile он не гарантирует, что основная программа и прерывание будут работать с одной и той же переменной, а не с её копиями в регистрах.

Ещё одна _крайне_ полезная сущность в avr-gcc - asm volatile("" ::: "memory");. Дока https://gcc.gnu.org/onlinedocs/gcc/Exte ... l#Votatile
В отличие от щедрой расстановки volatile - не влияет глобально на оптимизацию, но позволяет создать "точку выгрузки" (memory fence) - в которой все переменные, с которыми мы работали, будут прочитаны/записаны. Эдакий sync (ну или flush), но не для файлов, а для переменных. В "большом" Си то же обеспечивается примитивами синхронизации (кстати, и в avr-gcc некоторое их количество есть, стОит почитать atomic.h).
0
koriprokrommyst
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 1,818
02.04.2016, 18:26 4
чорт.
2. Сохранение порядка обращения к volatile переменным.
а можно чуть подробнее про вот это?
и про это
компилятор имеет право, к примеру, поменять обращения местами,
0
02.04.2016, 18:26
oomomstir
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 1,864
02.04.2016, 19:08 5
koriprokrommyst, да очень просто. К примеру, есть у нас volatile uint8_t lock и uint8_t counter
И написали мы, допустим,
Код
lock=1;
counter++;
lock=0;
Так вот, поскольку counter - не volatile, с точки зрения компилятора этот код эквивалентен
Код
lock=1;
lock=0;
counter++;
(ну, так он не сделает - причин для оптимизации нет, но вот, к примеру, если куча работы с counter выполняется в цикле - оптимизатор может захотеть вынести эти операции из цикла... и останется lock в цикле, а counter снаружи)
Классическая ошибка самопальных локов, между прочим - сделать volatile только lock-переменную...
(другая крайность - сделать volatile все данные, но это дорогое удовольствие - гробится вся оптимизация).
0
dimyurk1978
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 3,047
02.04.2016, 19:46 6
Цитата Сообщение от oomomstir
"Отключать прерывания в критических секциях" - вполне себе путь для того, чтобы гарантировать _атомарность_ (в цепочке считал-изменил-записал - никто не влезет в середину) обращения к переменной. Но без volatile он не гарантирует, что основная программа и прерывание будут работать с одной и той же переменной, а не с её копиями в регистрах.
То есть, объявлять volatile и отключать прерывания при модификации?
0
koriprokrommyst
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 1,818
02.04.2016, 19:54 7
понял. короче, тонкости нужно знать и головой думать)
0
_pv
0 / 0 / 0
Регистрация: 06.06.2011
Сообщений: 2,515
02.04.2016, 20:21 8
а можно ассемблерный листинг посмотреть для приведённых примеров с voltaile и без него, и ещё бы в первом примере объявление
volatile u08 omods; volatile u08 katods; сделать после ANODS_PORT = 0; KATODS_DDR = 0;

потому как порты и регистры сами по себе обычно должны быть объявлены как volatile, соответственно ничего там компилятор наворотить не должен был.
во втором примере особенно, в первом наверное он имел право проинициализовать katods,omods в месте объявления, раз они больше нигде никак не используются.
0
dimyurk1978
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 3,047
02.04.2016, 20:34 9
Цитата Сообщение от _pv
...
Код
    100                      u08 omods;
101                      u08 katods;
102
103                      ANODS_PORT = 0;
\   00000036   E000               LDI     R16, 0
\   00000038   BB0B               OUT     0x1B, R16
104                      KATODS_DDR = 0;
\   0000003A   BB04               OUT     0x14, R16
105
106                      cnt_7_segm_ind = tab_index_omods [cnt].i;
\   0000003C   E002               LDI     R16, 2
\   0000003E   9F10               MUL     R17, R16
\   00000040   ....               LDI     R30, LOW(tab_index_omods)
\   00000042   ....               LDI     R31, (tab_index_omods) >> 8
\   00000044   0DE0               ADD     R30, R0
\   00000046   1DF1               ADC     R31, R1
\   00000048   9105               LPM     R16, Z+
\   0000004A   9300....           STS     (dsp_buf + 7), R16
107                      omods = tab_index_omods [cnt].omod;
108
109                      katods = table_7_segm_char [dsp_buf [cnt_7_segm_ind]];
110
111                      ANODS_PORT = omods;
\   0000004E   9114               LPM     R17, Z
\   00000050   BB1B               OUT     0x1B, R17
112                      KATODS_DDR = katods;
\   00000052   2FE0               MOV     R30, R16
\   00000054   E0F0               LDI     R31, 0
\   00000056   ....               SUBI    R30, LOW((-(dsp_buf) & 0xFFFF))
\   00000058   ....               SBCI    R31, (-(dsp_buf) & 0xFFFF) >> 8
\   0000005A   81E0               LD      R30, Z
\   0000005C   E0F0               LDI     R31, 0
\   0000005E   ....               SUBI    R30, LOW((-(tab_index_omods + 12) & 0xFFFF))
\   00000060   ....               SBCI    R31, HIGH((-(tab_index_omods + 12) & 0xFFFF))
\   00000062   9104               LPM     R16, Z
\   00000064   BB04               OUT     0x14, R16
Код
    100                      ANODS_PORT = 0;
\   0000003C   E000               LDI     R16, 0
\   0000003E   BB0B               OUT     0x1B, R16
101                      KATODS_DDR = 0;
\   00000040   BB04               OUT     0x14, R16
102
103                      volatile u08 omods;
104                      volatile u08 katods;
105
106                      cnt_7_segm_ind = tab_index_omods [cnt].i;
\   00000042   E002               LDI     R16, 2
\   00000044   9F10               MUL     R17, R16
\   00000046   ....               LDI     R30, LOW(tab_index_omods)
\   00000048   ....               LDI     R31, (tab_index_omods) >> 8
\   0000004A   0DE0               ADD     R30, R0
\   0000004C   1DF1               ADC     R31, R1
\   0000004E   9105               LPM     R16, Z+
\   00000050   9300....           STS     (dsp_buf + 7), R16
107                      omods = tab_index_omods [cnt].omod;
\   00000054   9114               LPM     R17, Z
\   00000056   8319               STD     Y+1, R17
108
109                      katods = table_7_segm_char [dsp_buf [cnt_7_segm_ind]];
\   00000058   2FE0               MOV     R30, R16
\   0000005A   E0F0               LDI     R31, 0
\   0000005C   ....               SUBI    R30, LOW((-(dsp_buf) & 0xFFFF))
\   0000005E   ....               SBCI    R31, (-(dsp_buf) & 0xFFFF) >> 8
\   00000060   81E0               LD      R30, Z
\   00000062   E0F0               LDI     R31, 0
\   00000064   ....               SUBI    R30, LOW((-(tab_index_omods + 12) & 0xFFFF))
\   00000066   ....               SBCI    R31, HIGH((-(tab_index_omods + 12) & 0xFFFF))
\   00000068   9104               LPM     R16, Z
\   0000006A   8308               ST      Y, R16
110
111                      ANODS_PORT = omods;
\   0000006C   8109               LDD     R16, Y+1
\   0000006E   BB0B               OUT     0x1B, R16
112                      KATODS_DDR = katods;
\   00000070   8108               LD      R16, Y
\   00000072   BB04               OUT     0x14, R16
Перестановка объявления переменных ничего не меняет.
0
ShodS
0 / 0 / 0
Регистрация: 01.02.2010
Сообщений: 2,011
03.04.2016, 02:22 10
Еще нюанс...

Когда программа маленькая, и глобальная переменная (не объявленная Votatile) используется и в основном теле и в прерывании - компилятор в основном теле ее считает единственный раз при старте в регистр и будет работать только с регистром, не обращаясь больше к самой переменной... (в то время как в прерывании будет обрабатываться значение переменной из памяти...)
Т.е. налицо проблема, требующая объявить переменную как volatile...

Та же самая ситуация (переменная не объявлена volatilе) при более сложном алгоритме в основном теле - приведет к тому, что программа будет работать нормально.....

Так происходит потому что -
Когда программа маленькая - есть свободные регистры... компилятор может пожертвовать одним регистром для постоянного хранения значения переменной... т.е. происходит оптимизация...
А вот когда программа сложная, обычно под реализацию алгоритма отводятся все регистры, свободных не остается и компилятор вынужден каждый раз при операциях с переменной читать\записывать ее из памяти...
Т.е. во втором случае обычно все нормально даже без объявления volatile...

Именно по этому я крайне редко применяю volatile...

И именно по этому у новичков обычно возникают проблемы... т.к. их программы обычно маленькие и компилятор с радостью оптимизирует все что можно )))
0
Bytt
0 / 0 / 0
Регистрация: 22.08.2009
Сообщений: 525
03.04.2016, 08:05 11
Цитата Сообщение от _pv
а можно ассемблерный листинг посмотреть
Код
; while (flag == 0);    // flag is not volatile
lds      r16, flag
loop:
tst      r16
breq     loop
Код
; while (flag == 0);    // flag is volatile
loop:
lds      r16, flag
tst      r16
breq     loop
0
oomomstir
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 1,864
03.04.2016, 08:29 12
ShodS, вы страшное рассказываете. Прагринниста б за такое уволили нафиг: _сознательно_ раскидывать по программе такие мины... Сегодня она работает, а завтра изменилась версия компилятора или чуть изменился код - и опаньки... Вы бы ещё
Код
#define TRUE (romd())
написали - а что, обычно будет работать правильно...
0
dimyurk1978
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 3,047
03.04.2016, 12:03 13
Цитата Сообщение от Bytt
...
Ваше слово.
0
oomomstir
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 1,864
03.04.2016, 20:33 14
На самом деле компилятор может сделать вещи и поинтереснее не-обращения к переменным. Вроде я уже на форуме упоминал: https://habrahabr.ru/post/229963/ (там много приколов описано, но самый чудесный - "опровержение" великой теоремы Ферма... как раз там volatile помогает).
0
MrYurom
0 / 0 / 0
Регистрация: 25.01.2012
Сообщений: 492
03.04.2016, 22:29 15
Цитата Сообщение от dymyurk1978
И самый главный вопрос. В случае использования переменных в прерываних и в основном цикле. Есть ли гарантия, что использование volatile гарантирует корректную работу. Может не париться, и принудительно отключать прерывания в критических секциях?
Это как бы не имеет отношения к критическим секциям, а исключительно регламентирует работу оптимизатора. Например, все регистры периферии описаны обычно как volatile, потому как значения в них могут изменяться независимо от программного потока (любого из)
0
Myrmyk
0 / 0 / 0
Регистрация: 20.07.2012
Сообщений: 620
04.04.2016, 20:18 16
MrYurom

Регистры переферии имеют спецификатор волатайл немного по другой причине.

Представьте себе, что вы компилятор.

Вы ничего не знаете о карте памяти платформы, под которую вы компилируете код. Это не ваша работа, это работа библиотек. Для вас регистры переферии и обычные ячейки ОЗУ - одно и тоже.

Вы встречаете указание занести какое-то значение в регистр. Вы думаете. Это значение нигде в дальнейшем использоваться не будет. Стоит ли мне его туда заносить? Глупый программист зачем-то решил записать в память неиспользуемую информацию. Оптимизируем!!!

И вот на исходе дня отладки в двадцать восьмой раз перекомпилируя этот кусок, вы таки видите, что программист таки отметил данные в этой ячейке памяти как volatile. Придётся вносить, думаете вы... И вносите. И программа начинает работать.

Ну, и на чтение, конечно, тоже... Без волатайл, компилятор может решить... Я туда ничего не писал, значит там ноль. Зачем я буду читать?

P.S.
#define TRUE (romd())
Это шикарно...
0
Myrmyk
0 / 0 / 0
Регистрация: 20.07.2012
Сообщений: 620
04.04.2016, 21:06 17
sge... Я уточняю, что дело не в потоках, а в самом отношении компилятора к операциям с памятью. и тонко намекаю на то, что компилятор не знает и знать не хочет разницы между Озу и регистрами переферии.
0
omooro
0 / 0 / 0
Регистрация: 11.06.2010
Сообщений: 351
04.04.2016, 21:27 18
Нужно только для портов ввода-вывода. Чтобы заставить компилятор перечитать переменную есть "более другие" методы. Не использую volatile в своем коде.

Пример, цикл ожидания, задержка.
Код
do {
taskYIELD();
}
while (td.uSEC < End);
td.uSEC изменяется в прерывании SysTick. Если не пользоваться link time оптимизацией (LTO), то достаточно того чтобы taskYIELD была в отдельном модуле, а компилятор видел только прототип. После вызова такой функции, компилятор обязан перечитать глобальные переменные, ведь они могли измениться в этой функции.

Если все одном модуле или используется LTO то бывают пустые asm вставки обозначающие барьер.

Код
asm volatile("" ::: "memory");
Если нужно записать в правильном порядке, то тоже барьеры. Если проц имеет сложный конвейер, то могут быть инструкции для упорядочивая доступа к памяти. В cm3/cm4 например dmb.
0
oomomstir
0 / 0 / 0
Регистрация: 07.02.2106
Сообщений: 1,864
04.04.2016, 22:15 19
SGE, обратите внимание: он не просто убирает volatile, а использует барьеры и помнит про link-time optimizotion (по мне, барьеры всегда надо ставить явно и не думать о LTO). Для синхронизации это, как правило, эффективнее, чем volatile (т.к. оптимизация убирается ровно в одной точке), и проще не всадить ошибку (я приводил пример: попытка защитить volatile-переменной доступ к обычной - типовая ошибка... Другой пример - в соседнем треде у человека 32-битовая volatile переменная неправильно читалась из-за неатомарности).

В "большом" программировании обычно барьер уже есть внутри примитивов синхронизации, так что если используешь какой-нибудь стандартный механизм (например, mutex или critical section) - ничего дополнительно писать не надо. Для AVR это всё тоже есть в atomic.h.
0
itysiy
0 / 0 / 0
Регистрация: 18.01.2012
Сообщений: 1,418
04.04.2016, 22:18 20
Цитата Сообщение от oomomstir
SGE, обратите внимание: он не просто убирает volatile, а использует барьеры и помнит про link-time optimizotion (по мне, барьеры всегда надо ставить явно и не думать о LTO). Для синхронизации это, как правило, эффективнее, чем volatile (т.к. оптимизация убирается ровно в одной точке), и проще не всадить ошибку (я приводил пример: попытка защитить volatile-переменной доступ к обычной - типовая ошибка).
Мне кажется это преждевременная оптимизация. С барьерами проще ошибок наделать. Мое мнение)
0
04.04.2016, 22:18
Answers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
04.04.2016, 22:18

исследование volatile
Здравствуйте. В университете дали задание: &quot;Исследование квалификаторов volatile и инструкции...

Использование Volatile
В общем, вопрос как его использовать. Есть такой тестовый код: using System; using...

const volatile
Пример из Шилдт Г. &quot;С++ Базовый курс (3-е издание, 2010)&quot; стр 205 const volatile unsigned char...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.