Чому летучий існує?


222

Що робить volatile ключове слово? У C ++ яку проблему вона вирішує?

У моєму випадку я ніколи свідомо не потребував цього.


Ось цікава дискусія про мінливість стосовно малюнка Singleton: aristeia.com/Papers/DDJ_Jul_Aug_2004_revid.pdf
chessguy

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

Це приємний ресурс із прикладом того, коли volatileможна ефективно використовувати, складене в досить непростому плані. Посилання: публікації.gbdirect.co.uk
c_book/chapter8/…

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

Для мене volatileкорисніше friendключове слово.
ацеги

Відповіді:


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було написано замість цього? Це повинно позначити покажчик як мінливий (замість вказаного значення), так що перевірки на сам покажчик, наприклад, semPtr == SOME_ADDRне можуть бути оптимізовані. Це, однак, також означає, що знову виникає непостійне загострене значення. Немає?
Зіль

@Zyl Ні, це не так. На практиці те, що ви пропонуєте, швидше за все, що станеться. Але теоретично можна отримати компілятор, який оптимізує доступ до значень, оскільки вирішив, що жодне з цих значень ніколи не змінюється. І якби ви мали на увазі мінливість застосувати до значення, а не покажчика, то вас би вкрутили. Знову ж, малоймовірно, але краще помилитися з тим, щоб робити все правильно, ніж скористатися поведінкою, яка трапляється сьогодні.
iheanyi

1
@Doug T. Краще пояснення цього
машинобудівний вирок

3
@curiousguy він не вирішив неправильно. Це зроблено правильне відрахування на основі наданої інформації. Якщо вам не вдалося позначити щось мінливе, компілятор вільний припустити, що воно не є мінливим . Ось що робить компілятор при оптимізації коду. Якщо є більше інформації, а саме, що ці дані насправді є нестабільними, програміст зобов'язаний надати цю інформацію. Те, що ви претендуєте на помилковий компілятор, насправді є лише поганим програмуванням.
iheanyi

1
@curiousguy ні, тільки те, що колись з’являється мінливе ключове слово, ще не означає, що все раптом стає мінливим. Я дав сценарій, коли компілятор робить все правильно і досягає результату, який суперечить тому, що програміст неправильно очікує. Так само, як "найбільш роздратований синтаксичний аналіз" не є ознакою помилки компілятора, так і тут немає.
iheanyi

82

volatileпотрібен під час розробки вбудованих систем або драйверів пристроїв, де потрібно прочитати або записати апаратний пристрій, націлений на пам'ять. Вміст певного реєстру пристроїв може змінюватися в будь-який час, тому вам потрібне volatileключове слово, щоб компілятор не оптимізував такі звернення.


9
Це справедливо не тільки для вбудованих систем, але і для всіх розробників драйверів пристроїв.
Младен Янкович

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

Летючі дуже рідко є достатніми для контролю зовнішніх пристроїв. Його семантика неправильна для сучасного MMIO: вам доведеться зробити занадто багато об'єктів мінливими, і це шкодить оптимізації. Але сучасний MMIO поводиться як нормальна пам'ять, поки прапор не встановлений настільки непостійним, що він не потрібен. Багато водіїв ніколи не використовують летючі.
curiousguy

69

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

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

Це також корисно для деяких алгоритмів, які не мають алгебраїчного сенсу, але зменшують помилку з плаваючою комою, наприклад, підсумовування Кахана. Алгебраїчно це ноп, тому він часто буде неправильно оптимізований, якщо деякі проміжні змінні не змінюються.


5
При обчисленні числових похідних також корисно, щоб x + h - x == h ви визначили hh = x + h - x як мінливі, щоб можна було обчислити належну дельту.
Олександр Ч.

5
+1, дійсно на моєму досвіді був випадок, коли обчислення з плаваючою комою давали різні результати в «Налагодження» та «Випуск», тому одиничні тести, написані для однієї конфігурації, були невдалими для іншої. Ми вирішили це за допомогою оголошення однієї змінної з плаваючою точкою як volatile doubleпросто double, щоб забезпечити її усічення від точності FPU до точності 64-бітної (ОЗП), перш ніж продовжувати подальші обчислення. Результати суттєво відрізнялися через подальше перебільшення помилки з плаваючою комою.
Серж Рогач

