Програма дивно поводиться в Інтернеті IDE


82

Я натрапив на наведену нижче програму C ++ ( джерело ):

#include <iostream>
int main()
{
    for (int i = 0; i < 300; i++)
        std::cout << i << " " << i * 12345678 << std::endl;
}

Це схоже на просту програму і дає правильний результат на моїй локальній машині, тобто щось на зразок:

0 0
1 12345678
2 24691356
...
297 -628300930
298 -615955252
299 -603609574

Але в онлайнових IDE, таких як codechef , це дає такий результат:

0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597

Чому forцикл не закінчується на 300? Також ця програма завжди закінчується 4169. Чому, 4169а не якесь інше значення?


45
Можливо тому, що переповнене ціле число із підписом має невизначену поведінку.
eerorika

17
Я знаю, що підписане переповнення - UB, але це вражаючий провал ...
Галік

45
Хороший урок не вважати, що UB є обмеженим
MM

12
Мені цікаво, чому ви думаєте, що перший результат - це "правильний результат".
Гонки легкості на орбіті

5
@LightnessRacesinOrbit - ну, це нагадування є цінним!
davidbak

Відповіді:


108

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

-faggressive-loop-optimizations

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

Цей варіант дозволяє лише робити припущення на основі випадків, коли UB доведено. Щоб скористатися цими припущеннями, можливо, потрібно буде ввімкнути інші оптимізації, наприклад, постійне згортання.


Підписане ціле переповнення має невизначену поведінку. Оптимізатор зміг довести, що будь-яке значення, яке iперевищує 173, спричинить UB, і оскільки він може припустити, що UB не існує, він також може припустити, що iніколи не перевищує 173. Потім він може додатково довести, що i < 300завжди відповідає дійсності, і так що стан циклу можна оптимізувати.

Чому 4169, а не якесь інше значення?

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


3
Якщо компілятор може довести, що буде UB для будь-якого значення, що iперевищує 173, то чому він не видає попередження замість безглуздої оптимізації?
Jabberwocky

7
@MichaelWalz Це випромінює.
HolyBlackCat

3
@MichaelWalz: Видалення зайвих булевих тестів не є безглуздою оптимізацією; це дуже корисно. Що було б (в основному) безглуздо - це додавання додаткового коду для вимкнення / скасування оптимізації за наявності непрацюючого вихідного коду.

25
@MichaelWalz: Компілятор не може надійно розпізнати UB, як ви пропонуєте (хоча іноді він може попереджати про можливу подію, як це насправді робить тут). Натомість він може діяти з найкращими намірами, припускаючи, що УБ не буде . Це дві, можливо, тонко, але насправді суттєво різні речі. Це не так, як компілятор пішов "ага! УБ! Тепер я можу ввімкнути таку-то оптимізацію " - оптимізація завжди була. Це робить речі , як це все час . Але, поки ваша програма написана правильно, ви не спостерігаєте жодних змін у її потенційній семантиці.
Гонки легкості на орбіті

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

40

"Невизначена поведінка не визначена." (c)

Здається, компілятор, який використовується в codechef, використовує таку логіку:

  1. Невизначена поведінка не може відбутися.
  2. i * 12345678переповнює і призводить до UB if i > 173(припускаючи 32 біт int).
  3. Таким чином, iніколи не може перевищувати 173.
  4. Таким чином, i < 300це зайве і може бути замінено на true.

Сама петля видається нескінченною. Очевидно, codechef просто зупиняє програму через певний проміжок часу або скорочує результати.


11
@ArpanMangal Я просто підрахував символи на виході, і, схоже, це так 2^16. Очевидно, це збіг обставин, що обидва усічують вихід на 2^16символи.
HolyBlackCat

3
@ArpanMangal: носові демони, такі як 4169, і в даний час проводять вечірки біля Ideone та codechef. UB не визначено, це може бути що завгодно, включаючи носових демонів. Поправді кажучи, спроба аналізувати UB - це даремна витрата часу, використовуйте цей час, щоб зрозуміти, як цього не допустити.
jmoreno

6
@jmoreno це не даремна витрата часу, якщо вас цікавить дизайн компілятора
user253751

2
@jmoreno Хоча цього разу це вдалося проаналізувати. Розуміння того, як саме UB ламає речі, може бути корисним для висновку, в яких випадках UB є прийнятним, якщо такий є.
HolyBlackCat

4
@jmoreno Все, що робить Всесвіт, є правильним (за визначенням), то який сенс у вивченні астрономії?
user253751

11

Ви викликаєте невизначену поведінку, ймовірно, на 174-й ітерації всередині вашого forциклу, оскільки максимальне intзначення, ймовірно, 2147483647ще є 174 * 123456789виразом, яке обчислює 2148147972невизначену поведінку, оскільки немає підписаного цілого числа. Отже, ви спостерігаєте за ефектом UB, особливо за допомогою компілятора GCC із встановленими у вашому випадку прапорами оптимізації. Ймовірно, компілятор попередив би вас про це, зробивши таке попередження:

warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]

Видаліть -O2прапорці оптимізації ( ), щоб спостерігати різні результати.


4
Варто зазначити, що невизначена поведінка може мати зворотний ефект - оскільки UB відбуватиметься на ітерації 174, стандарт навіть не вимагає, щоб перші 173 ітерації циклу діяли належним чином!

@Hurkyl Справді. Дещо парадоксально, що UB змушує всю програму, включаючи попередні висловлювання, відображати UB.
Рон,

12
@Ron: Це не парадоксально. Або програма має чітко визначену семантику, або її немає. Період. Пам'ятайте, код С ++ не є послідовністю вказівок, які слід виконувати; це опис програми .
Гонки легкості на орбіті

@LightnessRacesinOrbit: Програма може мати семантику, яка частково не вказана, але не повністю невизначена. Стандарт C намагається застосувати це поняття до того, що він називає "Bounded UB", хоча мова, якою він користується, трохи розпливчаста. Надання компіляторам широкої, але не необмеженої свободи в обробці таких речей, як переповнення цілих чисел, не завадить багатьом корисним оптимізаціям, але зробить мову набагато придатнішою для обробки даних, отриманих з ненадійних джерел.
supercat

@supercat: Дійсно, невизначена поведінка - це не те саме, що невизначена поведінка. Ми обговорюємо останнє
Гонки легкості на орбіті

7

Компілятор може припустити, що невизначена поведінка не відбуватиметься, а оскільки підписаний переповнення є UB, він може припустити, що ніколи i * 12345678 > INT_MAX, таким чином, i <= INT_MAX / 12345678 < 300а отже, і видаляє перевірку i < 300.

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