Почему летучие существуют?


222

Что делает volatileключевое слово? В C ++ какую проблему это решает?

В моем случае я никогда не нуждался в этом сознательно.


Вот интересная дискуссия о летучими в отношении шаблона Singleton: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
chessguy

3
Существует интригующая техника, которая заставляет ваш компилятор обнаруживать возможные условия гонки, в значительной степени зависящие от ключевого слова volatile, вы можете прочитать об этом по адресу http://www.ddj.com/cpp/184403766 .
Нено Ганчев,

Это хороший ресурс с примером того, когда его volatileможно эффективно использовать, собрав в довольно обыденных терминах. Ссылка: publishing.gbdirect.co.uk/c_book/chapter8/…
Оптимизированный кодер

Я использую его для блокировки без кода / двойной проверки блокировки
Пол

Для меня volatileполезнее, чем friendключевое слово.
acegs

Ответы:


268

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

Раньше я работал с двухпортовым ОЗУ в многопроцессорной системе на прямом C. Мы использовали 16-битное значение с аппаратным управлением как семафор, чтобы знать, когда другой парень закончил. По сути, мы сделали это:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Без volatileэтого оптимизатор считает цикл бесполезным (парень никогда не устанавливает значение! Он сошел с ума, избавьтесь от этого кода!), И мой код продолжит работу, не получив семафор, что впоследствии вызовет проблемы.


В этом случае, что случилось бы, если бы uint16_t* volatile semPtrбыло написано вместо? Это должно пометить указатель как volatile (вместо значения, на которое указывает), так что проверки самого указателя, например, semPtr == SOME_ADDRмогут быть не оптимизированы. Это, однако, также подразумевает волатильное указанное значение. Нет?
Зил

@ Зил Нет, это не так. На практике то, что вы предлагаете, скорее всего, произойдет. Но теоретически можно получить компилятор, который оптимизирует доступ к значениям, потому что он решил, что ни одно из этих значений никогда не изменяется. И если бы вы имели в виду volatile для применения к значению, а не к указателю, вы бы облажались. Опять же, маловероятно, но лучше ошибиться, если поступить правильно, чем воспользоваться поведением, которое случается сегодня.
iheanyi

1
@Doug Т. Лучшее объяснение этого
machineaddict

3
@curiousguy это не решило неправильно. Он сделал правильный вывод на основе предоставленной информации. Если вам не удастся пометить что-то изменчивое, компилятор может предположить, что оно не является изменчивым . Это то, что делает компилятор при оптимизации кода. Если есть больше информации, а именно, что эти данные на самом деле изменчивы, то ответственность за предоставление этой информации лежит на программисте. То, на что вы претендуете с ошибочным компилятором, на самом деле просто плохое программирование.
iheanyi

1
@curiousguy нет, только то, что ключевое слово volatile появляется один раз, не означает, что все внезапно становится изменчивым. Я привел сценарий, в котором компилятор делает правильные вещи и достигает результата, противоречащего ошибочному ожиданиям программиста. Точно так же, как «самый неприятный синтаксический анализ» не является признаком ошибки компилятора, как и здесь.
iheanyi

82

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


9
Это относится не только к встроенным системам, но и ко всем разработкам драйверов устройств.
Младен Янкович

Единственный раз, когда я нуждался в этом на 8-битной шине ISA, где вы дважды читали один и тот же адрес - у компилятора была ошибка, и он ее игнорировал (ранний Zortech c ++)
Мартин Беккет

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

69

Некоторые процессоры имеют регистры с плавающей запятой, которые имеют точность более 64 бит (например, 32-битный x86 без SSE, см. Комментарий Питера). Таким образом, если вы выполняете несколько операций над числами с двойной точностью, вы на самом деле получаете ответ с более высокой точностью, чем если бы вы усекали каждый промежуточный результат до 64 бит.

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

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


5
Когда вы вычисляете числовые производные, это также полезно, чтобы убедиться, что x + h - x == h, вы определяете hh = x + h - x как изменчивый, чтобы можно было вычислить правильную дельту.
Александр С.

5
+1, действительно, по моему опыту, был случай, когда вычисления с плавающей запятой давали разные результаты в Debug и Release, поэтому модульные тесты, написанные для одной конфигурации, не выполнялись для другой. Мы решили это путем объявления одной переменной с плавающей точкой volatile doubleвместо простой double, чтобы обеспечить ее усечение с точности FPU до 64-битной (RAM) точности перед продолжением дальнейших вычислений. Результаты существенно отличались из-за дальнейшего преувеличения ошибки с плавающей точкой.
Серж Рогач,

