Если ваш вопрос не влез ни в одну из вышеперечисленных тем, вам сюда.
Ответить

О volatile замолвлю я слово

Вс янв 16, 2022 07:01:38

О volatile замолвлю я слово

volatile.pdf
(25.53 KiB) Скачиваний: 109


Глядя на полемику по поводу использования ключевого слова volatile, возникшую в viewtopic.php?f=62&t=37190&start=7140, я, признаться, был обескуражен той лёгкостью подхода, с которой некоторые авторы навязывают свою точку зрения и учат молодёжь дурному: не использовать это слово, использовать какие-то «ключи оптимизации» вместо него и т.д. и т.п.
В скобках отмечу, что исходя из своего опыта преподавания программирования микроконтроллеров, я знаю, что ровно 100% студентов налетают на «грабли» с volatile. И это — неспроста.
Всё дело в непонимании для чего оно нужно и зачем его использовать.

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

Код:
      Ключевое слово volatile информирует компилятор, что значение переменной может меняться извне.


Что это значит на практике? А именно то, что сказано. То есть, если переменная обозначена как volatile, то её значение может произвольно меняться в любой момент времени.
Что из этого следует? А следует из этого простой вывод: мы не можем гарантировать, что переменная в следующий момент времени будет иметь то же значение, что и в предыдущий.
Поэтому, компилятор, встречая volatile-переменную «понимает», что любое действие с ней (а это лишь два возможных действия — чтение и запись) — следует производить в точности как указал программист, без всяких оптимизаций.

Преимущества слова volatile перед многомудрыми советами «использовать ключи оптимизации», «прагмы» и «атрибуты» и прочими «а у меня и так всё работает» - очевидны:

1. Слово volatile — это стандарт. То есть его обязан понимать и использовать любой компилятор C/C++. Вывод — программа более переносима.
2. Ключи оптимизации распространяются на отдельный файл. Слово volatile — распространяется на отдельную переменную или поле структуры. То есть, с точки зрения оптимизации, за которую боролись сторонники «ключей оптимизации, прагм и атрибутов», именно слово volatile позволяет выполнить более точную оптимизацию программы.
3. Ключи компиляции привязаны к конкретному компилятору. Отсюда непереносимость программы. Кроме того, выставлять в программе из десятков, а то и сотен файлов индивидуальные ключи оптимизации для каждого из файлов — это очень на любителя извращений.
4. Прагмы и атрибуты ещё более, чем ключи оптимизации, привязаны к конкретному компилятору. Отсюда ещё большая непереносимость программы.

Итак, volatile — это стандарт и переносимость плюс облегчение себе жизни в плане экспериментов с ключами оптимизации.

Разумеется, что и прагмы и атрибуты и ключи оптимизации — вещи нужные и важные. Но не надо пытаться членом дрова рубить — и член угробишь и печь не растопишь. Каждой задаче — свой инструмент.

А теперь — слайды! Ну то есть примеры — зачем и для чего нужно использовать volatile.

Запись в аппаратный регистр
Итак, предположим, некто В.Пупкин начал писать программу. И есть у него байтовый регистр UART FIFO DATA, выглядящий для микроконтроллера как ячейка памяти, расположенная по адресу 0x010A Запись в этот регистр приводит к записи байта в FIFO UART, а чтение из него, соответственно к чтению из FIFO UART.
В.Пупкин, начитавшийся советов о том, что volatile есть зло, уверенно пишет тест-программу (инициализацию UART опустим):

Код:
unsigned char* udr = (unsigned char*)0x010A;
*udr = 'A';


и с любовью во взоре наблюдает буковку 'A' на экране терминала. Затем, В.Пупкин, как истинный говноайтишнег решает вывести своё имя на экран:

Код:
unsigned char* udr = (unsigned char*)0x010A;
*udr = 'В';
*udr = 'а';
*udr = 'с';
*udr = 'я';


И что он видит? А видит он только букву 'я' — последнюю букву. И начинает рвать волосы на теле, если таковые имеются, ругать землю, небо, страну, террана и вообще все на свете. Но, как обычно, виноват сам В.Пупкин.
Рвёт волосищи и ругается он до тех пор, пока не докладывается, что надо бы почитать умных людей и понять — почему от его имени осталась последняя буква.
Объяснение просто. Многие компиляторы, даже при отключённой оптимизации, кое-какую оптимизацию всё же производят. И компилятор, абсолютно законно, решает, что если в одну и ту же переменную производится несколько записей подряд, то он вправе выкинуть все операции записи, кроме последней. А что? Результат то не меняется. Для обычной переменной, разумеется, не меняется. Но у нас то переменная необычная.
Совершенно меняется результат выполнения кода, если написать:

