Я просто збираюся додати детальне посилання на правило як би і на нестабільне ключове слово. (Внизу цих сторінок дотримуйтесь "див. Також" та "Посилання", щоб прослідкувати до вихідних специфікацій, але я вважаю, що cppreference.com набагато легше читати / розуміти.)
Зокрема, я хочу, щоб ви прочитали цей розділ
мінливий об'єкт - об'єкт, тип якого є летким, або субоб'єкт летючого об'єкта, або мінливий суб'єкт конст-летючого об'єкта. Кожен доступ (операція читання або запису, виклик функції члена тощо), здійснений через вираз glvalue типу volatile-кваліфікується, розглядається як видимий побічний ефект для цілей оптимізації (тобто, в межах одного потоку виконання, volatile доступ не може бути оптимізований або упорядкований за допомогою іншого видимого побічного ефекту, який секвенується - до або секвенується - після нестабільного доступу. Це робить летючі об'єкти придатними для зв'язку з обробником сигналу, але не з іншим потоком виконання, див. std :: memory_order ). Будь-яка спроба посилатися на леткий об'єкт через енергонезалежне значення glvalue (наприклад, через посилання або вказівник на нелеткий тип) призводить до невизначеної поведінки.
Тож ключове слово volatile конкретно стосується вимкнення оптимізації компілятора на glvalues . Єдине, на що тут може вплинути нестабільне ключове слово, можливо return x
, компілятор може робити все, що хоче, з рештою функції.
Наскільки компілятор може оптимізувати повернення, залежить від того, наскільки компілятору дозволено оптимізувати доступ x у цьому випадку (оскільки він нічого не впорядковує, і, строго кажучи, не видаляє вираз return. Є доступ , але це читання та запис у стек, що має бути спрощеним.) Отож, коли я читав це, це сіра область у тому, наскільки компілятору дозволено оптимізувати, і його легко можна аргументувати в обох напрямках.
Примітка: У цих випадках завжди припускайте, що компілятор буде робити навпаки того, що ви хотіли / потребували. Вам слід або відключити оптимізацію (принаймні для цього модуля), або спробувати знайти більш визначену поведінку для того, що ви хочете. (Ось чому одиничне тестування так важливе.) Якщо ви вважаєте, що це дефект, вам слід повідомити про це у розробників C ++.
Це все ще важко прочитати, тому намагаюся включити те, що, на мою думку, є доречним, щоб ви могли прочитати це самостійно.
glvalue Вираз glvalue - це або lvalue, або xvalue.
Властивості:
Значення glvalue може неявно перетворюватися на перше значення зі значенням lvalue-to-rvalue, array-to-pointer або функцією-показником неявного перетворення. Значення glvalue може бути поліморфним: динамічний тип об'єкта, який він ідентифікує, не обов'язково є статичним типом виразу. Значення glvalue може мати неповний тип, якщо це дозволено виразом.
xvalue Наступні вирази є виразами xvalue:
виклик функції або перевантажений вираз оператора, типом повернення якого є посилання rvalue на об'єкт, наприклад std :: move (x); a [n], вбудований вираз індексу, де один операнд є значенням масиву r; am, член виразу об'єкта, де a - значення r, а m - нестатичний член даних нереференційного типу; a. * mp, вказівник на член вираження об'єкта, де a - значення r, а mp - вказівник на елемент даних; а? b: c, трійковий умовний вираз для деяких b та c (докладніше див. визначення); вираз вибору для посилання rvalue на тип об'єкта, наприклад static_cast (x); будь-який вираз, який позначає тимчасовий об'єкт після тимчасової матеріалізації. (з C ++ 17) Властивості:
Те саме, що rvalue (внизу). Те саме, що і glvalue (внизу). Зокрема, як і всі rvalues, xvalues прив'язуються до посилань rvalue, і як і всі glvalues, xvalues можуть бути поліморфними, а x-значення некласу можуть бути cv-кваліфікованими.
lvalue Наступні вирази є виразами lvalue:
ім'я змінної, функції або члена даних, незалежно від типу, наприклад std :: cin або std :: endl. Навіть якщо типом змінної є посилання rvalue, вираз, що складається з її імені, є виразом lvalue; виклик функції або перевантажений вираз оператора, типом повернення якого є посилання lvalue, наприклад std :: getline (std :: cin, str), std :: cout << 1, str1 = str2 або ++ it; a = b, a + = b, a% = b та всі інші вбудовані вирази присвоєння та складеного присвоєння; ++ a та --a, вбудовані вирази попереднього збільшення та попереднього зменшення; * p, вбудований непрямий вираз; a [n] та p [n], вбудовані вирази нижчого індексу, за винятком випадків, коли a є значенням масиву r (починаючи з C ++ 11); am, член виразу об'єкта, за винятком випадків, коли m є перелічувачем членів або нестатичною функцією члена, або де a - значення r, а m - нестатичний член даних нереференсного типу; p-> m, вбудований член виразу покажчика, за винятком випадків, коли m є перечислювачем членів або нестатичною функцією члена; a. * mp, вказівник на член вираження об'єкта, де a - значення l, а mp - вказівник на елемент даних; p -> * mp, вбудований вказівник на член виразу вказівника, де mp - вказівник на елемент даних; a, b, вбудований вираз коми, де b - значення l; а? b: c, потрійний умовний вираз для деяких b і c (наприклад, коли обидва значення l є однотипними, але докладніше див. визначення); рядковий літерал, наприклад "Привіт, світе!"; вираз вибору для посилального типу lvalue, наприклад static_cast (x); виклик функції або перевантажений вираз оператора, тип повернення якого є посиланням rvalue на функцію; вираз вибору для посилання rvalue на тип функції, такий як static_cast (x). (з C ++ 11) Властивості:
Те саме, що і glvalue (внизу). Адреса значення може бути прийнята: & ++ i 1
та & std :: endl є дійсними виразами. Модифікується значення lvalue може використовуватися як лівий операнд вбудованих операторів присвоєння та складеного призначення. Lvalue може використовуватися для ініціалізації посилання lvalue; це пов'язує нову назву з об'єктом, ідентифікованим виразом.
як би правило
Компілятору С ++ дозволяється вносити будь-які зміни в програму, якщо виконується наступне:
1) У кожній точці послідовності значення всіх летких об'єктів стабільні (попередні оцінки завершені, нові оцінки не розпочаті) (до C ++ 11) 1) Доступ (читання та запис) до летких об'єктів відбувається суворо відповідно до семантики виразів, у яких вони трапляються. Зокрема, вони не впорядковуються щодо інших летких доступів на тій самій нитці. (починаючи з C ++ 11) 2) При завершенні програми дані, записані у файли, точно відповідають вимогам програми. 3) Підказковий текст, який надсилається на інтерактивні пристрої, буде показаний до того, як програма зачекає на введення. 4) Якщо підтримується прагма ISO C # pragma STDC FENV_ACCESS і встановлено значення ON,
Якщо ви хочете прочитати специфікації, я вважаю, що саме ці вам потрібно прочитати
Список літератури
Стандарт С11 (ISO / IEC 9899: 2011): 6.7.3 Кваліфікатори типу (с: 121-123)
Стандарт C99 (ISO / IEC 9899: 1999): 6.7.3 Кваліфікатори типу (p: 108-110)
Стандарт C89 / C90 (ISO / IEC 9899: 1990): 3.5.3 Кваліфікатори типу