Ваше визначення "сучасного" трохи не вдається. На це впливає лише 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

Ви ОБОВ'ЯЗКОВО використовувати енергонезалежні під час впровадження безблокових структур даних. Інакше компілятор вільний оптимізувати доступ до змінної, що змінить семантику.

Іншим чином, непостійний повідомляє компілятору, що доступ до цієї змінної повинен відповідати операції зчитування / запису фізичної пам'яті.

Наприклад, саме так InterlockedIncrement оголошено в API Win32:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

Вам абсолютно НЕ потрібно оголошувати змінну летючою, щоб мати можливість використовувати InterlockedIncrement.
curiousguy

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

10

Великий додаток, над яким я працював на початку 1990-х, містив обробку винятків на основі С, використовуючи setjmp та longjmp. Мінливе ключове слово було необхідне для змінних, значення яких потрібно зберегти у блоці коду, який послужив умовою "лову", щоб вони не зберігалися в регістрах і не стиралися longjmp.


10

У стандарті C одне з місць використання volatile- це обробник сигналу. Насправді в стандарті C все, що ви можете безпечно зробити в обробнику сигналів, - це змінити volatile sig_atomic_tзмінну або швидко вийти. Дійсно, AFAIK - це єдине місце в Стандарті С, яке потрібно використовувати volatile, щоб уникнути невизначеної поведінки.

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

¶5 Якщо сигнал виникає інакше, ніж результат виклику abortабо raiseфункції, поведінка не визначена, якщо обробник сигналу посилається на будь-який об'єкт зі статичною тривалістю або тривалістю зберігання потоку, який не є атомним об'єктом, що не є замкнутим, крім присвоєння значення об'єкту, оголошеному як volatile sig_atomic_t, або обробник сигналу викликає будь-яку функцію в стандартній бібліотеці, крім abortфункції, _Exitфункції, quick_exitфункції або signalфункції з першим аргументом, рівним номеру сигналу, що відповідає сигналу, який викликав виклик обробник. Крім того, якщо такий виклик signalфункції призводить до повернення SIG_ERR, значення не визначене errno. 252)

252) Якщо будь-який сигнал генерується асинхронним обробником сигналу, поведінка не визначена.

Це означає, що в Standard 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

Розробляючи вбудований, у мене є цикл, який перевіряє змінну, яку можна змінити в обробці переривання. Без "мінливих" цикл стає noop - наскільки компілятор може сказати, змінна ніколи не змінюється, тому оптимізує перевірку.

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


7

Я використовував це в налагодженнях, коли компілятор наполягає на оптимізації змінної, яку я хочу бачити, переглядаючи код.


7

Окрім того, як використовувати його за призначенням, енергонезалежний використовується і в (шаблонному) метапрограмуванні. Він може бути використаний для запобігання випадкових перевантажень, оскільки мінливий атрибут (як 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. Багато хто робить цю плутанину (навіть стандартні автори).
curiousguy

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

5
Вам краще скористатися бібліотекою, характеристиками компілятора або вбудованим кодом складання. Летючий ненадійний.
Зан Лінкс

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

Хто говорить що-небудь про нестабільне забезпечення атомної семантики? Я сказав, що потрібно ВИКОРИСТОВУВАТИ мінливі дії з атомними операціями, і якщо ви не вважаєте, що це правда, подивіться на декларації про замкнені операції API win32 (цей хлопець також пояснив це у своїй відповіді)
Младен Янкович

4

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

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

Розглянемо наступні випадки

1) Глобальні змінні, модифіковані процедурою служби переривання поза сферою дії.

2) Глобальні змінні в рамках багатопотокової програми.

Якщо ми не використовуємо класифікуючий непостійний, можуть виникнути такі проблеми

1) Код може не працювати, як очікувалося, коли ввімкнено оптимізацію.

2) Код може не працювати, як очікувалося, коли вмикання та використання перерв увімкнено.

Нестабільний: найкращий друг програміста

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