Код:
volatile unsigned char* udr = (unsigned char*)0x010A;
*udr = 'В';
*udr = 'а';
*udr = 'с';
*udr = 'я';


На экране появится имя 'Вася' и наш программист будет удовлетворён полностью. Почему так произойдёт — понять не сложно. Раз переменная помечена как volatile — то запись в неё будет производиться каждый раз, вне зависимости от того, что там кажется компилятору.
Пример этот не такой надуманный как кажется. Буфера FIFO в UART и не только в UART - это вещь распространённая.

Чтение из аппаратного регистра
Предположим, что наш В.Пупкин опрашивает состояние входа GPIO и ждёт нажатия кнопки. Пусть регистр состояния GPIO байтовый и расположен как ячейка памяти по адресу 0x0507. Проверять будем бит 3. Пусть при нажатой кнопке в бит 3 выставляется «нуль», а если кнопка не нажата, то в бите 3 будет «единица».

Код:
unsigned char* gpio = (unsigned char*)0x0507;
while( (*gpio) & (1<<3) ) {} // Ждём нажатия кнопки


Тут сложно точно сказать, что произойдёт. Но многие компиляторы соптимизируют этот цикл до однократного чтения из регистра gpio и затем уйдёт в бесконечный цикл, если в момент опроса кнопка не нажата.
И опять же — запись:

Код:
volatile unsigned char* gpio = (unsigned char*)0x0507;
while( (*gpio) & (1<<3) ) {} // Ждём нажатия кнопки (бит 3 == 0)


решит проблему опроса регистра. Опрос регистра gpio будет производиться каждую итерацию цикла.

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

Код:
// Переменная хранит посленее считанное значение из UART
char udr_data;

// Прерывание по приходу байта в UART
ISR_UART_RECV(){
      udr_data = UDR; /* читаем данные в переменную */
}

// Какойто основной цикл
..
udr_data=0;
while(1) {
      switch(udr_data) {
         case '1':
            // Пришло 1 — что-то делаем
         break;
         case '2':
            // Пришло 2 — что-то делаем
         break;
      }
}


То есть идёт постоянная проверка значения переменной udr_data и в зависимости от значения в ней производится какое-то действие. Всё логично. С первого взгляда.
Но… работать такой код часто не будет. Многие компиляторы вообще выкинут все тело switch-case потому как решат, что если переменной присвоен 0 — то она никогда не станет равной '1' или '2'. И, следовательно, зачем лишний код? Мёртвый цикл лучше. И короче.
Точно так же тут поможет объявление переменной как volatile:

Код:
// Переменная хранит посленее считанное значение из UART
volatile char udr_data;


В этом случае switch-case будет работать как надо — каждый раз проверяя значение переменной udr_data.
Заключение
Разумеется, что во всех приведённых примерах можно было бы добиться работоспособности кода и без volatile.
Но только для этого надо было бы написать примеры для каждого конкретного компилятора: GCC, IAR, KEIL и других.
А вот ключевое слово volatile — универсально и бьёт точно в цель — то есть в ту переменную, которая особенная и не задевает код, не относящейся к другим объектам.
Если вы загляните в исходник библиотек для микроконтроллеров, то увидите, что все регистры там объявлены как volatile. И это не с проста.
Проще всегда делать правильно, чем сэкономить десяток байт кода, но постоянно рисковать нарваться на грабли. Адепты мантры - «а у меня и так работает» - часто нарываются на очень неожиданное поведение кода в самый неподходящий момент времени. Удачи им в скачке по граблям.
Разумеется, что использовать ключевое слово volatile нужно с умом, то есть применять его только к тем переменным, которые либо изменяются аппаратно (как на чтение, так и на запись), либо модифицируются в прерывании, а проверяются вне его.
Конечно, бывают и более сложные случаи. Но нельзя объять необъятного и потому думайте сами, где надо ставить volatile, а где оно лишнее.
Счастливой разработки!

Re: О volatile замолвлю я слово

Вс янв 16, 2022 08:27:41