Ваше определение «современный» немного не так. Это касается только 32-битного кода x86, который избегает SSE / SSE2, и он не был «современным» даже 10 лет назад. Все MIPS / ARM / POWER имеют 64-битные аппаратные регистры, как и x86 с SSE2. Реализации C ++ x86-64 всегда используют SSE2, и у компиляторов есть опции, такие как g++ -mfpmath=sseиспользовать его и для 32-битной x86. Вы можете использовать gcc -ffloat-storeдля принудительного округления везде, даже при использовании x87, или вы можете установить точность x87 на 53-битную мантиссу: randomascii.wordpress.com/2012/03/21/… .
Питер Кордес

Но все же хороший ответ: для устаревшего кода x87 вы можете использовать его volatileдля принудительного округления в нескольких конкретных местах, не теряя преимущества везде.
Питер Кордес

1
Или я ошибочно путаю с непоследовательным?
Чипстер

49

Из статьи Дэна Сакса «Волатильно как обещание» :

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

Вот ссылки на три его статьи относительно volatileключевого слова:


23

Вы ДОЛЖНЫ использовать volatile при реализации структур данных без блокировки. В противном случае компилятор может оптимизировать доступ к переменной, что изменит семантику.

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

Например, вот как InterlockedIncrement объявляется в Win32 API:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

Вам абсолютно НЕ нужно объявлять переменную volatile, чтобы иметь возможность использовать InterlockedIncrement.
любопытный парень

Теперь этот ответ устарел, поскольку в C ++ 11 std::atomic<LONG>вы можете писать код без блокировок более безопасно, без проблем с оптимизацией чистых загрузок / чистых хранилищ, переупорядочением или чем-то еще.
Питер Кордес

10

Большое приложение, над которым я работал в начале 1990-х годов, содержало обработку исключений на основе C с использованием setjmp и longjmp. Ключевое слово volatile было необходимо для переменных, значения которых нужно было сохранить в блоке кода, который служил в качестве предложения «catch», чтобы эти переменные не сохранялись в регистрах и не стирались longjmp.


10

В стандарте C одно из мест для использования volatile- с обработчиком сигнала. Фактически, в стандарте C все, что вы можете безопасно сделать в обработчике сигналов, это изменить volatile sig_atomic_tпеременную или быстро выйти. Действительно, AFAIK, это единственное место в Стандарте C, для volatileкоторого требуется использование, чтобы избежать неопределенного поведения.

ISO / IEC 9899: 2011 §7.14.1.1 signalФункция

If5 Если сигнал возникает не в результате вызова функции abortили raise, поведение не определено, если обработчик сигнала ссылается на какой-либо объект со статическим или потоковым сроком хранения, который не является атомарным объектом без блокировки, кроме как путем присвоения значения объекту, объявленному как volatile sig_atomic_t, или обработчик сигнала вызывает любую функцию в стандартной библиотеке, кроме abortфункции, _Exitфункции, quick_exitфункции или signalфункции с первым аргументом, равным номеру сигнала, соответствующему сигналу, вызвавшему вызов обработчик. Кроме того, если такой вызов signalфункции приводит к возвращению SIG_ERR, значение errnoявляется неопределенным. 252)

252) Если какой-либо сигнал генерируется асинхронным обработчиком сигнала, поведение не определено.

Это означает, что в стандарте C вы можете написать:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

и не намного больше.

POSIX гораздо более снисходительно относится к тому, что вы можете делать в обработчике сигналов, но есть все еще ограничения (и одно из ограничений заключается в том, что стандартную библиотеку ввода-вывода printf()и т. Д. Нельзя безопасно использовать).


7

Разрабатывая для встроенного, у меня есть цикл, который проверяет переменную, которая может быть изменена в обработчике прерываний. Без «volatile» цикл превращается в петлю - насколько может сказать компилятор, переменная никогда не меняется, поэтому она оптимизирует проверку.

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


7

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


7

Помимо использования по назначению, volatile используется в (шаблонном) метапрограммировании. Его можно использовать для предотвращения случайной перегрузки, поскольку атрибут volatile (например, const) участвует в разрешении перегрузки.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

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

Обратите внимание, что код никогда не зависит от volatileдоступа к памяти.


Не могли бы вы пояснить это на примере? Это действительно поможет мне лучше понять. Спасибо!
батбрат

« Приведение в энергозависимой перегрузке » Приведение является явным преобразованием. Это конструкция SYNTAX. Многие люди путают это (даже стандартные авторы).
любопытный парень

6
  1. Вы должны использовать его для реализации спин-блокировок, а также некоторых (всех?) структур данных без блокировки
  2. используйте его с атомарными операциями / инструкциями
  3. помог мне однажды преодолеть ошибку компилятора (неправильно сгенерированный код во время оптимизации)

5
Вам лучше использовать библиотеку, встроенные функции компилятора или встроенный ассемблерный код. Летучий ненадежен.
Zan Lynx

