Почему в C требуется летучесть?

Почему volatile требуется в C? Для чего его используют? Что он будет делать?

Ответ 1

Volatile говорит компилятору не оптимизировать ничего, что связано с переменной volatile.

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

Допустим, у вас есть небольшая часть оборудования, которая где-то отображается в ОЗУ и имеет два адреса: порт команды и порт данных:

typedef struct
{
  int command;
  int data;
  int isbusy;
} MyHardwareGadget;

Теперь вы хотите отправить какую-то команду:

void SendCommand (MyHardwareGadget * gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isbusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

Выглядит просто, но может не получиться, потому что компилятор может свободно изменять порядок записи данных и команд. Это заставит наш маленький гаджет выдавать команды с предыдущим значением данных. Также взгляните на ожидание при занятом цикле. Этот будет оптимизирован. Компилятор попытается быть умным, прочитает значение isbusy только один раз и затем перейдет в бесконечный цикл. Это не то, что вы хотите.

Чтобы обойти это, нужно объявить гаджет-указатель как volatile. Таким образом, компилятор вынужден делать то, что вы написали. Он не может удалить назначения памяти, он не может кэшировать переменные в регистрах и не может изменить порядок назначений:

Это правильная версия:

   void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }

Ответ 2

volatile в C фактически появился для того, чтобы не кэшировать значения переменной автоматически. Он скажет компилятору не кэшировать значение этой переменной. Таким образом, он будет генерировать код для получения значения заданной переменной volatile из основной памяти каждый раз, когда встречается с ней. Этот механизм используется потому, что в любое время значение может быть изменено ОС или любым прерыванием. Поэтому использование volatile поможет нам каждый раз получать доступ к значению заново.

Ответ 3

Другое использование для volatile - обработчики сигналов. Если у вас есть такой код:

int quit = 0;
while (!quit)
{
    /* very small loop which is completely visible to the compiler */
}

Компилятору разрешено замечать, что тело цикла не касается переменной quit, и преобразовывать цикл в цикл while (true). Даже если переменная quit установлена в обработчике сигналов для SIGINT и SIGTERM; компилятор не может этого знать.

Однако, если переменная quit объявлена как volatile, компилятор вынужден загружать ее каждый раз, потому что она может быть изменена в другом месте. Это именно то, что вы хотите в этой ситуации.

Ответ 4

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

Ответ 5

См. эту статью Андрея Александреску, " volatile - Лучший друг с несколькими программистами

Ключевое слово volatileразработан для предотвращения компиляции оптимизация, которая может отображать код неверно в присутствии определенных асинхронные события. Например, если вы объявляете примитивную переменную как volatile, компилятор не разрешено кэшировать его в реестре - общая оптимизация, которая была бы катастрофически, если эта переменная была разделяемый между несколькими потоками. Итак общее правило: если у вас есть переменные примитивного типа, который должен быть общим среди нескольких потоков, объявите эти переменные volatile. Но вы можете на самом деле делают гораздо больше с этим ключевое слово: вы можете использовать его, чтобы поймать код который не является потокобезопасным, и вы можете делать это во время компиляции. Эта статья показывает, как это делается; решение включает в себя простой умный указатель, который также упрощает сериализацию критические разделы кода.

Статья применяется как к C, так и к C++.

Также см. статью " С++ и опасения блокировки с двойным проверкой" Скотта Мейерса и Андрея Александреску:

Таким образом, при работе с некоторыми ячейками памяти (например, с отображенными в памяти портами или памятью, на которые ссылаются ISR [Процедуры обслуживания прерываний]), некоторые оптимизации должны быть приостановлены. (1) содержание изменчивой переменной "неустойчиво" (может быть изменено неизвестным компилятору способом), (2) все записи в изменчивые данные "наблюдаемы", поэтому они должны выполняться религиозно, и (3) все операции с изменчивыми данными выполняются в последовательности, в которой они отображаются в исходном коде. Первые два правила обеспечивают правильное чтение и письмо. Последний позволяет реализовать протоколы ввода/вывода, которые смешивают ввод и вывод. Это неофициально то, что нестабильные гарантии C и С++.

Ответ 6

Мое простое объяснение:

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

Например:

bool usb_interface_flag = 0;
while(usb_interface_flag == 0)
{
    // execute logic for the scenario where the USB isn't connected 
}

Из приведенного выше кода компилятор может думать, что usb_interface_flag определяется как 0, а в цикле while он будет равен нулю навсегда. После оптимизации компилятор будет обрабатывать его как while(true) все время, что приведет к бесконечному циклу.