Это что ж за осел про некие ключи компиляции говорил? Что за ключи такие, которые позволяют часть переменных волатильными обозначить?

Re: О volatile замолвлю я слово

Вс янв 16, 2022 08:29:28

Это что ж за осел про некие ключи компиляции говорил? Что за ключи такие, которые позволяют часть переменных волатильными обозначить?


Не часть, а все, как я понял. Оптимизацию полностью отключить:)

Но там было больше "а у меня и так все работает".

Re: О volatile замолвлю я слово

Вс янв 16, 2022 09:20:22

И что он видит? А видит он только букву 'я' — последнюю букву. И начинает рвать волосы на теле, если таковые имеются, ругать землю, небо, страну, террана и вообще все на свете.

Мало того, Вася в ряде случаев не сможет проконтролировать вожделенную переменную в окне окне отладки, если она не volatile. Ну не появляется в окошке с переменными и все тут. Поэтому приходится волатилить даже там, где вроде нужды особой нет, лишь бы отладку сделать чуть более комфортной. Вот и пишешь volatile, а сам думаешь - где этот модификатор мне свинью подложит в будущем? Ведь более опытные товарищи то тут, то там пишут - volatile опасносте. Но, редко пишут в чем же проявляются баги - следствие использования модификатора. Или пишут настолько пространно, туманно и запутанно, что начинаешь сомневаться в их компетентности.

Поэтому считаю нужным выслушать начальников транспортн... противников volatile, хотя бы заочно. Вдруг что-то понятное и внятное скажут. Хотелось бы простыми человеческими словами (как можно более кратко) услышать - чем (и где, в каких случаях) я рискую используя этот модификатор?

Re: О volatile замолвлю я слово

Вс янв 16, 2022 12:46:24

Asmodey писал(а):Хотелось бы простыми человеческими словами (как можно более кратко) услышать - чем (и где, в каких случаях) я рискую используя этот модификатор?
Ничем, кроме связыванием рук оптимизатору.
Зачем выдумывать какие-то ситуации и описывать их, если просто достаточно вникнуть в суть квалификатора и понять, что он делает ? Тогда все вопросы отпадут сами собой.

Re: О volatile замолвлю я слово

Вс янв 16, 2022 13:11:34

Зачем выдумывать какие-то ситуации и описывать их, если просто достаточно вникнуть в суть квалификатора и понять, что он делает ? Тогда все вопросы отпадут сами собой.
Я знаю что он делает. Не знаю почему его считают опасным. Мало ли, может пропустил чего.

Ничем, кроме связыванием рук оптимизатору.

Понятно. А связывание, это хорошо. Потому как даже с отключенной оптимизацией компиляторы все равно продолжают чего-то там оптимизировать, причем подло - втихую и без предупреждения.

Re: О volatile замолвлю я слово

Вс янв 16, 2022 13:58:32

Код:
unsigned char* gpio = (unsigned char*)0x0507;
while( (*gpio) & (1<<3) ) {} // Ждём нажатия кнопки
Тут сложно точно сказать, что произойдёт. Но многие компиляторы соптимизируют этот цикл до однократного чтения из регистра gpio и затем уйдёт в бесконечный цикл, если в момент опроса кнопка не нажата.
А самые умные, если увидят, что незадолго до этого цикла, была запись по этому адресу какого-то значения X, могут и "однократное чтение" удалить как ненужное, использовав сохранённое в регистре значение X. :)

А ведь (раз это адрес регистра периферии) по данному адресу для операций чтения и для операций записи могли находиться даже совершенно разные регистры.

Re: О volatile замолвлю я слово

Вс янв 16, 2022 16:41:46

Ведь более опытные товарищи то тут, то там пишут - volatile опасносте. Но, редко пишут в чем же проявляются баги - следствие использования модификатора. Или пишут настолько пространно, туманно и запутанно, что начинаешь сомневаться в их компетентности.

Поэтому считаю нужным выслушать начальников транспортн... противников volatile, хотя бы заочно. Вдруг что-то понятное и внятное скажут. Хотелось бы простыми человеческими словами (как можно более кратко) услышать - чем (и где, в каких случаях) я рискую используя этот модификатор?


Никакой опасности от volatile нет.

Он говорит, что к данной переменной надо обращаться всегда, не используя оптимизацию.

Если написано, что надо считать значение волатильной переменной, то программа обязательно произведет чтение ячейки памяти.

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