Посилання, яке ви опублікували, надзвичайно застаріле і не відображає поточних найкращих практик.
Тім Сегуїн

2

Окрім того, що мінливе ключове слово використовується для того, щоб сказати компілятору не оптимізувати доступ до якоїсь змінної (яка може бути змінена потоком або процедурою переривання), вона також може бути використана для видалення деяких помилок компілятора - ТАК може бути ---.

Наприклад, я працював на вбудованій платформі, коли компілятор робив помилкові припущення щодо значення змінної. Якщо код не був оптимізований, програма працювала б нормально. З оптимізаціями (які справді були потрібні, оскільки це було критичним розпорядником) код не працюватиме правильно. Єдиним рішенням (хоча і не дуже правильним) було визнання змінної "несправності" як мінливою.


3
Помилковим є припущення про те, що компілятор не оптимізує доступ до летучих речовин. Стандарт не знає нічого про оптимізацію. Компілятор зобов'язаний поважати те, що диктує стандарт, але він може робити будь-які оптимізації, які не заважають нормальній поведінці.
Термінал

3
З мого досвіду 99,9% всіх «помилок» оптимізації в gcc arm - це помилки з боку програміста. Не маю уявлення, чи стосується це відповідь. Просто
заїка

@Terminus "Неправильним припущенням є ідея, що компілятор не оптимізує доступ до летючих речовин " Джерело?
curiousguy

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 компілятори інтерпретували всі дії, які читають і записують значення, як операції з пам'яттю, виконуючись у тій же послідовності, що і читання та запис, що з'являються в коді. Ефективність може бути значно покращена у багатьох випадках, якби компіляторам було надано певну свободу для переупорядкування та консолідації операцій, але з цим виникла проблема. Навіть операції часто вказувались у певному порядку лише тому, що потрібно було вказувати їх у певному порядку, і, таким чином, програміст обрав одну з багатьох не менш хороших альтернатив, що не завжди так. Іноді було б важливо, щоб певні операції відбувалися в певній послідовності.

Точно, які деталі послідовності є важливими, залежать від цільової платформи та області застосування. Замість того, щоб надати особливо детальний контроль, Стандарт вибрав просту модель: якщо послідовність доступу робиться з значеннями, які не є кваліфікованими volatile, компілятор може переупорядкувати та консолідувати їх, як вважає за потрібне. Якщо дія виконується з volatileкваліфікованим значенням lvalue, якісна реалізація повинна пропонувати будь-які додаткові гарантії впорядкування, вимагаючи код, орієнтований на її передбачувану платформу та поле програми, не вимагаючи використання нестандартного синтаксису.

На жаль, замість того, щоб визначити, які гарантії потребуватимуть програмісти, багато компіляторів замість цього запропонували мінімальні гарантії, передбачені Стандартом. Це робить volatileнабагато менш корисними, ніж слід. Наприклад, на gcc або clang, програміст, який повинен реалізувати основний "mutex-передача файлів" [той, де завдання, яке придбало та випустило мютекс, більше не зробить це, поки не виконає інше завдання]. з чотирьох речей:

  1. Поставте придбання та випуск mutex у функцію, яку компілятор не може вбудувати, і до якої він не може застосувати оптимізацію всієї програми.

  2. Кваліфікуйте всі об'єкти, що охороняються volatileмютекс, як - щось, що не повинно бути необхідним, якщо всі звернення виникають після придбання мютексу та перед його випуском.

  3. Використовуйте рівень оптимізації 0 , щоб змусити компілятор генерувати код , як якщо б всі об'єкти, які не кваліфіковані registerє volatile.

  4. Використовуйте специфічні для gcc директиви.

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

  1. Переконайтеся, що запит- volatileкваліфікований виконується щоразу, коли потрібне придбання або випуск.

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


1

Хочеться нагадати вам, що у функції обробки сигналу, якщо ви хочете отримати доступ / змінити глобальну змінну (наприклад, позначити її як вихід = вірно), ви повинні оголосити цю змінну як "мінливу".


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): значення, здається, передається поза програмою, тому, якщо значення залежить від обчислення, його потрібно закінчити раніше .

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.