1
1 и 2 оба используют атомарные операции, но volatile не обеспечивает атомарную семантику, и специфичные для платформы реализации atomic заменят необходимость использования volatile, поэтому для 1 и 2, я не согласен, вам не нужно volatile для них.

Кто что-нибудь говорит о нестабильном обеспечении атомарной семантики? Я сказал, что вам нужно ИСПОЛЬЗОВАТЬ volatile с атомарными операциями, и если вы не думаете, что это правда, посмотрите на объявления о взаимосвязанных операциях API win32 (этот парень также объяснил это в своем ответе)
Младен Янкович,

4

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

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

Рассмотрим следующие случаи

1) Глобальные переменные, измененные подпрограммой обработки прерываний вне области действия.

2) Глобальные переменные в многопоточном приложении.

Если мы не используем volatile квалификатор, могут возникнуть следующие проблемы

1) Код может не работать должным образом при включенной оптимизации.

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

Изменчивый: лучший друг программиста

https://en.wikipedia.org/wiki/Volatile_(computer_programming)


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

2

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

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


3
Это ошибочное предположение, что компилятор не оптимизирует доступ к летучим компонентам. Стандарт ничего не знает об оптимизации. Компилятор обязан уважать то, что предписывает стандарт, но он может делать любые оптимизации, которые не мешают нормальному поведению.
Конец

3
Исходя из моего опыта, 99,9% всех «ошибок» оптимизации в gcc arm являются ошибками со стороны программиста. Не знаю, относится ли это к этому ответу. Просто разглагольствовать на общую тему
odinthenerd

@ Terminus " Это ошибочное предположение, что компилятор не оптимизирует доступ к летучим компонентам " Источник?
любопытный парень

2

Кажется, ваша программа работает даже без volatileключевого слова? Возможно, это причина

Как упоминалось ранее, volatileключевое слово помогает в таких случаях, как

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

Но, похоже, эффект почти не проявляется после вызова внешней или не встроенной функции. Например:

while( *p!=0 ) { g(); }

Затем с или без volatileпочти такой же результат генерируется.

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

Но остерегайтесь того дня, когда ваша функция g () станет встроенной (либо из-за явных изменений, либо из-за хитрости компилятора / компоновщика), тогда ваш код может сломаться, если вы забудете volatileключевое слово!

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


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

Избегать переносимого встраивания вызова (или «встраивания» его семантики) с помощью функции, тело которой видно компилятору (даже во время компоновки с глобальной оптимизацией), возможно с помощью volatileквалифицированного указателя на функцию:void (* volatile fun_ptr)() = fun; fun_ptr();
curiousguy

2

В первые дни C компиляторы интерпретировали все действия, которые читают и записывают значения l, как операции с памятью, которые должны выполняться в той же последовательности, в которой операции чтения и записи появились в коде. Во многих случаях эффективность могла бы быть значительно улучшена, если бы компиляторам была предоставлена ​​определенная свобода для переупорядочения и консолидации операций, но с этим была проблема. Даже операции часто указывались в определенном порядке просто потому, что необходимо было указывать их в каком-то порядке, и, таким образом, программист выбрал одну из многих одинаково хороших альтернатив, что не всегда имело место. Иногда было бы важно, чтобы определенные операции происходили в определенной последовательности.

Какие именно детали последовательности важны, зависит от целевой платформы и области применения. Вместо того, чтобы предоставлять особенно подробный контроль, Стандарт выбрал простую модель: если последовательность обращений выполняется с l-значениями, которые не квалифицированы volatile, компилятор может переупорядочить и объединить их по своему усмотрению. Если действие выполняется с volatile-качественным lvalue, качественная реализация должна предлагать любые дополнительные гарантии упорядочения, которые могут потребоваться при коде, предназначенном для его предполагаемой платформы и области приложения, без необходимости использования нестандартного синтаксиса.

К сожалению, вместо того, чтобы определить, какие гарантии понадобятся программистам, многие компиляторы предпочли предложить минимальные гарантии, предусмотренные стандартом. Это делает volatileгораздо менее полезным, чем должно быть. Например, в gcc или clang программист, которому необходимо реализовать базовый «мьютекс передачи» [тот, в котором задача, которая получила и освободил мьютекс, не будет делать это до тех пор, пока другая задача не выполнит это], должен выполнить один из четырех вещей:

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

  2. Определите все объекты, охраняемые мьютексом, как volatile-что-то, что не должно быть необходимым, если все обращения происходят после получения мьютекса и перед его освобождением.

  3. Используйте уровень оптимизации 0 , чтобы заставить компилятор генерировать код , как если бы все объекты, которые не квалифицированы registerявляются volatile.

  4. Используйте специфичные для gcc директивы.