Это чуть чуть увеличивает размер кода и немного замедляет программу. Но делает ее более предсказуемой.

Разумеется, что для обычных переменных это, как правило, не нужно.

Re: О volatile замолвлю я слово

Вс янв 16, 2022 16:48:58

Оптимизацию полностью отключить:)

Жееесть!

Re: О volatile замолвлю я слово

Пн янв 17, 2022 17:51:39

Кстати, есть возможность заставить известные компиляторы С и без волатайла всегда читать и писать ячейку памяти. Догадайтесь, какие:) только выигрыша это не дает:)

Re: О volatile замолвлю я слово

Пн янв 17, 2022 20:35:03

SfS писал(а):Никакой опасности от volatile нет
всегда найдется веревка достаточной длины... ну. вы понели :)))

любой инструмент может быть опасным. например, если чтение регистра что-то там сбрасывает в другом регистре. написал лишний раз упоминание такой переменной, и получи трудно вылавливаемый баг.


хотя бед от "неприменения" volatile, по моему мнению, все-таки больше :)))

Re: О volatile замолвлю я слово

Пн янв 17, 2022 20:48:25

ARV писал(а):если чтение регистра что-то там сбрасывает в другом регистре
пример такого идиотизма в студию!
каким образом чтение регистра может повлиять на другой регистр???

Re: О volatile замолвлю я слово

Пн янв 17, 2022 21:07:05

Starichok51 писал(а):пример такого идиотизма в студию!
легко: смотрим на регистр статуса USART AVR:
Bit 7 - RXC: UART Receive Complete - Прием завершен
Данный бит устанавливается в состояние 1 при пересылке принятого символа из сдвигового регистра приема в UDR. Бит устанавливается вне зависимости от отсутствия или наличия ошибок приема кадра. При установленном в UCR бите RXCIE и установленном бите RXC выполняется прерывание по завершению приема UART. Бит RXC очищается при считывании UDR. При приеме данных инициированном прерыванием, подпрограмма обработки прерывания по завершению приема UART должна считать UDR, с тем, чтобы очистить RXC, иначе по окончании подпрограммы обработки прерывания произойдет новое прерывание.
как видите, читаем регистр данных, бит падает в регистре статуса.

что-то подобное и в других периферийных модулях AVR есть, и, если память не подводит, на других платформах такое не редкость

Re: О volatile замолвлю я слово

Пн янв 17, 2022 21:16:36

как видите, читаем регистр данных, бит падает в регистре статуса.

И из-за этого получаем трудно вылавливаемый баг? А если бы не было volatile и компилятор мог читать или не читать из регистра когда ему вздумается, соответственно флаг мог сбрасываться или нет, то никаких проблем? :)

Re: О volatile замолвлю я слово

Пн янв 17, 2022 21:23:12

Reflector писал(а):И из-за этого получаем трудно вылавливаемый баг?
из-за этого нет, конечно. данный пример демонстрирует только ситуацию, когда чтение одного регистра воздействует на другой.

Re: О volatile замолвлю я слово

Пн янв 17, 2022 21:26:42

Правильно, этот пример демонстрирует полезность volatile, а не его потенциальную опасность.

Re: О volatile замолвлю я слово

Пн янв 17, 2022 21:34:56

предположим, что чтение регистра REG вызывает прерывание (потому что где в другом регистре битик устанавливается)
Код:
REG; REG;
в случае не-volatie прерывания не вызовет, с volatile - аж два раза

Re: О volatile замолвлю я слово

Пн янв 17, 2022 21:39:32

в случае не-volatie прерывания не вызовет, с volatile - аж два раза

Нет, в случае с не-volatile получим два, одно или ни одного прерывания. А два, или прерывание при каждом чтении, - это как раз правильное число прерываний и есть.
Последний раз редактировалось Reflector Пн янв 17, 2022 21:41:13, всего редактировалось 1 раз.

Re: О volatile замолвлю я слово

Пн янв 17, 2022 21:40:59

Reflector писал(а):в случае с не-volatile получим два, одно или ни одного прерывания.
тем хуже.
ч.т.д.

Re: О volatile замолвлю я слово

Пн янв 17, 2022 21:44:14

Что тем хуже? Программист читая регистр знает, что должно генериться прерывание, возможно именно для этого он его и читает, а без volatile результат непредсказуем...
Ответить