Volatile read c: How to Use C’s Volatile Keyword
Read(Boolean) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(Byte) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом. On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(Double) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(Int16) | Считывает значение указанного поля. Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(Int32) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(Int64) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(IntPtr) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом. On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(SByte) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(Single) | Считывает значение указанного поля. Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(UInt16) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(UInt32) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(UInt64) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом. On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read(UIntPtr) | Считывает значение указанного поля.Reads the value of the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Read<T>(T) | Считывает ссылку на объект из указанного поля. Reads the object reference from the specified field. В системах, которым это необходимо, вставляет барьер памяти, не позволяющий процессору изменять порядок операций памяти следующим образом: если операция чтения или записи появляется после данного метода в коде, процессор не сможет переместить ее перед этим методом.On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method. |
Volatile, модели и барьеры памяти – Бояринцев .NET – Разработка на .NET
Сегодня будем разбираться с volatile и всем, что с ним связано. Тема эта интересна тем, что чтобы полностью её понимать необходимо опуститься вплоть до уровня процессора и даже узнать чем отличаются разные процессорные архитектуры в плане работы с памятью. Так как материал объёмный и сложный, то не буду пытаться, что-то объяснить сам, а буду давать ссылки.
Что о volatile нам рассказал Рихтер
У Рихтера в книге для volatile отведёно 7 страниц и этого явно недостаточно, чтобы хорошенько разобраться с темой.
Компилятор C#, JIT-компилятор и даже сам процессор могут оптимизировать ваш код.
В процессе оптимизации кода компилятором C#, JIT-компилятором и процессором гарантируется сохранение его назначения. То есть с точки зрения одного потока метод делает то, зачем мы его написали, хотя способ реализации может отличаться от описанного исходном коде. Однако при переходе к многопоточной конфигурации ситуация может измениться.
То есть, если у вас многопоточное приложение с разделяемыми несколькими потоками данными (например полями класса), то у вас нет гарантии того, что данные в эти разделяемые поля будут записаны одним потоком и прочитаны другим потоком именно в том порядке, в котором вы их написали в своём коде.
a = c;
b = d;
flag = true;
.NET не гарантирует, что чтения и записи выше будут произведены именно в этом порядке, поэтому если вы хотите написать код, в котором один поток сначала читает какие-то данные и потом проставляет флаг в true, а второй поток проверяет значение флага и начинает работать только тогда, когда он выставлен в true, то без использования специальных средств у вас нет гарантий, что этот код будет работать так как вы его задумали.
Что же может сделать этот код работоспособным? — Методы Volatile.Write
и Volatile.Read
Метод Volatile.Write заставляет записать значение в параметр location непосредственно в момент обращения. Бодее ранние загрузки и сохранения программы должны происходить до вызова этого метода.
Метод Volatile.Read заставляет считать значение параметра address непосредственно в момент обращения. Более поздние загрузки и сохранения программы должны происходить после вызова этого метода.
Или ключевое слово volatile
применённое к полям
JIT-компилятор гарантирует, что доступ к полям, помеченным данным ключевым словом, будет происходить в режиме волатильного чтения или записи, поэтому в явном виде вызывать статические методы Read и Write класса Volatile больше не требуется.
Volatile и Модель памяти
Разобраться в теме гораздо глубже поможет доклад Валерия Петрова Модель памяти . NET
Из доклада можно узнать:
- Почему процессоры переставляют выполняемые инструкции местами
- Какие оптимизации могут произвести с вашим кодом Компилятор/JIT/CPU
- Что такое модель памяти и при чём тут она
- Как работает ключевое слово
volatile
и методыVolatile.Write
иVolatile.Read
и как правильно их использовать
Кроме того, что в докладе очень доступная подача материала, мне нравится ещё и то, что Валерий для подтверждения своих слов приводит ссылки на пункты спецификации и цитаты из неё.
Также я нашёл презентацию Валерия Петрова, но видимо она сделана к какому-то другому докладу, потому что слайдов в ней намного больше и больше разного материала затронуто.
Хочется остановится на определении модели памяти.
Согласно википедии:
In computing, a memory model describes the interactions of threads through memory and their shared use of the data.
A memory model allows a compiler to perform many important optimizations. Compiler optimizations like loop fusion move statements in the program, which can influence the order of read and write operations of potentially shared variables. Changes in the ordering of reads and writes can cause race conditions. Without a memory model, a compiler is not allowed to apply such optimizations to multi-threaded programs in general, or only in special cases.
Моя “расслабленная” интерпретация этого определения: :
Модель памяти — это разрешения, которые есть у компилятора на проведение оптимизаций, которые могут повлиять на порядок операций чтения и записи, которые могут производиться с памятью, с которой работают несколько потоков одновременно, что в свою очередь может привести к багу в работе какого-либо потока, или если ещё более кратко — это возможные перестановки операций чтения и записи относительно их порядка в исходном коде.
На хабре также есть статья, которая довольно близка к докладу Валерия по кругу разбираемых вопросов:
Барьеры памяти и неблокирующая синхронизация в . NET от Дмитрия Костикова.
В ней материал тоже подаётся вполне доступно, но есть несколько комментариев от меня:
Написано, что в модели памяти .NET разрешены все перестановки кроме write-write — Валерий Петров упоминает, в своём докладе, что об этом часто пишут в статьях, но неизвестно откуда взялся этот факт и насколько он соответствует действительности, в спецификациях или каких-либо других источниках его подтверждение найти не удаётся.
Материал непосредственно про барьеры памяти мне кажется изложен не очень понятно.
В самом конце статьи в разделе “Производительность Thread.Volatile* и ключевого слово volatile” написано, что: “На большинстве платформ (точнее говоря, на всех платформах, поддерживаемых Windows, кроме умирающей IA64) все записи и чтения являются volatile write и volatile read соответственно. Таким образом, во время выполнения ключевое слово volatile не оказывает никакого влияния на производительность. ” — текст неактуальный на данный момент, так как с тех пор появилась поддержка ARM-процессоров, а так как в статье, не были затронуты особенности разных процессорных архитектур в плане перестановок инструкций и то как на них влияет volatile, то этот параграф всё-равно будет непонятен неподготовленному читателю. Также лично мне не кажется удачной формулировка, что запись и чтения на платформах являются волатильными, но об этом позже.
Какие ещё есть статьи, которые, в принципе, можно пропустить
- Статья Джо Албахари Threading in C# PART 4: ADVANCED THREADING первая часть, которой посвящена неблокирующей синхронизации в общем и volatile в частности — есть утверждения, которые либо не понятны, либо которые я не знаю как подтвердить.
- Модель памяти C# в теории и на практике Игоря Островского — к этой статье тоже есть вопросы в плане используемых утверждений и формулировок.
- C# — The C# Memory Model in Theory and Practice, Part 2 — вторая часть статьи Игоря Островского про модель памяти, в этой статье разбираются три вида оптимизаций, которые может произвести с кодом компилятор, а также особенности работы volatile на архитектурах x86/x64, Itanium, ARM — материал про особенности конкретных архитектур может представлять интерес.
Если вы прочитали/прослушали материалы выше, то теперь вы знаете интересные факты о том, что в .NET
- Вызов Volatile.Write/Volatile.Read идентичны использованию ключевого слова volatile в плане получаемых эффектов на выполнение кода, а вот вызовы Thread.VolatileWrite/Thread.VolatileRead ведут себя по другому.
- Волатильная запись и последующее волатильное чтение могут быть переставлены местами (но это не только в .NET)
Барьеры памяти
По определению David Howells и David Howells в статье LINUX KERNEL MEMORY BARRIERS:
Independent memory operations are effectively performed
in random order, but this can be a problem for CPU-CPU interaction and for I/O.
What is required is some way of intervening to instruct the compiler and the
CPU to restrict the order.
Memory barriers are such interventions. They impose a perceived partial
ordering over the memory operations on either side of the barrier.
Such enforcement is important because the CPUs and other devices in a system
can use a variety of tricks to improve performance, including reordering,
deferral and combination of memory operations; speculative loads; speculative
branch prediction and various types of caching. Memory barriers are used to
override or suppress these tricks, allowing the code to sanely control the
interaction of multiple CPUs and/or devices.
Или в моей расслабленной интерпретации: барьеры памяти — это инструкции, способные заставить компилятор и даже процессор прекратить выполнять оптимизации и гарантировать, что определённые операции чтения и записи могут остаться с какой-либо из сторон барьера памяти.
Использование ключевого слова volatile
или методов Volatile.Read/Write
— это один из способов установить барьер памяти, Thread.MemoryBarier
— другой.
Статья на эту тему Memory Barriers in . NET Nadeem Afana.
Статья интересна тем, что рассматривает вопрос работы барьеров памяти довольно близко к тому, как они работают на уровне процессоров.
Мои замечания к статье:
- Автор тоже упоминает, что существует модель памяти .NET в которой запрещены перестановки запись-запись.
- Автор упоминает, что для lock, Interlocked и прочих вещей генерируется полный барьер памяти — ECMA-335 говорит нам другое в разделе I.12.6.5 Locks and threads.
Если статья вас заинтересовала, но некоторые слова вы не поняли, например, такие STORE Buffer и Cache Coherence, и есть желание разобраться дальше, то читайте статью Memory Barriers: a Hardware View for Software Hackers Paul E. McKenney (или русский перевод первой части статьи) — тут всё прямо с алгоритмами того, как процесс происходит внутри процессора.
Дополнительный материал по барьерам памяти
LINUX KERNEL MEMORY BARRIERS David Howells, Paul E. McKenney
Волатильное чтение и запись на архитектуре процессора x86
Во многих статьях пишут, что на архитектуре процессора x86 все операции чтения и записи осуществляются как волатильное чтение и волатильная запись, поэтому использование волатильного чтения и записи в коде программы будет иметь влияние только на компилятор, но не на инструкции процессора. К сожалению, никто не даёт ссылок на источник этого утверждения, я попытался найти этот источник в итоге нашёл только описание модели памяти x86: Intel® 64 and IA-32 Architectures
Software Developer’s Manual (раздел 8.2) и в нём нет формулировки про волатильное чтение и запись, есть только список разрешённых перестановок и фактически разрешена только перестановка запись и последующее чтение, что совпадает с разрешёнными перестановками при волатильных чтениях и записях (волатильная запись и последующее волатильное чтение могут быть переставлены) — видимо из-за этого совпадения разрешённых/запрещённых перестановок и возникла формулировка про то что операции чтения/записи на архитектуре x86 волатильные.
Что ещё можно прочитать
volatile vs. volatile / Блог компании OTUS / Хабр
Всем привет! Мы подготовили перевод данной статьи в преддверии старта курса «Разработчик C++»
Повесть о двух, казалось бы, похожих, но все же разных инструментах
Херб — автор бестселлеров и консультант по вопросам разработки программного обеспечения, а также архитектор ПО в Microsoft. Вы можете связаться с ним на www.gotw.ca.
Что означает ключевое слово volatile? Как его следует использовать? К всеобщему замешательству, существует два распространенных ответа, потому что в зависимости от языка, на котором вы пишете код, volatile относится к одной из двух различных техник программирования: lock-free программированию (без блокировок) и работе со «необычной» памятью. (См. Рисунок 1.)
Рисунок 1: повесть о двух технических требованиях.
Усугубляет путаницу и то, что эти два различных случая использования имеют частично совпадающие предпосылки и накладываемые ограничения, что заставляет их выглядеть более схожими, нежели они являются на самом деле. Давайте же четко определим и поймем их, и разберемся, как их правильно употреблять в C, C++, Java и C# — и всегда ли именно как volatile.
Таблица 1: Сравнение накладывающихся, но разных предпосылок.
Случай 1: Упорядоченные атомарные переменные для lock-free программирования
Lock-free программирование связано с налаживанием коммуникации и синхронизации между потоками с помощью инструментов более низкого уровня, нежели взаимоисключающие блокировки. Как в прошлом, так и сегодня существует широкий спектр таких инструментов. В грубом историческом порядке они включают явные барьеры (explicit fences/barriers — например, mb() в Linux), специальные упорядочивающие вызовы API (например, InterlockedExchange в Windows) и различные разновидности специальных атомарных типов. Многие из этих инструментов муторны и/или сложны, и их широкое разнообразие означает, что в конечном итоге lock-free код пишется в разных средах по-разному.
Однако в последние несколько лет наблюдается значительная конвергенция между поставщиками аппаратного и программного обеспечения: вычислительная индустрия объединяется вокруг последовательно согласованных упорядоченных атомарных переменных (ordered atomic variables) в качестве стандарта или единственного способа написания lock-free кода с использованием основных языков и платформ ОС. В двух словах, упорядоченные атомарные переменные безопасны для чтения и записи в нескольких потоках одновременно без каких-либо явных блокировок, поскольку они обеспечивают две гарантии: их чтение и запись гарантированно будут выполняться в том порядке, в котором они появляются в исходном коде вашей программы; и каждое чтение или запись гарантированно будут атомарными, “все или ничего”. У них также есть специальные операции, такие как compareAndSet, которые гарантированно выполняются атомарно. См. [1] для получения дополнительной информации об упорядоченных атомарных переменных и о том, как их правильно использовать.
Упорядоченные атомарные переменные доступны в Java, C# и других языках .NET, а также в готовящемся стандарте ISO C++, но под другими именами:
- Java предоставляет упорядоченные атомарные переменные под ключевым словом volatile (например, volatile int), полностью поддерживая это с Java 5 (2004). Java дополнительно предоставляет несколько именованных типов в java.util.concurrent.atomic, например, AtomicLongArray, который вы можете использовать для тех же целей.
- .NET добавил их в Visual Studio 2005, также под ключевым словом volatile (например, volatile int). Они подходят почти для любого варианта использования lock-free кода, за исключением редких примеров, подобных алгоритму Деккера. .NET исправляет оставшиеся ошибки в Visual Studio 2010, которая находится на стадии бета-тестирования на момент написания этой статьи.
- ISO C++ добавил их в черновик стандарта C++ 0x в 2007 году под шаблонным именем atomic
<T
>
(например, atomic). С 2008 года они стали доступны в Boost и некоторых других реализациях. [2]. Библиотека atomic ISO C++ также предоставляет C-совместимый способ написания этих типов и их операций (например, atomic_int), и они, вероятно, будут приняты ISO C в ближайшем будущем.
Пару слов об оптимизации
Мы рассмотрим, как упорядоченные атомарные переменные ограничивают оптимизацию, которую могут выполнять компиляторы, процессоры, эффекты кэширования и другие элементы вашей среды выполнения. Итак, давайте сначала кратко рассмотрим некоторые основные правила оптимизации.
Фундаментальное правило оптимизации практически во всех языках таково: оптимизации, которые переупорядочивают («трансформируют») выполнение вашего кода, всегда являются легитимными, только если они не меняют смысла программы, так что программа не может определить разницу между выполнением исходного кода и преобразованного. В некоторых языках это также известно как правило «as-if», которое получает свое название из-за того факта, что преобразованный код имеет те же наблюдаемые эффекты, «как если бы» (as if) исходный исходный код был выполнен в том виде, в котором он был изначально написан.
Это правило имеет двоякий эффект: во-первых, оптимизация никогда не должна позволять получить результат, который раньше был невозможен, или нарушать любые гарантии, на которые исходному коду было разрешено полагаться, включая семантику языка. Если мы дадим невозможный результат, в конце концов, программа и пользователь, безусловно, смогут заметить разницу, и это уже не «как если бы» мы выполнили исходный не преобразованный код.
Во-вторых, оптимизации позволено сократить набор возможных исполнений. Например, оптимизация может привести к тому, что некоторые потенциальные (но не гарантированные) чередования (изменение порядка выполнения инструкций — интерливинг) никогда не произойдут. Это нормально, потому что программа все равно не может рассчитывать на то, что они произойдут.
Упорядоченные атомарные переменные и оптимизация
Использование упорядоченных атомарных переменных ограничивает виды оптимизации, которые может выполнять ваш компилятор, процессор и система кэширования. [3] Стоит отметить два вида оптимизаций:
- Оптимизации упорядоченных атомарных операций чтения и записи.
- Оптимизации соседних обычных операций чтения и записи.
Во-первых, все упорядоченные атомарные операции чтения и записи в заданном потоке должны выполняться строго в порядке исходного кода, потому что это одна из фундаментальных гарантий упорядоченных атомарных переменных. Тем не менее, мы все еще можем выполнить некоторые оптимизации, в частности, оптимизации, которые имеют такой же эффект, как если бы этот поток просто всегда выполнялся так быстро, что в некоторых точках не возникало бы чередования с другим потоком.
Например, рассмотрим этот код, где a — упорядоченная атомарная переменная:
a = 1; // A
a = 2; // B
Допустимо ли для компилятора, процессора, кэша или другой части среды выполнения преобразовывать приведенный выше код в следующий, исключая избыточную запись в строке A?
// A ': OK: полностью исключить строку A
a = 2; // B
Ответ: «Да». Это легитимно, потому что программа не может определить разницу; это “как если бы” этот поток всегда работал так быстро, что никакой другой поток, работающий параллельно, в принципе не может чередоваться между строками A и B, чтобы увидеть промежуточное значение. [4]
Аналогично, если a — упорядоченная атомарная переменная, а local — неразделяемая локальная переменная, допустимо преобразовать
a = 1; // C: запись в a
local = a; // D: чтение из a
в
a = 1; // C: запись в a
local = 1; // D': OK, применить "подстановку константы"
что исключает чтение из a. Даже если другой поток одновременно пытается выполнить запись в a, это “как если бы” этот поток всегда работал так быстро, что другому потоку никогда не удавалось чередовать строки C и D, чтобы изменить значение, прежде чем мы успеем записать наше собственное обратно в local.
Во-вторых, близлежащие обычные операции чтения и записи все еще могут быть переупорядочены вокруг упорядоченных атомарных, но с некоторыми ограничениями. В частности, как описано в [3], обычные операции чтения и записи не могут перемещаться вверх по отношению к упорядоченному атомарному чтению (от “после” к “до”) и не могут перемещаться вниз по отношению к упорядоченной атомарной записи (от “до” к “после”). Короче говоря, это может вывести их из критического раздела кода, и вы сможете писать программы, которые выиграют от этого в производительности. Для получения более подробной информации см. [3].
На этом все касательно lock-free программирования и упорядоченных атомарных переменных. А как насчет другого случая, в котором рассматриваются какие-то «волатильные» адреса?
Случай 2: Свободные от семантики переменные для памяти с «необычной» семантикой
- Вторая необходимость — работать с «необычной» памятью, которая выходит за рамки модели памяти данного языка, где компилятор должен предполагать, что переменная может изменить значение в любое время и/или что чтение и запись могут иметь непознаваемую семантику и следствия. Классические примеры:
- Аппаратные регистры, часть 1: Асинхронные изменения. Например, рассмотрим ячейку памяти М на пользовательской плате, которая подключена к прибору, который производит запись непосредственно в M. В отличие от обычной памяти, которая изменяется только самой программой, значение, хранящееся в M, может измениться в любое время, даже если ни один программный поток не пишет в нее; следовательно, компилятор не может делать никаких предположений о том, что значение будет стабильным.
- Аппаратные регистры, часть 2: Семантика. Например, рассмотрим область памяти M на пользовательской плате, где запись в эту позицию всегда автоматически увеличивается на единицу. В отличие от обычного места в RAM памяти, компилятор даже не может предположить, что выполнение записи в M и последующее сразу после нее чтение из M обязательно прочитает то же значение, которое было записано.
- Память, имеющая более одного адреса. Если данная ячейка памяти доступна с использованием двух разных адресов А1 и А2, компилятор или процессор может не знать, что запись в ячейку А1 может изменить значение в ячейке А2. Любая оптимизация, предполагающая? что запись в A1, не изменяет значение A2, будет ломать программу, и должна быть предотвращена.
Переменные в таких местах памяти являются неоптимизируемыми переменными, потому что компилятор не может безопасно делать какие-либо предположения о них вообще. Иными словами, компилятору нужно сказать, что такая переменная не участвует в обычной системе типов, даже если она имеет конкретный тип. Например, если ячейка памяти M или A1/A2 в вышеупомянутых примерах в программе объявлена как «int», то что это в действительности означает? Самое большее, что это может означать, это то, что она имеет размер и расположение int, но это не может означать, что он ведет себя как int — в конце концов, int не автоинкрементируют себя, когда вы записываете в него, или таинственным образом не изменяет свое значение, когда вы совершите запись во что-то похожее на другую переменную по другому адресу.
Нам нужен способ отключить все оптимизации для их чтения и записи. ISO C и C++ имеют портативный, стандартный способ сообщить компилятору, что это такая специальная переменная, которую он не должен оптимизировать: volatile.
Java и .NET не имеют сопоставимой концепции. В конце концов, управляемые среды должны знать полную семантику программы, которую они выполняют, поэтому неудивительно, что они не поддерживают память с «непознаваемой» семантикой. Но и Java, и .NET предоставляют аварийные шлюзы для выхода из управляемой среды и вызова нативного кода: Java предоставляет Java Native Interface (JNI), а .NET предоставляет Platform Invoke (P/Invoke). Однако в спецификации JNI [5] о volatile ничего не говорится и вообще не упоминается ни Java volatile, ни C/C++ volatile; аналогично, в документации P/Invoke не упоминается взаимодействие с .NET volatile или C/C++ volatile. Таким образом, для правильного доступа к неоптимизируемой области памяти в Java или .NET вы должны написать функции C/C++, которые используют C/C++ volatile для выполнения необходимой работы от имени вызывающего их уравляющего кода, чтобы они полностью инкапсулировали и скрывали volatile память (т. е. не принимали и не возвращали ничего volatile) и вызывать эти функции через JNI и P/Invoke.
Неоптимизируемые переменные и (не) оптимизация
Все операции чтения и записи неоптимизируемых переменных в заданном потоке должны выполняться точно так, как написаны; никакие оптимизации не допускаются вообще, потому что компилятор не может знать полную семантику переменной и когда и как ее значение может измениться. Это более строгие выражения (по сравнению с упорядоченными атомарными переменными), которую нужно выполнять только в порядке исходного кода.
Рассмотрим снова два преобразования, которые мы рассматривали ранее, но на этот раз заменим упорядоченную атомарную переменную a на неоптимизируемую (C/C++ volatile) переменную v:
v = 1; // A
v = 2; // B
Легитимно ли это преобразовать следующим образом, чтобы удалить явно лишнюю запись в строке A?
// A ': невалидно, нельзя исключить запись
v = 2; // B
Ответ — нет, потому что компилятор не может знать, что исключение записи строки A в v не изменит смысла программы. Например, v может быть местоположением, к которому обращается пользовательское оборудование, которое ожидает увидеть значение 1 перед значением 2 и иначе не будет работать правильно.
Аналогично, если v неоптимизируемая переменная, а local — неразделяемая локальная переменная, преобразование недопустимо
v = 1; // C: запись в v
local = v; // C: чтение из v
в
a = 1; // C: запись в v
local = l; // D': невалидно, нельзя совершить
// "подстановку константы"
для упразднения чтение из v. Например, v может быть аппаратным адресом, который автоматически увеличивается каждый раз при записи, так что запись 1 даст значение 2 при следующем считывании.
Во-вторых, что насчет соседних обычных операций чтения и записи — можно ли их переупорядочить вокруг неоптимизируемых? Сегодня нет практического портативного ответа, потому что реализации компилятора C/C++ сильно различаются и вряд ли в скором времени начнут движение к единообразию. Например, одна интерпретация Стандарта C++ гласит, что обычные операции чтения могут свободно перемещаться в любом направлении относительно чтения или записи volatile C/C++, а вот обычная запись вообще не может перемещаться относительно чтения или записи volatile C/C++ — что делает volatile C/C++ в то же время и менее и более ограничительным, чем упорядоченные атомарные операции. Некоторые поставщики компиляторов поддерживают эту интерпретацию; другие вообще не оптимизируют чтение или запись volatile; а третьи имеют свою собственную семантику.
Резюме
Для написания безопасного lock-free кода, который коммуницирует между потоками без использования блокировок, предпочитайте использовать упорядоченные атомарные переменные: Java/.NET volatile, C++0x atomic<T
>
и C-совместимый atomic_T.
Чтобы безопасно обмениваться данными со специальным оборудованием или другой памятью с необычной семантикой, используйте неоптимизируемые переменные: ISO C/C++ volatile. Помните, что чтение и запись этих переменных не обязательно должны быть атомарными.
И наконец, чтобы объявить переменную, которая имеет необычную семантику и обладает какой-либо из или же сразу всеми гарантиями атомарности и/или упорядочения, необходимыми для написания lock-free кода, только черновик стандарта ISO C++0x предоставляет прямой способ ее реализации: volatile atomic <T
>
.
Примечания
- Г. Саттер. «Writing Lock-Free Code: A Corrected Queue» (DDJ, октябрь 2008 г.). Доступно online тут.
- [2] См. www.boost.org.
- [3] Г. Саттер. «Apply Critical Sections Consistently» (DDJ, ноябрь 2007 г.). Доступно в Интернете тут.
- [4] Существует распространенное возражение: «В исходном коде другой поток мог видеть промежуточное значение, но это невозможно в преобразованном коде. Разве это не изменение наблюдаемого поведения?» ответ: «Нет», потому что программе никогда не гарантировалось, что она будет фактически чередоваться как раз вовремя, чтобы увидеть это значение; для этого потока уже был легитимный результат — он всегда работал так быстро, что чередование никогда не случалось. Опять же, то, что следует из этой оптимизации, так это уменьшает набор возможных исполнений, что всегда является легитимным.
- [5] С. Лянг. Java Native Interface: Руководство программиста и спецификация. (Прентис Холл, 1999). Доступно online тут.
Бесплатный вебинар: «Hello, World!» на фарси или как использовать Unicode в C++»
1337/h5x0r | j00 f001, 7|-|47 p4g3 d0|\|’7 eXi57! y0u sux0rz. |
Afrikaans | Die webblad is nie beskikbaar nie. |
American South | Ah cain’t find th’ page yer lookin’ fer. |
Amiga User | Software Failure. Guru Meditation #22000000.48454C50 |
Arabic | Unwan Al URL Aladiina Tabhatuuna Anhu Ghayru mawjuud. |
Armenian | Ait tegheh vor uzumek tesnek chi tcharvoom. |
Armenian | Tents ej mer mot el chka, yerevi chi el yekhel! |
Assamese (Indian) | Kyama kariba. Apuni bichara tathyakhini vartaman majoot nai. |
Asturiano | La fueya non ta. |
Australian | Strewth mate yer bloody page has shot through |
Aymara | Pagina haniw utjiti! |
Bajan | Wuhloss, man, de page yuh lookin for ent here!! |
Bangla | Ohho! page ta to ekhon ar shekhane nai. |
base64 | VGhlIHBhZ2UgeW91IGFyZSBsb29raW5nIGZvciBpcyBubyBsb25n ZXIgb24gaWJpYmxpby5vcmcgb3IgaGFzIGJlZW4gbW92ZWQuCg== |
Basque-Euskara | Aizu! Ibiblio.org-en bilatzen duzun web-orririk ez dago. |
Basque | Eup! Ibiblio.org-en topetan zabilzan web-orririk ez dago. |
Bavarian | Dö Seitn is net do, vastest host kört! |
Belarusian (беларуская мова) | Запрошаная старонка не iснуе на гэтым сайте альбо была перанесена. |
Belgian (Antwerp dialect) | Eej, da blad kannek kik ni vinne jung! |
Belizean Creole | Ah nuh no weh e deh…..I FOUND A QUARTER! |
Bosnian | Dje ba zapelo? |
Brazil (São Paulo slang) | Aê mano, a página que cê tá caçano rodô, meu! |
Brazilian (gaúcho) slang | Mas báh tchê, tu tá mais perdido que cusco caído de caminhão de mudança! A págna não tá aqui. |
Brazilian Portuguese slang | Cadê a página que tava aqui? O gato comeu. |
Brazilian Portuguese | A página que você procura não existe. |
BSD user | The server is running Linux. What do you expect? |
Bulgarian | Greshka 404: Stranitsata ne e namerena. |
C | return ENOENT; |
Cape Afrikaans Slang | Kyk nou, die ding wat jy soek issie hierie sienjy. |
Carinthia | De vadommte saitn kumt nit! |
Catalan | La pàgina que busqueu ha canviat d’ubicació o ja no existeix. |
Chinese (Simplified) | 啊呀,没法找到网页.Þadovaná stránka nebyla nalezena. |
Danish | Filen eksisterer ikke længere på serveren. |
Dholuo | Mos, it oboke ma idwaro ok yudore gi sani. |
Dutch (Amsterdams) | Wat jij soek, kenne wij nie finde |
Dutch (Brabants dialect) | Wà gij zoekt op deez’ servert ister nie mir. |
Dutch (Land-van-Axels) | Da wa jie zoek, da kan’k hlad nie vinn’n. |
Dutch (Leids) | Teerrring juh, ga errrges onderrs kijke dan juh |
Dutch (Nederlands) | De pagina die U zoekt kan niet gevonden worden. |
Dutch | De door u opgevraagde pagina kan niet worden gevonden. |
Egyptian slang | El safHa elli bet-daWwar AlaiHa, lel-Asaff, mesh mutaaHa Hena, Haliyyan. |
Emo | I don’t know what you’re looking for but it’s no use. Might as well kill yourself. |
English (Bristolian Accent) | I casn’t find what thee bist lookin’ fer, me babber. |
English (East African- Kikuyu) | Da paej yu ah lookin fo eiz not avaerabouh. |
English (Lancastrian dialect) | Weers yon page geet to? T’int ‘ere! |
English (Yorkshire dialect) | Sithi, it’s noreer, issit? |
Esperanto | Ĉi paĝo, ĝi ne ekzistas. |
Estonian | Sinu poolt soovitud veebilehte ei ole kahjuks siin. |
Fail | Your page already boarded the failboat. |
Filipino | Wala na d’yan, ha? |
Finnish | Ehtimäsi sivvu ei löyvy ennää. |
Finnish (South Helsinki) | Ettimäsi sivu ei oo tääl enää. |
French | La page que vous avez demandée n’existe pas ou n’existe plus. |
French (ch’ti, a dialect spoken in northern France) | Ch’tio page que tisote a demandé l’est plus là. |
Frisian | De side dysto sikest , kin net fûn wurde. |
Galego | Non podo atopa-la páxina que andas a procurar. |
Galician | ¿U-la páxina? ¡Xa non está! |
Gascon (dialect of Occitan) | Qu’ei mort lo ligam, praubin, e n’i a pas mèi arrès a véder aquiu. |
Defcon Goons | Dog Balls Niner! |
Gents | Da paginatse keunde nie pakkeu. |
German | Das angeforderte Objekt existiert nicht auf diesem Server. |
German (Koelsch) | Dat Deil watt De hann wills, iss net do, wo De meens, dat et sin sullt. |
German (low) | Waut jie siejtje ess bloos nich too fingje. |
German (Pfaelzisch) | Ei horsche mo, die Sach isch ned do, kuksch alt woannaesch. |
German (Schwaebisch) | Die gsuchde Dadei isch nedd hir. Da mussch woanders gugge. |
German (Viennese dialect) | Des gibt’s wieda amoi ned. |
Glaswegian | Yur page izznae here. |
Greek | Den yparxi h selida re file, pos na to kanoume? |
Greek | Ma pou na einai auti i selida re fille? |
Grunnens | Sedel zuik! |
Haitian Creole | Mwen pa ka jwen paj ke w’ap cheche a. |
Hausa | Shafi da kuke nema ba ya nan. |
Hawaiʻian (attempted) | Hoʻokahua āʻole ke apuapu. |
Hawaiʻian (Creole) | Da page no stay! |
Hawaiʻian (pidgin) | ʻe bu, da bugga he no stae. |
Hebrew | Ha’amud Lo Nimtza. |
Hilarian | Nonie deang nfeos lieulnvnd ehoiw kdjoiutn nodiut ekoi detjoi kdj ewr tr aqo. |
Hindi | Woh panna jise tum khoj te ho, nahin mila. |
Hrvatski | ova stranica vi¹e nije dostupna! |
Hungarian slang (Budapest) | Haver, ez az izé nincs itt! |
Hungarian | A keresett lap vagy megszünt, vagy máshová került. |
Icelandic | Síðan sem ég var að leita að, er bara ekki hér. Af hverju ekki????? |
Icelandic | Umbeðin síða fannst ekki. |
Ilokano | Awan ditan. |
Indonesian | Halaman yang anda inginkan tidak ada dalam sistem ini. |
Irish | Níl an leathanach atá uait anseo. |
Italian (Naples slang) | Uhaaa! a paggin che stev cercann ‘ncopp a ibiblio.org nun c’ sta oppure l’hann levat’ e miezz. |
Italian (Pisticcese southern dialect) | A pagg’n ca stiev c’rcann sop a ibiblio.org non n’ge’ cchiu’, o non n’ge’ ma’ stat. |
Italian (Rome slang) | Ahooo! a paggina che stavi a cercà su ibiblio.org nun c’è più o l’hanno tolta. |
Italian (Sicilian slang) | Mizzica! A paggina ca stautu ciccannu nun c’e’ chiui o l’ana luvatu. |
Italian | L’URL che avete richiesto non e’ presente su questo server. |
Jamaican Patois | It no ded-deh! |
Japanese | 選択したURLは存在しません. |
Kannada (Indian) | Neevu hudukuthiruva file illi illa. |
Kiluhya | Olukaratasi lwokhabanga lubula. |
Kimtian | no so pageo couldos bea foundes hereas. chekas sum wear elsa pleasa. |
Kinyarwanda | Tubababarire, ibyo wifuzaga gusoma ntibishoboye kuboneka. |
Klingon | De’teywI’ ‘agh vonlu’. |
Korean | I peige reul mot chat get da ha o. |
Korean | 페이지가 존재하지 않습니다. |
Kölsch | De Sick, die De sööks jiddet he nit. |
Latin | Pagina quam tu quaeris abest. |
Latin (Pig) | Ethay agepay ouyay areyay ookinglay orfay annotcay ebay oundfay. |
Latvian | Lapa, ko meklējāt, neeksistē |
Limburgisch | De site daeste zeuks kènt neet gevónje waere! |
Linux User | That’s fixed in subversion |
Lithuanian | Negaliu rasti puslapio kurio jûs ieðkote. |
Lojban | Le pagbu poi do djica ke’a cu na se sanji. |
LOLcat | U cant haz page, is 404. Srsly. KTHXBAI! |
Lower Cardrossian | Achsno therbuh(t). |
Luxembourgish | Di Säit, déi Dir sicht, ka net fond gin. |
Mac User | This wouldn’t have happened if you were using a Mac. |
Macedonian | Stranicata ne e dostapna. |
Malagasy Madagascan | Tsy hita eto amin’ity sehatra ity io. |
Malagasy | Tsy hita ilay pejy nangatahinao. |
Malayalam | Ningal therayunna page ivide illa. |
Malaysian Iban | Apu! Nadai temu Web Page ti’ die’ giga nuan nya tadi! |
Malti | Dak li qed tfittex ma huhiex ghand dan is-server. Grazzi. |
Malti (slang) | għalxejn tgħarrex għax dok li trejd miex hawn |
Marathi | Aapan shodhat hotat, te pan sapadle nahi. |
Mauritian Creole | Page qui ou pe roder la pas exister ou nepli la! |
Mechels (Belgian dialect) | Dei bladzaa kunne we nie vinne zenne joeng. |
Mid-Michigan dairy farm colloquialism | That file is cow’s-legs-up. |
Mirpuri | Au jera barka tu lonán se itteh koneen e. |
Mongolian | chinii haij baisan huudas chin bayhguy yumuu baihaa bolhson baina. |
MopTalk | Tophope popagope yopou arope lopookopinopgop foporop isop nopotop hoperope |
Morse | ….- —— ….- ..-. .. .-.. . -. — — ..-. — ..- -. -.. |
Mäori | Käore kai könei te mea! It’s not here! |
Mäori | Vaed’rae! Ta krenn — ai hnhaudr ne hrrau etrehh. Jolan Tru! |
Nepali | Tapaile khojeko panna yaha chaina. |
Newfinease (Canadian dialect) | Lawrd tunderin jesus bye it tidin dere. |
Norwegian | Siden du leter etter er enten flyttet eller eksisterer ikke lenger. |
Nyaggitarri | Tiga obochinga! Risakara erio okare korigwa ntokaribwate nainde ndiri anda. |
Obi Wan | These are not the files you’re looking for. Move along. |
Opish | Tophe popage yopou opare lopookoping fopor opis nopot hopere. |
Papiamentu | E página ku bo ta buska no ta eksistí mas. |
Persian | Baba jan, in safeh inja nist digeh. |
Pirate | Haaarr, Lubber! I’ve sailed yon seas with toil and trial, and yet I cannot find ye file! |
Pittsburghese | This page needs fixed n’at… it’s all caddywhompus! Yinz needs look somewheres else. |
Polish | Takiej strony nie ma |
Portuguese (Portugal) | Essa página não existe. A última vez que a vi foi ali no bar da esquina. |
Portuguese Slang | Sócio, a página que tavas à procura fugiu, foi de boca… |
Properste Afrikaans | Die webblad deur u versoek is ongelukkig nie in voorraad nie. |
Punjabi | Barka aithe nehi Ha. |
Pushto (Afghanistan And Pakistan) | Da safha da kama chi taso malumavel guarai melao nashva. |
Quechua | Mana Caipipi Ni Imata Taripunichu. |
Québécois | ‘Sti man, la page eille pas là. |
Québécois (Angry) | Hey osti! La page que tu cherche est pas là ! Cherche pas tu la trouveras pas!! |
Rohingya (Burma) | Thuñí thuwoddé sáfa íba thuaifaa noóza. |
Romanian (Moldavian accent) | Pajinî pi cari o cãutaþi nu iesti aiºea. |
Romanian | Pagina pe care o cautati nu exista. |
Rot 13 | Reebe 404 cntr abg sbhaq. |
Russian | Stranitsa, kotoruyu vy ishete, byla peremeschena na drugoy sait. |
Russian | Нет такой страницы подумал Штирлиц. |
Rövarspråket | Sosidodanon dodu sosökokeror fofinonnonsos inontote lolänongogrore hohäror. Dodenon hoharor totrorololigogenon foflolytottotatot. |
Saarländisch | Mit der lo Seid gebädds huddel (404): Das Ding gibbted nid! |
Sanskrit | nAvaziSTaM tvad-iSTaM pRSTam. |
Sardinian | Impossíbbile agatare sa pàgina. |
Scots (Doric Dialect) | Fit page? We divnae hae it. |
Scottish Gaelic | Chan eil duilleag an seo a charaid. |
Sdrawkcab | Devom neeb sah ro gro.oilbibi no regnol no si rof gnikool era uoy egap eht spoo eua. |
Semaphore | see the animated gif here. |
Serbian (Diesel Slang) | E, brate, sharay malo! |
Serbian | Stranica koju trazite vise nije dostupna.— |
Sheng (Kiswahili Slang) | Manze Jo Hiyo page iko zi |
Shona (Zimbabwe) | Handisi kuwona peji yako. |
Sinhalese (Sri Lanka) | Oba Illum kala pituva soya genimata noheki viya. |
Slovak | Stranka, ktoru hladate je niekde uplne inde. Ak, pravda, este existuje. |
Slovenian | Strani, ki jo iščete, ni mogoče prikazati. |
Solomon Islands Pijin | Waswe? Hemme no stap moa. Hem go wae finis. |
Somali: | Waan ka xunahay bogan aad doonayso ma hayno bal si fiican ugu noqo oo markale sax. |
South Ostrohbotnian | Sidon jir int jär na mäir. |
Spanish | Ha intentado acceder a un recurso inexistente o que ha sido cambiado de lugar. |
Spanish | La página que estás buscando no existe. |
Spanish (Jeringozo, Argentinian Slang) | Lapa papagipinapa quepe espetapabaspa buspacanpandopo, nopo exispetepe maspa |
Spanish (Pachuco, Mexican-American Slang) | Chale, La page ya no sta ese. Me entiendes Mendes. |
Spanish (West Bolivian Slang) | ahurasito, la pagina ha pirdiu! |
Suavian | Die Seit konnt net gfonde werde. |
Suisse | Diä Siite gits nümm oder isch nöime anderscht. |
Surinamese | A papiera you soekoe, no de djaso. |
Svabian | Dia seita geids fei id! |
Swahili | Samahani, kurasa unayotafuta haipatikani kwa sasa. |
Swedish | Sidan du söker finns inte längre här … Den har troligen flyttat. |
Swedish (South Helsinki) | Siidan du söökkää e int hää meera. |
Swedish (chef) | De peege nöt hoerk! Børk børk børk! |
Süddeutsch | Döös isch net doo! |
Tagalog | Hindi ko makita ang pahinang hinanap mo dito. |
Tamil (Indian) | Neengal Thaedum Innaiya Pakkam Ingu Illai. |
Tech Support | Are you sure your computer is plugged in? |
Telegu (Indian) | Aa Filu Ikkada Ledu. |
Thai | Naa tee koon haa gor mai yuu leree. |
Tok Pisin (Neo-Melanesian Pidgin) | No ken painim pail i stap. |
Turkish | Aradiginiz sayfa bulunamadi. |
Turkish | Gardaþ sayfayý nereye kodüün? |
Ukrainian | Nemaye takoyi storinky! |
Ubbi Dubbi | Suborruby kubids, thubere’s nubo pubage lubike thubat. |
Ulster Scots | Ah cannae find it. yeu hae loast it. |
UNIX (V6) | |
Urdu | Yeah Sufha yuhan mojood nuheen hay. |
Vietnamese | Trang này dã bi xóa hay là dã bi chuyên di chô khác. |
Visayan/Cebuano | Wala na man ang file dinhi! Pero, naa’y og isda, ka-on nato! |
Waray | Waray man an ginbibiling mo. |
Webmaster (local dialect) | The problem is your fault. |
Welsh | Lle mae y dudalen? |
Welsh | Methwyd dod o hyd i’r ddogfen. |
Wookie | GRRRRRRRRRRRAAAAAAAAAHHHHHHH GRAAAAAAAAAA, HRAGGGGGGGGG!!! |
(C3PO) | Let the Wookie have his files! |
Yiddish | Dos vebzaytl vos ir zukht iz nishto. |
Zombie | Arrgrg 404 BrAiNs aAAArrggh No ggrrgrh page brAiNz heRe BrAAAAIIINNSSSS! |
Zulu Slang | Lento oyi funayo ayikho lana. |
Как использовать изменчивое ключевое слово C
Многие программисты плохо понимают правильное использование ключевого слова volatile языка C. Это неудивительно, так как большинство текстов на языке Си отклоняют volatile в одном-двух предложениях. Эта статья научит вас, как правильно использовать volatile.
Испытывали ли вы что-либо из следующего во встроенном коде C или C ++?
- Код, который работает нормально — пока вы не включите оптимизацию компилятора
- Код, который работает нормально — до разрешения прерываний
- Неустойчивые драйверы оборудования
- задач RTOS, которые отлично работают изолированно — до тех пор, пока не будет создана другая задача
Если вы ответили утвердительно на любой из вышеперечисленных вопросов, вероятно, вы не использовали ключевое слово C. volatile.Вы не одиноки: слишком многие программисты плохо понимают использование volatile.
[Правильное использование volatile требуется в соответствии со стандартом встроенного кодирования C, убивающим ошибки. Если вам понадобится помощь с нестабильностью, Barr Group предоставит услуги по обзору исходного кода C.]
Ключевое слово volatile в
C — это квалификатор, который применяется к переменной при ее объявлении. Он сообщает компилятору, что значение переменной может измениться в любое время — без каких-либо действий со стороны кода, который компилятор находит поблизости.Последствия этого могут быть довольно серьезными, и иногда эксперты по программному обеспечению дают свидетельские показания о сбоях продукта. Но прежде чем мы рассмотрим последствия, давайте посмотрим на синтаксис.
Синтаксис изменчивого ключевого слова языка Си
Чтобы объявить переменную volatile, включите ключевое слово volatile до или после типа данных в определение переменной. Например, в обоих этих объявлениях 16-разрядная целочисленная переменная без знака будет объявлена изменчивым целым числом:
изменчивый uint16_t x; uint16_t volatile y;
Итак, оказывается, что указатели на изменчивые переменные очень распространены, особенно с отображенными в память регистрами ввода-вывода.Оба этих объявления объявляют p_reg указателем на непостоянное 8-битное целое число без знака:
изменчивый uint8_t * p_reg; uint8_t volatile * p_reg;
Неустойчивые указатели на энергонезависимые данные очень редки (я думаю, что однажды использовал их), но я лучше дам вам синтаксис:
uint16_t * изменчивый p_x;
И для полноты картины, если вам действительно нужен изменчивый указатель на изменчивую переменную, вы должны написать:
uint16_t volatile * volatile p_y;
Между прочим, для прекрасного объяснения того, почему у вас есть выбор, где разместить volatile и почему вы должны размещать его после типа данных (например, int volatile * foo), прочтите столбец Дэна Сака «CV-квалификаторы верхнего уровня в Параметры функций »(Программирование встроенных систем, февраль 2000 г., стр.63).
Наконец, если вы примените volatile к структуре или объединению, все содержимое структуры или объединения будет изменчивым. Если вам не нужно такое поведение, вы можете применить квалификатор volatile к отдельным членам структуры или объединения.
Правильное использование изменчивого ключевого слова C.
Переменная должна быть объявлена изменчивой, если ее значение может неожиданно измениться. На практике могут изменяться только три типа переменных:
1. Регистры периферийных устройств с отображением памяти
2.Глобальные переменные, измененные программой обслуживания прерывания
3. Глобальные переменные, к которым имеют доступ несколько задач в многопоточном приложении
Мы поговорим о каждом из этих случаев в следующих разделах.
Периферийные регистры
Встроенные системы содержат реальное оборудование, обычно со сложной периферией. Эти периферийные устройства содержат регистры, значения которых могут изменяться асинхронно с ходом выполнения программы. В качестве очень простого примера рассмотрим 8-битный регистр состояния, отображаемый в памяти по адресу 0x1234.Требуется опросить регистр состояния, пока он не станет отличным от нуля. Наивная и неверная реализация выглядит следующим образом:
uint8_t * p_reg = (uint8_t *) 0x1234; // Подождите, пока регистр не прочитает ненулевое значение делать {...} в то время как (0 == * p_reg)
Этот код почти наверняка выйдет из строя, как только вы включите оптимизацию компилятора. Это потому, что компилятор сгенерирует язык ассемблера (здесь для 16-разрядного процессора x86), который выглядит примерно так:
mov p_reg, # 0x1234 mov a, @p_reg петля: ... bz петля
Смысл оптимизатора довольно прост: уже прочитав значение переменной в аккумулятор (во второй строке сборки), нет необходимости перечитывать его, так как значение будет (да!) Всегда будет одним и тем же. Таким образом, с третьей строки сборки мы входим в бесконечный цикл. Чтобы заставить компилятор делать то, что мы хотим, мы должны изменить объявление на:
uint8_t volatile * p_reg = (uint8_t volatile *) 0x1234;
Язык ассемблера теперь выглядит так:
mov p_reg, # 0x1234 петля: ... mov a, @p_reg bz петля
Таким образом достигается желаемое поведение.
Более тонкие виды ошибок имеют тенденцию возникать, когда доступ к регистрам со специальными свойствами осуществляется без объявления volatile. Например, многие периферийные устройства содержат регистры, которые очищаются просто путем их чтения. Дополнительные (или меньшие) чтения, чем вы предполагаете, в этих случаях могут привести к весьма неожиданному поведению.
Процедуры обслуживания прерывания
Процедуры обслуживания прерывания часто устанавливают переменные, которые тестируются в основном коде.Например, прерывание последовательного порта может проверять каждый полученный символ, чтобы узнать, является ли он символом ETX (предположительно, обозначающим конец сообщения). Если символ является ETX, ISR может установить глобальный флаг. Неправильная реализация этого может быть:
bool gb_etx_found = false; пустая функция() { ... пока (! gb_etx_found) { // Ждать } ... } прерывание void rx_isr (недействительно) { ... если (ETX == rx_char) { gb_etx_found = правда; } ... }
[ПРИМЕЧАНИЕ: мы не поддерживаем использование глобальных переменных; в этом коде используется один, чтобы пример был кратким / понятным.]
Эта программа может работать с отключенной оптимизацией компилятора. Однако любой полуприличный оптимизатор «сломает» программу. Проблема в том, что компилятор не знает, что gb_etx_found можно изменить в функции ISR, которая, похоже, никогда не вызывается.
Что касается компилятора, выражение! Gb_ext_found будет иметь один и тот же результат каждый раз при прохождении цикла, и поэтому вы никогда не должны выходить из цикла while.Следовательно, весь код после цикла while может быть просто удален оптимизатором. Если вам повезет, ваш компилятор предупредит вас об этом. Если вам не повезло (или вы еще не научились серьезно относиться к предупреждениям компилятора), ваш код потерпит неудачу. Естественно, вина будет на «паршивом оптимизаторе».
Решение состоит в том, чтобы объявить переменную gb_etx_found изменчивой. После чего эта программа будет работать так, как вы задумали.
Многопоточные приложения
Несмотря на наличие очередей, каналов и других механизмов связи, поддерживающих планировщик, в операционных системах реального времени, все же возможно, что задачи RTOS будут обмениваться информацией через общую область памяти (т.е., глобальное хранилище). Когда вы добавляете в код упреждающий планировщик, ваш компилятор не знает, что такое переключение контекста и когда оно может произойти. Таким образом, задача асинхронного изменения совместно используемого глобала концептуально такая же, как и описанный выше сценарий ISR. Таким образом, все общие глобальные объекты (переменные, буферы памяти, аппаратные регистры и т. Д.) Также должны быть объявлены энергозависимыми, чтобы оптимизация компилятора не привела к неожиданному поведению. Например, этот код вызывает проблемы:
uint8_t gn_bluetask_runs = 0; void red_task (недействителен) { в то время как (4Эта программа, скорее всего, завершится ошибкой после включения оптимизатора компилятора. Объявление gn_bluetask_runs с volatile - правильный способ решить проблему.
[ПРИМЕЧАНИЕ: мы не поддерживаем использование глобальных переменных; этот код использует глобальную переменную, потому что он объясняет связь между изменчивыми и глобальными переменными.]
[ВНИМАНИЕ: глобальные переменные, совместно используемые задачами и обработчиками прерывания, также необходимо защитить от состояний гонки, например мьютексом.]
Последние мысли
Некоторые компиляторы позволяют неявно объявлять все переменные как изменчивые. Сопротивляйтесь этому искушению, поскольку он, по сути, заменяет мысль. Это также приводит к потенциально менее эффективному коду.
Также не поддавайтесь искушению обвинить оптимизатор или выключить его, когда вы столкнетесь с неожиданным поведением программы.Современные оптимизаторы C / C ++ настолько хороши, что я не могу вспомнить, когда в последний раз сталкивался с ошибкой оптимизации. Напротив, я регулярно сталкиваюсь с отказами программистов при использовании volatile.
Если вам дали «исправить» фрагмент нестабильного кода, выполните команду grep для volatile. Если grep оказывается пустым, приведенные здесь примеры, вероятно, являются хорошей отправной точкой для поиска проблем.
Эта статья была опубликована в июльском выпуске журнала Embedded Systems Programming за 2001 год. Если вы хотите процитировать статью в своей работе, вам может быть полезна следующая информация в стиле MLA:
Джонс, Найджел."Введение в изменчивое ключевое слово" Программирование встроенных систем, июль 2001 г.
Связанные ресурсы
Связанные курсы Barr Group
Полный список курсов Barr Group можно найти в нашем каталоге курсов.
декларация - Зачем нужен volatile в C?
volatile
сообщает компилятору не оптимизировать ничего, что связано с переменнойvolatile
.Есть по крайней мере три общие причины для его использования, все из которых связаны с ситуациями, когда значение переменной может измениться без каких-либо действий со стороны видимого кода: когда вы взаимодействуете с оборудованием, которое изменяет само значение; когда запущен другой поток, который также использует переменную; или когда есть обработчик сигнала, который может изменить значение переменной.
Допустим, у вас есть небольшая часть оборудования, которая где-то отображается в ОЗУ и имеет два адреса: командный порт и порт данных:
структура typedef { int command; данные int; int isBusy; } MyHardwareGadget;
Теперь вы хотите отправить команду:
void SendCommand (MyHardwareGadget * гаджет, команда int, данные int) { // ждем, пока гаджет занят: пока (гаджет-> занят) { // здесь ничего не делаем. } // сначала устанавливаем данные: гаджет-> данные = данные; // запись команды запускает действие: гаджет-> команда = команда; }
Выглядит просто, но может дать сбой, потому что компилятор может изменить порядок, в котором записываются данные и команды.Это заставит наш маленький гаджет выдавать команды с предыдущим значением данных. Также обратите внимание на цикл ожидания при занятости. Этот будет оптимизирован. Компилятор постарается быть умным, прочитает значение
isBusy
только один раз, а затем войдет в бесконечный цикл. Это не то, что вам нужно.Способ обойти это - объявить гаджет указателя
как
volatile
. Таким образом, компилятор вынужден делать то, что вы написали. Он не может удалить назначения памяти, он не может кэшировать переменные в регистрах и не может изменить порядок назначенийЭто правильная версия:
void SendCommand (изменчивый гаджет MyHardwareGadget *, команда int, данные int) { // ждем, пока гаджет занят: пока (гаджет-> isBusy) { // здесь ничего не делаем.} // сначала устанавливаем данные: гаджет-> данные = данные; // запись команды запускает действие: гаджет-> команда = команда; }
Квалификатор изменчивого типа
- cppreference.com
Каждый отдельный тип в системе типов C имеет несколько квалифицированных версий этого типа, соответствующих одной, двум или всем трем из констант, volatile и, для указателей на типы объектов, квалификаторам ограничения. На этой странице описываются эффекты квалификатора volatile .
Каждый доступ (как чтение, так и запись), выполняемый через выражение lvalue типа volatile-квалифицированный, считается наблюдаемым побочным эффектом с целью оптимизации и оценивается строго в соответствии с правилами абстрактной машины (то есть все записи являются завершено за некоторое время до следующей точки последовательности). Это означает, что в пределах одного потока выполнения изменчивый доступ не может быть оптимизирован или переупорядочен относительно другого видимого побочного эффекта, который отделен точкой последовательности от изменчивого доступа.
Преобразование энергонезависимого значения в изменчивый тип не имеет никакого эффекта. Чтобы получить доступ к энергонезависимому объекту с использованием семантики volatile, его адрес должен быть преобразован в указатель на volatile, а затем доступ должен быть осуществлен через этот указатель.
Любая попытка чтения или записи в объект, тип которого является изменчивым с помощью энергонезависимого lvalue, приводит к неопределенному поведению:
volatile int n = 1; // объект с изменяемым типом int * p = (int *) & n; int val = * p; // неопределенное поведениеЧлен изменчивой структуры или типа объединения получает квалификацию типа, к которому он принадлежит (оба при доступе с использованием
.Оператор
или оператор->
):struct s {int i; const int ci; } s; // тип s.i - int, тип s.ci - const int volatile struct s vs; // типы vs.i и vs.ci - volatile int и const volatile int
Если тип массива объявлен с квалификатором volatile типа (посредством использования typedef), тип массива не квалифицируется как volatile, а его тип элемента -.
(до C23) Тип массива и тип его элемента всегда считаются одинаково квалифицированными как изменчивые.
(начиная с C23) Если тип функции объявлен с квалифицированным изменчивым типом (с использованием typedef), поведение не определено.
typedef int A [2] [3]; летучий A a = {{4, 5, 6}, {7, 8, 9}}; // массив массива volatile int int * pi = a [0]; // Ошибка: a [0] имеет тип volatile int * void * unqual_ptr = a; // ОК до C23; ошибка с C23 // Примечания: clang применяет правило в C ++ / C23 даже в режимах C89-C17
В объявлении функции ключевое слово
volatile
может появиться внутри квадратных скобок, которые используются для объявления типа массива параметра функции.Он определяет тип указателя, в который преобразуется тип массива.Следующие два объявления объявляют одну и ту же функцию:
void f (double x [volatile], const double y [volatile]); void f (double * volatile x, const double * volatile y);(начиная с C99) Указатель на энергонезависимый тип может быть неявно преобразован в указатель на версию того же или совместимого типа с указанием на энергозависимость. Обратное преобразование может быть выполнено с помощью выражения приведения.
int * p = 0; изменчивый int * vp = p; // ОК: добавляет квалификаторы (int в volatile int) p = vp; // Ошибка: отбрасывает квалификаторы (от volatile int к int) p = (int *) vp; // ОК: приведениеОбратите внимание, что указатель на указатель на
T
не может быть преобразован в указатель на указатель наvolatile T
; чтобы два типа были совместимы, их квалификация должна быть идентична:char * p = 0; изменчивый символ ** vpp = & p; // Ошибка: char * и volatile char * несовместимы char * volatile * pvp = & p; // ОК, добавляем квалификаторы (char * в char * volatile)[править] Использование нестабильного
1) static
volatile объекты
моделируют отображаемые в память порты ввода-вывода, аstatic
const
volatile
objects моделируют отображаемые в память входные порты, такие как часы реального времени:volatile short * ttyport = (volatile short *) TTYPORT_ADDR; для (int i = 0; i2)
static
volatile
объектов типа sig_atomic_t используются для связи с обработчиками сигналов.3)volatile
переменных, которые являются локальными для функции, содержащей вызов макроса setjmp, являются единственными локальными переменными, которые гарантированно сохранят свои значения после возврата longjmp.4) Кроме того, изменчивые переменные могут использоваться для отключения определенных форм оптимизации, например чтобы отключить устранение мертвого накопителя или постоянное сворачивание для микротестов.
Обратите внимание, что изменчивые переменные не подходят для обмена данными между потоками; они не предлагают атомарность, синхронизацию или упорядочение памяти.Чтение из изменчивой переменной, которая изменяется другим потоком без синхронизации или одновременного изменения из двух несинхронизированных потоков, является неопределенным поведением из-за гонки данных.
[править] Ключевые слова
летучий
[править] Пример
демонстрирует использование volatile для отключения оптимизаций
#include#include int main (пусто) { clock_t t = часы (); двойной d = 0,0; для (int n = 0; n <10000; ++ n) для (int m = 0; m <10000; ++ m) д + = д * п * м; // читает и записывает в энергонезависимую printf ("Изменена энергонезависимая переменная 100 миллионов раз." "Использованное время:% .2f секунд \ n", (двойной) (часы () - t) / CLOCKS_PER_SEC); t = часы (); летучий двойной vd = 0,0; для (int n = 0; n <10000; ++ n) for (int m = 0; m <10000; ++ m) { двойной прод = vd * n * m; // читает из изменчивой vd + = прод; // читает и записывает в изменчивую } printf ("Изменена изменчивая переменная 100 миллионов раз." "Использованное время:% .2f секунд \ n", (двойной) (часы () - t) / CLOCKS_PER_SEC); } Возможный выход:
Изменена энергонезависимая переменная 100м раз.Использованное время: 0,00 секунды Модифицировал изменчивую переменную 100м раз. Использованное время: 0,79 секунды[править] Ссылки
- C17 стандарт (ISO / IEC 9899: 2018):
- 6.7.3 Квалификаторы типа (стр. 87-90)
- Стандарт C11 (ISO / IEC 9899: 2011):
- 6.7.3 Квалификаторы типов (стр. 121-123)
- Стандарт C99 (ISO / IEC 9899: 1999):
- 6.7.3 Квалификаторы типов (стр: 108-110)
- Стандарт C89 / C90 (ISO / IEC 9899: 1990):
[править] См. Также
Язык C - Летучие переменные
Пример
Ключевое слово
volatile
сообщает компилятору, что значение переменной может измениться в любое время в результате внешних условий, а не только в результате потока управления программой.Компилятор не оптимизирует ничего, что связано с изменчивой переменной.
volatile int foo; / * Различные способы объявления изменчивой переменной * / int volatile foo; изменчивый uint8_t * pReg; / * Указатели на изменчивую переменную * / uint8_t volatile * pReg;
Есть две основные причины использовать изменчивые переменные:
- Для взаимодействия с оборудованием, имеющим отображенные в память регистры ввода-вывода.
- При использовании переменных, которые изменяются вне потока управления программой (например, в программе обслуживания прерывания)
Давайте посмотрим на этот пример:
int quit = false; пустая функция() { ... while (! quit) { // Делаем что-нибудь, что не меняет переменную выхода } ... } void interrupt_handler (недействительно) { quit = true; }
Компилятору разрешено заметить, что цикл while не изменяет переменную
quit
и не преобразует цикл в бесконечный циклwhile (true)
. Даже если переменнаяquit
установлена в обработчике сигналов дляSIGINT
иSIGTERM
, компилятор этого не знает.Объявление
quit
какvolatile
укажет компилятору не оптимизировать цикл, и проблема будет решена.Та же проблема возникает при доступе к оборудованию, как мы видим в этом примере:
uint8_t * pReg = (uint8_t *) 0x1717; // Ждем, пока регистр станет ненулевым while (* pReg == 0) {} // Сделаем что-нибудь еще
Оптимизатор выполняет однократное считывание значения переменной, повторное считывание не требуется, поскольку значение всегда будет одним и тем же.Таким образом, мы получаем бесконечный цикл. Чтобы заставить компилятор делать то, что мы хотим, мы изменяем объявление на:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;
Использование ключевого слова Volatile во встроенном ПО
Неправильное или неиспользованное ключевое слово volatile в коде C является частым источником ошибок во встроенном программном обеспечении в реальном времени.
Основная идея volatile заключается в том, что он сообщает компилятору, что переменная, помеченная как volatile, может быть изменена «в любое время» и событиями, внешними по отношению к компилируемой функции / файлу.
Это имеет особое значение при использовании оптимизации компилятора.
Пример 1 - Аппаратные регистры устройства.
Одним из наиболее ярких примеров, когда требуется энергозависимость, является доступ к регистрам аппаратного устройства, отображаемым в память. В приведенном ниже простом примере показана часть вымышленного «драйвера» контроллера DMA.
Предположим, что контроллер DMA имеет набор регистров, включающий регистр команды, регистр состояния, адрес источника, адрес назначения и счетчик. Чтобы осуществить передачу DMA, программист должен загрузить регистры источника, назначения и счетчика, а затем записать 1 в регистр cmd, чтобы сообщить контроллеру о необходимости выполнения передачи. Когда передача будет завершена, устройство установит значение 1 в регистр состояния.
Вот небольшой код, иллюстрирующий проблему:
/ * Структура, представляющая набор регистров устройства DMA * / struct { беззнаковый длинный cmd; беззнаковый длинный статус; беззнаковый длинный исходный_адрес; беззнаковый длинный целевой_адрес; беззнаковый длинный счетчик; } изменчивый dma_dev_regs_t; / * Аппаратный базовый адрес набора регистров * / #define DEVICE_BASE_ADDRESS 0x7f004000 / * Функция делать dma.Параметры: исходный начальный адрес данных для перемещения dest начальный адрес назначения подсчитать количество байтов данных для перемещения * / void do_dma (unsigned long source, unsigned long dest, int count) { dma_dev_regs_t * pRegs = DEVICE_BASE_ADDRESS; pRegs-> status = 0; pRegs-> адрес_источника = источник; pRegs-> destination_address = dest; pRegs-> count = count; pRegs-> cmd = 1; в то время как (pRegs-> status == 0) {} }В этом простом примере, если ключевое слово volatile было пропущено, а код был скомпилирован без оптимизации, функция может работать правильно.В частности, оператор while (pRegs-> status == 0) {} будет работать так, как ожидалось - непрерывно опрашивать регистр состояния, считывая физический регистр по адресу 0x7f004004, пока регистр не будет содержать ненулевое значение.
С другой стороны, если бы код был скомпилирован с включенной оптимизацией, код, сгенерированный компилятором, скорее всего, не опрашивал бы регистр состояния. Компилятор знает, что строка pRegs-> status = 0 устанавливает значение статуса в 0, и он «знает», что pRegs-> status не был снова установлен в подпрограмме.Следовательно, компилятор определит, что нет необходимости повторно считывать регистр состояния в цикле while. Скорее всего, результатом будет бесконечный цикл, как в «while (1) {}»
Ключевое слово volatile предупреждает компилятор о том, что он должен включить чтение регистра состояния в цикл while, поскольку значение может изменяться вне этой подпрограммы.
Пример 2 - Программа обслуживания прерывания
Аналогичный пример может иметь место, когда на картинке присутствует процедура обслуживания прерывания (ISR).
Используя вымышленный контроллер DMA из приведенного выше примера, теперь предположим, что контроллер DMA генерирует прерывание, когда DMA завершается.
/ * Структура, представляющая набор регистров устройства DMA * / typedef struct { беззнаковый длинный cmd; беззнаковый длинный статус; беззнаковый длинный исходный_адрес; беззнаковый длинный destication_address; беззнаковый длинный счетчик; } изменчивый dma_dev_regs_t; / * Аппаратный базовый адрес набора регистров * / #define DEVICE_BASE_ADDRESS 0x7f004000 / * глобальный флаг указывает, что dma выполнено * / volatile int dma_done_flag; / * Вызывается процедура обслуживания прерывания контроллера DMA когда контроллер DMA завершает операцию DMA.* / void dma_isr () { dma_done_flag = 1; } / * Функция делать dma. Параметры: исходный начальный адрес данных для перемещения dest начальный адрес назначения подсчитать количество байтов данных для перемещения * / void do_dma (unsigned long source, unsigned long dest, int count) { dma_done_flag = 0; dma_dev_regs_t * pRegs = DEVICE_BASE_ADDRESS; pRegs-> адрес_источника = источник; pRegs-> destination_address = dest; pRegs-> count = count; pRegs-> cmd = 1; пока (dma_done_flag == 0) {} }Как и в первом примере, если ключевое слово volatile было опущено для dma_done_flag, оптимизатор компилятора удалил бы чтение dma_done_flag в цикле while.Разница здесь в том, что вместо аппаратного регистра у нас есть ячейка памяти, которая изменяется из-за подпрограммы обслуживания прерывания, которая запускается действием оборудования.
Пример 3 - Несколько потоков
Подобная ситуация может возникнуть в многозадачной или многопоточной среде. Это проиллюстрировано на следующем простом примере:
/ * Глобальная переменная счетчика * / счетчик volatile int; / * Флаги включения задач * / изменчивый int task_1_enable = 0 изменчивый int task_2_enable = 0 / * Задача 2 ожидает счетчика перед выполнением другие действия * / void task_2 (void) { пока (task_2_enable == 0) {} while (counter <100) {} / * делаем то, что нужно для задачи 2 * / ... } / * Задача 1 увеличивает счетчик в цикле * / void task_1 (void) { пока (task_1_enable == 0) {} task_2_enable = 1; for (int i = 0; i <= 100; i ++) { счетчик ++; сон (10) } } / * основной распорядок * / int main () { счетчик = 0; / * запускаем глобальный счетчик * / task_1_enable = 1; / * запускаем задачу 1 * / }В этом примере все три счетчика глобальных переменных, task_1_enable и task_2_enable, должны быть помечены как изменчивые, иначе оптимизатор компилятора предотвратит выполнение программы должным образом.
Обратите внимание, что приведенные выше фрагменты кода предназначены для иллюстрации обсуждаемого вопроса и очень упрощены для этой цели. Часто ситуации, проиллюстрированные выше, могут присутствовать в сложном программном обеспечении, и это может быть неочевидно. Тщательное изучение кода при обдумывании каждого из вышеперечисленных сценариев может выявить проблему.
Ключевым индикатором того, что может быть проблема с использованием ключевого слова volatile, является то, что программное обеспечение будет работать правильно, когда оптимизация компилятора отключена, но не работать, когда оптимизация включена.
Однако также важно ограничить использование ключевого слова volatile ситуациями, когда оно действительно необходимо. Использование volatile для переменных, которые на самом деле не нужны, приведет к снижению производительности, потому что оптимизатор будет ограничен в выполнении наилучшей работы при оптимизации кода.
Свяжитесь с нами, чтобы получить помощь по проектам встраиваемых систем!
Использование ключевого слова «Volatile» в Embedded C
Представьте, что вы проходите собеседование по поводу работы и.Интервьюер дает вам следующий код и просит вас найти проблему [1]:
void IO_WaitForRegChange (unsigned int * reg, unsigned int bitmask)
{
unsigned int orig = * reg & bitmask;
while (orig == (* reg & bitmask)) {/ * do noting * /;}
}
Вы можете сказать, что «нет заголовка комментария для документации» или указать, «почему мы должны ждать изменение регистра и ничего не делать без надлежащей обработки ошибок и тайм-аута ».Все это правда, но все же вы упускаете важную часть. Если вы не укажете важную вещь, интервьюируемый может добавить, что функция никогда не возвращается, несмотря на изменения регистров, отслеживаемые осциллографом, и что код компилируется с включенной оптимизацией.
Итак, в чем проблема?
Регистр изменился, но почему наш код не работал?
Почему интервьюируемое было важно упомянуть оптимизацию?
Имеет ли здесь значение выключенная или включенная оптимизация?
Мы еще вернемся к этому вопросу, но сначала давайте посмотрим, что такое «изменчивый» в C и почему он важен для Embedded C.
В языке C, Volatile, является квалификатором переменной. Создавая переменную, изменчивую переменную, мы сообщаем компилятору, что:
«значение этой переменной может измениться из-за некоторых внешних источников, которые вы можете не видеть поблизости. Поэтому, пожалуйста, не оптимизируйте его, а перечитывайте каждый раз, когда вам понадобится его значение. «
, и это действительно полезно и важно для Embedded C, поскольку мы ежедневно работаем с оборудованием, регистрами и прерываниями.
У нас есть процедура обслуживания прерываний (ISR), в которой мы можем изменить значение переменной, если произошло какое-то прерывание.Например, прерывания от таймера широко используются.
Это может быть случай регистров карты памяти, значение которых может изменяться каким-то оборудованием или каким-либо портом ввода-вывода.
Возможно, у вас многопоточное приложение, и значение вашей глобальной переменной может измениться в другом потоке.
Во всех вышеперечисленных важных случаях изменение значения определенной переменной может быть выполнено не близким кодом, а некоторыми внешними силами.Поэтому важно объявить их как переменные Volatile и сообщить компилятору о ситуации.
Синтаксис использования Volatile:
Чтобы объявить изменчивую переменную, вам просто нужно добавить квалификатор к объявлению.
volatile int reg;
int volatile reg;
Как и следовало ожидать во Embedded C, указатели на изменчивую переменную являются общими:
volatile uint8_t * pReg;
uint8_t volatile * pReg;
«энергозависимый указатель на переменную» и «энергозависимый указатель на энергозависимую переменную» также возможны и существуют, но вы можете их использовать редко.
int * volatile p; / * изменчивый указатель на целое число * /
int volatile * volatile p;
/ * изменчивый указатель на переменное целое число * /
Давайте еще раз внимательно рассмотрим вопрос интервью. Мы пытаемся прочитать регистр и ждем изменения его значения. Но поскольку оптимизация включена, и мы не сказали компилятору, что значение reg может измениться где-то еще, чего вы не видите, компилятор срежет какой-то угол, чтобы сэкономить место или сделать что-то, чтобы увеличить speed и в основном не будет читать значение регистра каждый раз в нашем цикле while.
Почему? потому что он не нашел причины, потому что мы не сказали ему.
Следовательно, чтобы ответить на вопрос интервью, нам не хватает «изменчивой» клавиатуры в объявлении переменной для «reg».
Но что, если мы отключим оптимизацию? Что бы случилось иначе?
Без оптимизации код интервью должен нормально работать даже без ключевого слова Volatile. Потому что компилятор не будет пытаться оптимизировать скорость или пространство, а в основном будет каждый раз читать значение из памяти.Но даже если оптимизация отключена, а вы намеренно отключили ее, пропуск ключевого слова Volatile для такой переменной считается плохой практикой. Это добавляет удобочитаемости вашему коду, так что в будущем вы или кто-то еще будет знать, что происходит, увидев переменную «Volatile». Кроме того, это сэкономит вам много времени, если вы вдруг решите включить оптимизацию позже.
Итак, не забудьте использовать ключевое слово «Volatile», когда это необходимо, независимо от того, включена ли оптимизация или нет.
Я также рекомендую посмотреть эти два коротких видео на YouTube.
Чтобы узнать больше о том, что происходит в фоновом режиме внутри компилятора, см .:
https://www.youtube.com/watch?v=W3pFxSBkeJ8
И увидеть разницу между включением и выключением оптимизации на практике , посмотрите это:
https://www.youtube.com/watch?v=ImHW6I0BoEs
Знание причин и фонового поведения определенных вещей поможет вам и сэкономит вам много времени в будущем в вашем встроенном карьеры. Итак, как всегда, я призываю вас спрашивать ПОЧЕМУ и искать фоновое поведение, когда вы пытаетесь изучить новые концепции Embedded C.Задавая более точные вопросы, вы сможете найти лучшие ответы.
Ссылки:
[1] White, E. (2011). Создание встроенных систем . О'РЕЙЛИ, стр.108.
------------------------------------------------ -------------------------------------
Если вы найдете эту статью полезной, поделитесь ею, чтобы ваши друзья также могут получить пользу. Было бы приятно иметь вас в качестве подписчика, поэтому не забудьте подписаться на информационный бюллетень, чтобы оставаться в топе моих избранных сообщений в блоге каждый месяц.
Когда осуществляется доступ к изменчивому объекту?
И стандарты C, и C ++ имеют понятие изменчивых объектов. Эти
обычно доступны с помощью указателей и используются для доступа к оборудованию. В
стандарты поощряют компиляторы воздерживаться от оптимизации
относительно доступа к изменчивым объектам, которые он может выполнять на
энергонезависимые объекты. Стандарт C оставляет его реализацию определенной
относительно того, что представляет собой изменчивый доступ. Стандарт C ++ не учитывает
укажите это, за исключением того, что C ++ должен вести себя аналогичным образом
до C в отношении летучих, где это возможно.Минимум либо
стандарт указывает, что в точке последовательности все предыдущие обращения к
летучие объекты стабилизировались, и никакие последующие обращения не
произошел. Таким образом, реализация может свободно переупорядочивать и комбинировать
непостоянные обращения, которые происходят между точками последовательности, но не могут этого сделать
для доступа через точку следования. Использование летучих веществ не
позволяют многократно нарушать ограничение на обновление объектов
в точке следования.В большинстве выражений интуитивно очевидно, что является считыванием, а что -
написать.Например
volatile int * dst = somevalue; изменчивый int * src = someothervalue; * dst = * src;вызовет чтение изменчивого объекта, на который указывает src, и сохранит
значение в изменчивый объект, на который указывает dst. Здесь нет
гарантировать, что эти операции чтения и записи являются атомарными, особенно для объектов.
больше, чем int.Менее очевидные выражения - это когда что-то похожее на доступ
используется в пустом контексте.Например,
volatile int * src = somevalue; * src;В C такие выражения являются значениями r, а в качестве значений r вызывают чтение
объект, GCC интерпретирует это как чтение указателя volatile
к. Стандарт C ++ указывает, что такие выражения не подвергаются
lvalue в rvalue преобразование, и что тип разыменованного
объект может быть неполным. Стандарт C ++ не определяет явно
что именно это преобразование lvalue в rvalue отвечает за
вызывая доступ.Однако есть основания полагать, что это так,
потому что в противном случае некоторые простые выражения становятся неопределенными. Тем не мение,
поскольку это удивило бы большинство программистов, G ++ рассматривает разыменование
указатель на изменчивый объект полного типа в пустом контексте как чтение
объекта.