Напротив, при использовании более качественного компилятора, который больше подходит для системного программирования, такого как icc, можно было бы выбрать другой вариант:

  1. Убедитесь, что volatile-качественная запись выполняется везде, где требуется приобретение или выпуск.

Для получения базового «мьютекса ручной передачи» требуется volatileчтение (чтобы увидеть, готово ли оно), и не требуется также и volatileзапись (другая сторона не будет пытаться повторно получить ее, пока она не будет возвращена), но для этого нужно выполнять бессмысленную volatileзапись все же лучше, чем любой из параметров, доступных в gcc или clang.


1

Я должен напомнить вам, что в функции-обработчике сигналов вы можете использовать глобальную переменную (например, пометить ее как exit = true) и объявить ее как 'volatile'.


1

Все ответы отличные. Но вдобавок ко всему, я хотел бы поделиться примером.

Ниже приведена небольшая программа cpp:

#include <iostream>

int x;

int main(){
    char buf[50];
    x = 8;

    if(x == 8)
        printf("x is 8\n");
    else
        sprintf(buf, "x is not 8\n");

    x=1000;
    while(x > 5)
        x--;
    return 0;
}

Теперь давайте сгенерируем сборку из приведенного выше кода (и я вставлю только те части сборки, которые здесь уместны):

Команда для генерации сборки:

g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp

И сборка:

main:
.LFB1594:
    subq    $40, %rsp    #,
    .seh_stackalloc 40
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC0(%rip), %rcx     #,
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:10:         printf("x is 8\n");
    call    _ZL6printfPKcz.constprop.0   #
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    movl    $5, x(%rip)  #, x
    addq    $40, %rsp    #,
    ret 
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

В сборке видно, что код сборки не был сгенерирован, sprintfпотому что компилятор предполагал, что xон не изменится вне программы. И то же самое в случае с whileпетлей. whileцикл был полностью удален из - за оптимизации , потому что компилятор видел его как ненужный код и , таким образом , непосредственно закрепленный 5в x(см movl $5, x(%rip)).

Проблема возникает, когда что, если внешний процесс / оборудование изменит значение xгде-то между x = 8;и if(x == 8). Мы ожидаем, что elseблок сработает, но, к сожалению, компилятор обрезал эту часть.

Теперь, чтобы решить эту проблему assembly.cpp, давайте перейдем int x;к volatile int x;и быстро увидим сгенерированный код сборки:

main:
.LFB1594:
    subq    $104, %rsp   #,
    .seh_stackalloc 104
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:9:     if(x == 8)
    movl    x(%rip), %eax    # x, x.1_1
 # assembly.cpp:9:     if(x == 8)
    cmpl    $8, %eax     #, x.1_1
    je  .L11     #,
 # assembly.cpp:12:         sprintf(buf, "x is not 8\n");
    leaq    32(%rsp), %rcx   #, tmp93
    leaq    .LC0(%rip), %rdx     #,
    call    _ZL7sprintfPcPKcz.constprop.0    #
.L7:
 # assembly.cpp:14:     x=1000;
    movl    $1000, x(%rip)   #, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_15
    cmpl    $5, %eax     #, x.3_15
    jle .L8  #,
    .p2align 4,,10
.L9:
 # assembly.cpp:16:         x--;
    movl    x(%rip), %eax    # x, x.4_3
    subl    $1, %eax     #, _4
    movl    %eax, x(%rip)    # _4, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_2
    cmpl    $5, %eax     #, x.3_2
    jg  .L9  #,
.L8:
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    addq    $104, %rsp   #,
    ret 
.L11:
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC1(%rip), %rcx     #,
    call    _ZL6printfPKcz.constprop.1   #
    jmp .L7  #
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

Здесь вы можете увидеть , что сборочные коды sprintf, printfи whileпетли были созданы. Преимущество состоит в том, что если xпеременная изменяется какой-либо внешней программой или оборудованием, sprintfчасть кода будет выполнена. И аналогичным образом whileцикл можно использовать для занятого ожидания сейчас.


0

Другие ответы уже упоминают об избежании некоторой оптимизации, чтобы:

  • использовать регистры с отображением памяти (или "MMIO")
  • написать драйверы устройства
  • облегчить отладку программ
  • сделать вычисления с плавающей запятой более детерминированными

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

Изменчивое чтение похоже на операцию ввода (например, scanfиспользование cin): значение, похоже, приходит извне программы, поэтому любые вычисления, зависящие от значения, должны начинаться после нее .

Энергозависимая запись похожа на операцию вывода (например, printfиспользование cout): значение, по-видимому, передается вне программы, поэтому, если значение зависит от вычисления, его необходимо завершить раньше .

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.