Це UB; в ISO C ++, вся поведінка всієї програми абсолютно не визначено для виконання, яке врешті-решт потрапить на UB. Класичний приклад, що стосується стандарту C ++, це може змусити демонів вилетіти з вашого носа. (Рекомендую не застосовувати реалізацію, де назальні демони - реальна можливість). Дивіться інші відповіді для отримання більш детальної інформації.
Компілятори можуть «спричинити неприємності» під час компіляції для шляхів виконання, які вони можуть бачити, що ведуть до видимого часу компіляції UB, наприклад, припускають, що ці базові блоки ніколи не будуть досягнуті.
Дивіться також те, що повинен знати кожен програміст на C щодо не визначеної поведінки (блог LLVM). Як пояснено там, підписаний переповнення UB дозволяє компіляторам довести, що for(... i <= n ...)
петлі не є нескінченними циклами, навіть для невідомих n
. Це також дозволяє їм "просувати" лічильники циклів int до ширини вказівника замість повторного розширення знаків. (Таким чином, наслідком UB у цьому випадку може бути доступ за межами 64k або 4G елементів масиву, якщо ви очікували підписання підписання i
в його діапазон значень.)
У деяких випадках компілятори видають незаконну інструкцію, як x86, ud2
для блоку, який, ймовірно, викликає UB, якщо він коли-небудь виконується. (Зверніть увагу, що функцію ніколи не можна викликати, тому компілятори взагалі не можуть переглядати та порушувати інші функції, або навіть можливі шляхи через функцію, яка не вражає UB, тобто машинний код, який вона компілює, все одно повинен працювати всі входи, які не призводять до UB.)
Напевно, найефективнішим рішенням є вручну factor*=10
зняти останню ітерацію, щоб уникнути непотрібних .
int result = 0;
int factor = 1;
for (... i < n-1) { // stop 1 iteration early
result = ...
factor *= 10;
}
result = ... // another copy of the loop body, using the last factor
// factor *= 10; // and optimize away this dead operation.
return result;
Або якщо тіло циклу велике, спробуйте просто використати непідписаний тип для factor
. Тоді ви можете дозволити безпідписаному множині переповнюватись, і він просто виконає чітко визначене обгортання до деякої потужності 2 (кількість бітів значення у неподписаному типі).
Це добре, навіть якщо ви використовуєте його з підписаними типами, особливо якщо ваша непідписана конверсія ніколи не переповнюється.
Перетворення між підписаним та доповненим комплектом 2 є безкоштовним (однаковий біт-шаблон для всіх значень); модульне обгортання для int -> без знака, визначеного стандартом C ++, спрощує просто використання одного і того ж бітового шаблону, на відміну від доповнення чи знаку / величини.
І unsigned-> підписано аналогічно тривіально, хоча воно визначається для значень, більших за INT_MAX
. Якщо ви не використовуєте величезний неподписаний результат від останньої ітерації, вам нічого не турбуватися. Але якщо ви є, див. Чи не визначено конверсію з непідписаного до підписаного? . Випадок "значення не відповідає" визначається реалізацією , що означає, що імплементація повинна вибрати певну поведінку; здорові просто обрізають (при необхідності) непідписаний бітовий шаблон і використовують його як підписано, оскільки це працює для значень в діапазоні так само, без зайвої роботи. І це точно не UB. Тож великі неподписані значення можуть стати негативними підписаними цілими числами. наприклад, після того, як int x = u;
gcc і clang не оптимізуютьсяx>=0
як завжди правда, навіть без -fwrapv
, тому що вони визначали поведінку.