Чтобы избежать подобных сценариев, мы объявляем флаг изменчивым, мы сообщаем компилятору, что это значение может быть изменено внешним интерфейсом или другим модулем программы, т.е. не оптимизировать его. Это вариант использования для волатильности.

Ответ 7

Маржинальное использование для неустойчивого заключается в следующем. Скажем, вы хотите вычислить численную производную функции f:

double der_f(double x)
{
    static const double h = 1e-3;
    return (f(x + h) - f(x)) / h;
}

Проблема заключается в том, что x+h-x обычно не равно h из-за ошибок округления. Подумайте об этом: когда вы вычитаете очень близкие цифры, вы теряете много значительных цифр, которые могут испортить вычисление производной (подумайте 1.00001 - 1). Возможным обходным решением может быть

double der_f2(double x)
{
    static const double h = 1e-3;
    double hh = x + h - x;
    return (f(x + hh) - f(x)) / hh;
}

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

    volatile double hh = x + h;
    hh -= x;

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

Ответ 8

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

  • Компилятор не будет оптимизировать функции, которые используют переменные, которые определены с помощью volatile keyword

  • Volatile используется для доступа к точным ячейкам памяти в ОЗУ, ПЗУ и т.д. Это чаще используется для управления устройствами с отображением памяти, доступа к регистрам ЦП и определения местоположения конкретной ячейки памяти.

См. примеры с листингом сборки. Re: Использование C "volatile" ключевого слова в Embedded Development

Ответ 9

Volatile также полезен, если вы хотите заставить компилятор не оптимизировать определенную последовательность кода (например, для записи микро-теста).

Ответ 10

Я расскажу о другом сценарии, в котором важны летучие.

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

Если вы получаете доступ к файлам данных с отображением памяти через указатели на энергонезависимые объекты (на уровне исходного кода), тогда код, сгенерированный компилятором, может извлекать одни и те же данные несколько раз, не зная об этом.

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

Если вы заботитесь о безопасности, и вы должны, это важный сценарий для рассмотрения.

Ответ 11

volatile означает, что хранилище, вероятно, изменится в любое время и будет изменено, но что-то вне контроля пользовательской программы. Это означает, что если вы ссылаетесь на переменную, программа всегда должна проверять физический адрес (т.е. Отображаемый ввод fifo) и не использовать его кэшированным способом.

Ответ 13

На мой взгляд, вы не должны ожидать слишком многого от volatile. Чтобы проиллюстрировать это, посмотрите на Нилс Пипенбринк с высоким рейтингом.

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

В этом примере:

    void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }

gadget->data = data до gadget->command = command только гарантируется только скомпилированным кодом компилятором. Во время работы процессор все еще может переупорядочить данные и назначение команд в отношении архитектуры процессора. Аппаратное обеспечение может получить неверные данные (предположим, что гаджет отображается на аппаратный ввод-вывод). Необходим барьер памяти между данными и назначением команд.

Ответ 14

В языке, разработанном Деннисом Ритчи, каждый доступ к любому объекту, кроме автоматических объектов, адрес которых не был взят, будет вести себя так, как если бы он вычислил адрес объекта, а затем прочитал или написал хранилище по этому адресу. Это сделало язык очень мощным, но сильно ограниченным возможностями оптимизации.

Хотя возможно было бы добавить квалификатор, который пригласил бы компилятор предположить, что какой-то конкретный объект не будет изменен в странных путях, такое предположение было бы подходящим для подавляющего большинства объектов в программах на C, и оно имело бы было нецелесообразно добавлять квалификатор ко всем объектам, для которых такое предположение было бы уместным. С другой стороны, некоторым программам необходимо использовать некоторые объекты, для которых такое предположение не будет выполнено. Чтобы устранить эту проблему, в Стандарте говорится, что компиляторы могут предполагать, что объекты, которые не объявлены volatile, не будут иметь своего значения, наблюдаемого или измененного способами, которые находятся вне элемента управления компилятором, или будут находиться вне разумного понимания компилятора.

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

Ответ 15

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

Ответ 16

Изменчивость может быть изменена извне скомпилированного кода (например, программа может отображать изменчивую переменную в регистр, привязанный к памяти). Компилятор не будет применять определенные оптимизации для кода, который обрабатывает изменчивую переменную - например, он не будет загружать его в регистр, не записывая его в память. Это важно при работе с аппаратными регистрами.

Ответ 17

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