Чому цей цикл створює "попередження: ітерація 3u викликає невизначене поведінку" та виводить більше 4 рядків?


162

Складання цього:

#include <iostream>

int main()
{
    for (int i = 0; i < 4; ++i)
        std::cout << i*1000000000 << std::endl;
}

і gccвидає таке попередження:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

Я розумію, що існує підписане ціле число.

Що я не можу отримати, це чому iзначення порушується цією операцією переповнення?

Я прочитав відповіді на те, Чому переповнення цілого числа на x86 з GCC викликає нескінченний цикл? , але мені все ще не зрозуміло, чому це відбувається - я розумію, що "невизначене" означає "що завгодно може статися", але в чому полягає основна причина цієї конкретної поведінки ?

Онлайн: http://ideone.com/dMrRKR

Компілятор: gcc (4.8)


49
Переповнене ціле число переповнення => Невизначена поведінка => Насальні демони. Але мушу визнати, що приклад досить приємний.
dyp


1
Трапляється на GCC 4.8 із позначкою O2та O3прапором, але ні, O0абоO1
Алекс

3
@dyp коли я читав Nasal Daemons, я робив "смішний сміх", який полягає в тому, щоб злегка видихнути ніс, коли побачите щось смішне. І тоді я зрозумів ... Мені повинен бути проклятий Насал Демон!
corsiKa

4
Закладка на це, щоб я міг пов’язати це наступного разу, коли хтось відмовиться "це технічно UB, але він повинен зробити щось " :)
ММ

Відповіді:


107

Переповнене ціле число переповнення (як строго кажучи, не існує такого поняття, як "перепис без цілого числа") означає не визначену поведінку . А це означає, що все може статися, і обговорювати, чому це відбувається за правилами C ++, немає сенсу.

C ++ 11 проект N3337: §5.4: 1

Якщо під час оцінювання виразу результат математично не визначений або не знаходиться в діапазоні репрезентативних значень для його типу, поведінка не визначена. [Примітка: більшість існуючих реалізацій C ++ ігнорують цілі числа понад fl ows. Обробка поділу на нуль, утворюючи залишок за допомогою нульового дільника, і всі винятки, що впливають на температуру, залежать від машин і зазвичай регулюються функцією бібліотеки. —Закінчити примітку]

Ваш код, складений із g++ -O3випромінюванням (навіть без -Wall)

a.cpp: In function 'int main()':
a.cpp:11:18: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^
a.cpp:9:2: note: containing loop
  for (int i = 0; i < 4; ++i)
  ^

Єдиний спосіб ми можемо проаналізувати, що робить програма, - прочитавши згенерований код складання.

Ось повний список складання:

    .file   "a.cpp"
    .section    .text$_ZNKSt5ctypeIcE8do_widenEc,"x"
    .linkonce discard
    .align 2
LCOLDB0:
LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  __ZNKSt5ctypeIcE8do_widenEc
    .def    __ZNKSt5ctypeIcE8do_widenEc;    .scl    2;  .type   32; .endef
__ZNKSt5ctypeIcE8do_widenEc:
LFB860:
    .cfi_startproc
    movzbl  4(%esp), %eax
    ret $4
    .cfi_endproc
LFE860:
LCOLDE0:
LHOTE0:
    .section    .text.unlikely,"x"
LCOLDB1:
    .text
LHOTB1:
    .p2align 4,,15
    .def    ___tcf_0;   .scl    3;  .type   32; .endef
___tcf_0:
LFB1091:
    .cfi_startproc
    movl    $__ZStL8__ioinit, %ecx
    jmp __ZNSt8ios_base4InitD1Ev
    .cfi_endproc
LFE1091:
    .section    .text.unlikely,"x"
LCOLDE1:
    .text
LHOTE1:
    .def    ___main;    .scl    2;  .type   32; .endef
    .section    .text.unlikely,"x"
LCOLDB2:
    .section    .text.startup,"x"
LHOTB2:
    .p2align 4,,15
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB1084:
    .cfi_startproc
    leal    4(%esp), %ecx
    .cfi_def_cfa 1, 0
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    .cfi_escape 0x10,0x5,0x2,0x75,0
    movl    %esp, %ebp
    pushl   %edi
    pushl   %esi
    pushl   %ebx
    pushl   %ecx
    .cfi_escape 0xf,0x3,0x75,0x70,0x6
    .cfi_escape 0x10,0x7,0x2,0x75,0x7c
    .cfi_escape 0x10,0x6,0x2,0x75,0x78
    .cfi_escape 0x10,0x3,0x2,0x75,0x74
    xorl    %edi, %edi
    subl    $24, %esp
    call    ___main
L4:
    movl    %edi, (%esp)
    movl    $__ZSt4cout, %ecx
    call    __ZNSolsEi
    movl    %eax, %esi
    movl    (%eax), %eax
    subl    $4, %esp
    movl    -12(%eax), %eax
    movl    124(%esi,%eax), %ebx
    testl   %ebx, %ebx
    je  L15
    cmpb    $0, 28(%ebx)
    je  L5
    movsbl  39(%ebx), %eax
L6:
    movl    %esi, %ecx
    movl    %eax, (%esp)
    addl    $1000000000, %edi
    call    __ZNSo3putEc
    subl    $4, %esp
    movl    %eax, %ecx
    call    __ZNSo5flushEv
    jmp L4
    .p2align 4,,10
L5:
    movl    %ebx, %ecx
    call    __ZNKSt5ctypeIcE13_M_widen_initEv
    movl    (%ebx), %eax
    movl    24(%eax), %edx
    movl    $10, %eax
    cmpl    $__ZNKSt5ctypeIcE8do_widenEc, %edx
    je  L6
    movl    $10, (%esp)
    movl    %ebx, %ecx
    call    *%edx
    movsbl  %al, %eax
    pushl   %edx
    jmp L6
L15:
    call    __ZSt16__throw_bad_castv
    .cfi_endproc
LFE1084:
    .section    .text.unlikely,"x"
LCOLDE2:
    .section    .text.startup,"x"
LHOTE2:
    .section    .text.unlikely,"x"
LCOLDB3:
    .section    .text.startup,"x"
LHOTB3:
    .p2align 4,,15
    .def    __GLOBAL__sub_I_main;   .scl    3;  .type   32; .endef
__GLOBAL__sub_I_main:
LFB1092:
    .cfi_startproc
    subl    $28, %esp
    .cfi_def_cfa_offset 32
    movl    $__ZStL8__ioinit, %ecx
    call    __ZNSt8ios_base4InitC1Ev
    movl    $___tcf_0, (%esp)
    call    _atexit
    addl    $28, %esp
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc
LFE1092:
    .section    .text.unlikely,"x"
LCOLDE3:
    .section    .text.startup,"x"
LHOTE3:
    .section    .ctors,"w"
    .align 4
    .long   __GLOBAL__sub_I_main
.lcomm __ZStL8__ioinit,1,1
    .ident  "GCC: (i686-posix-dwarf-rev1, Built by MinGW-W64 project) 4.9.0"
    .def    __ZNSt8ios_base4InitD1Ev;   .scl    2;  .type   32; .endef
    .def    __ZNSolsEi; .scl    2;  .type   32; .endef
    .def    __ZNSo3putEc;   .scl    2;  .type   32; .endef
    .def    __ZNSo5flushEv; .scl    2;  .type   32; .endef
    .def    __ZNKSt5ctypeIcE13_M_widen_initEv;  .scl    2;  .type   32; .endef
    .def    __ZSt16__throw_bad_castv;   .scl    2;  .type   32; .endef
    .def    __ZNSt8ios_base4InitC1Ev;   .scl    2;  .type   32; .endef
    .def    _atexit;    .scl    2;  .type   32; .endef

Я ледве навіть читаю збірку, але навіть бачу addl $1000000000, %ediрядок. Отриманий код виглядає більше схожим

for(int i = 0; /* nothing, that is - infinite loop */; i += 1000000000)
    std::cout << i << std::endl;

Цей коментар @TC:

Я підозрюю, що це щось на зразок: (1), тому що кожна ітерація iбудь-якого значення, що перевищує 2, має невизначене поведінку -> (2), ми можемо вважати, що i <= 2для оптимізації -> (3) умова циклу завжди відповідає -> (4 ) вона оптимізована в нескінченну петлю.

дав мені ідею порівняти код складання коду ОП з асемблерним кодом наступного коду, без визначеної поведінки.

#include <iostream>

int main()
{
    // changed the termination condition
    for (int i = 0; i < 3; ++i)
        std::cout << i*1000000000 << std::endl;
}

І, власне, правильний код має умову припинення.

    ; ...snip...
L6:
    mov ecx, edi
    mov DWORD PTR [esp], eax
    add esi, 1000000000
    call    __ZNSo3putEc
    sub esp, 4
    mov ecx, eax
    call    __ZNSo5flushEv
    cmp esi, -1294967296 // here it is
    jne L7
    lea esp, [ebp-16]
    xor eax, eax
    pop ecx
    ; ...snip...

OMG, це абсолютно не очевидно! Це не чесно! Я вимагаю суду вогнем!

З цим розібравшись, ви написали баггі-код і вам слід погано себе почувати. Нести наслідки.

... або, навпаки, правильно використовувати кращу діагностику та кращі інструменти налагодження - ось для чого вони:

  • включити всі попередження

    • -Wallє опцією gcc, яка дозволяє використовувати всі корисні попередження без помилкових позитивних результатів. Це мінімальний мінімум, яким ви завжди повинні користуватися.
    • gcc має багато інших варіантів попередження , однак вони не ввімкнуті, -Wallоскільки можуть попередити про помилкові позитиви
    • На жаль, Visual C ++, на жаль, відстає з можливістю давати корисні попередження. Принаймні, IDE дозволяє дещо за замовчуванням.
  • використовувати налагодження прапорів для налагодження

    • для цілочисельних переповнених -ftrapvпасток програма переповнення,
    • Clang компілятор відмінно підходить для цього: -fcatch-undefined-behaviorловить багато випадків невизначеного поведінки (примітка: "a lot of" != "all of them")

У мене безладний спагетті програми, не написаної мною, яку потрібно відправити завтра! ДОПОМОГА !!!!!! 111oneone

Використовуйте gcc -fwrapv

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

1 - це правило не поширюється на "переписане ціле число без підпису", як сказано в §3.9.1.4

Непідписані цілі числа, оголошені неподписаними, повинні підкорятися законам арифметичної модулі 2 n, де n - кількість бітів у представленні значень цього конкретного розміру цілого числа.

і напр. результат UINT_MAX + 1математично визначений - правилами арифметичної модулі 2 n


7
Я досі не розумію, що тут відбувається ... Чому iсаме це постраждало? Загалом, невизначена поведінка не має таких дивних побічних ефектів, зрештою, i*100000000має бути вагомим значенням
vsoftco

26
Я підозрюю, що це щось на зразок: (1), тому що кожна ітерація iбудь-якого значення, що перевищує 2, має невизначене поведінку -> (2), ми можемо вважати, що i <= 2для оптимізації -> (3) умова циклу завжди справжня -> (4 ) вона оптимізована в нескінченну петлю.
ТК

28
@vsoftco: Що відбувається, це випадок зменшення сили , точніше, усунення індукційних змінних . Компілятор виключає множення, видаючи код, який замість цього збільшується iна 1e9 кожну ітерацію (і відповідно змінюючи умову циклу). Це абсолютно допустима оптимізація за правилом "як би", оскільки ця програма не могла помітити різницю, якби вона добре себе поводила. На жаль, це не так, і оптимізація "протікає".
ЙоханнесД

8
@JohannesD прибив причину цієї ламання. Однак це погана оптимізація, оскільки умова завершення циклу не передбачає переповнення. Використання зменшення сили було нормально - я не знаю, що зробив би множник у процесорі (4 * 100000000), який би відрізнявся від (100000000 + 100000000 + 100000000 + 100000000), і відкидання на «не визначено - хто знає »розумно. Але замінити те, що повинно бути добре сприйнятим циклом, який виконується в 4 рази і дає невизначені результати, на те, що виконується більше 4 разів, "тому що це невизначено!" це ідіотизм.
Джулі в Остіні

14
@JulieinAustin Хоча це може бути ідіотичним для вас, це абсолютно законно. З позитивного боку, компілятор попереджає вас про це.
тисячоліття

68

Короткий відповідь, gccспеціально задокументував цю проблему, ми можемо побачити, що у примітках до випуску gcc 4.8, який говорить ( акцент минає вперед ):

Тепер GCC використовує більш агресивний аналіз для отримання верхньої межі кількості ітерацій циклів, використовуючи обмеження, накладені мовними стандартами . Це може призвести до того, що невідповідні програми більше не працюватимуть, як очікувалося, наприклад, SPEC CPU 2006 464.h264ref та 416.gamess. Для відключення цього агресивного аналізу було додано новий варіант -fno-agresive-loop-оптимізації. У деяких циклах, які знають постійну кількість ітерацій, але, як відомо, необозначена поведінка виникає в циклі до досягнення або під час останньої ітерації, GCC попередить про невизначене поведінку в циклі замість отримання нижньої верхньої межі кількості ітерацій для петлі. Попередження можна відключити за допомогою -Wno-агресивного циклу-оптимізації.

і дійсно, якщо ми використовуємо -fno-aggressive-loop-optimizationsнескінченну поведінку циклу, слід припинити, і це відбувається у всіх перевірених нами випадках.

Довга відповідь починається з усвідомлення того, що переписане ціле число є невизначеною поведінкою, переглянувши проект пункту 4 5 виразів стандартного розділу C ++, який говорить:

Якщо під час оцінки виразу результат математично не визначений або не знаходиться в діапазоні представлених значень для його типу, поведінка не визначена . [Примітка: більшість існуючих реалізацій C ++ ігнорують цілі числа переповнення. Обробка поділу на нуль, формування залишку за допомогою нульового дільника, і всі винятки з плаваючою точкою залежать від машин і зазвичай регулюються функцією бібліотеки. —Закінчити нотатку

Ми знаємо, що стандарт говорить, що невизначена поведінка є непередбачуваною з примітки, що поставляється з визначенням, яке говорить:

[Примітка: Не визначена поведінка може очікуватися, коли цей Міжнародний стандарт не містить явного визначення поведінки або коли програма використовує помилкову конструкцію або помилкові дані. Допустима невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами , до поведінки під час перекладу чи виконання програми в документально підтвердженому для середовища середовищі (з видачею діагностичного повідомлення або без нього), до припинення перекладу чи виконання (з видачею діагностичного повідомлення). Багато помилкових програмних конструкцій не породжують невизначеної поведінки; їх вимагають діагностувати. —Закінчити примітку]

Але що в світі може зробити gccоптимізатор, щоб перетворити це на нескінченний цикл? Це звучить зовсім дурно. Але, на щастя, gccдає нам зрозуміти, як це з'ясувати у попередженні:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

Підказка - це Waggressive-loop-optimizations, що це означає? На щастя для нас, це не перший раз, коли така оптимізація таким чином порушила код, і нам пощастило, оскільки Джон Реджер задокументував випадок у статті GCC до 4.8 Breaks Broken SPEC 2006, яка показує такий код:

int d[16];

int SATD (void)
{
  int satd = 0, dd, k;
  for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
  }
  return satd;
}

стаття говорить:

Невизначена поведінка має доступ до d [16] безпосередньо перед виходом із циклу. У C99 законним є створення вказівника на елемент, який знаходиться на позиції минулого кінця масиву, але цей покажчик не повинен бути відмежований.

а згодом каже:

Детально, ось що відбувається. Компілятор змінного струму, побачивши d [++ k], дозволяється припускати, що збільшене значення k знаходиться в межах масиву, оскільки в іншому випадку не визначена поведінка. Щодо коду, GCC може зробити висновок, що k знаходиться в діапазоні 0..15. Трохи пізніше, коли GCC бачить k <16, він каже собі: "Ага - це вираз завжди вірний, тому у нас є нескінченний цикл". Тут ситуація, коли компілятор використовує припущення про чітко визначену для висновку корисного факту потоку даних,

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

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

struct foo *s = ...;
int x = s->f;
if (!s) return ERROR;

gccзробили висновок, що оскільки sбуло відзначено s->f;і оскільки перенаправлення нульового покажчика є невизначеним поведінкою, то воно sне повинно бути нульовим і тому оптимізує if (!s)перевірку черговості у наступному рядку.

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


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

1
@JulieinAustin Я погоджуюсь, це досить дивна поведінка, кажучи, що розробникам потрібно уникати невизначеної поведінки, це справді лише половина питання. Очевидно, що компілятор також повинен надавати кращі відгуки і розробнику. У цьому випадку надсилається попередження, хоча воно є насправді недостатньо інформативним.
Шафік Ягмур

3
Я думаю, що це добре, я хочу кращого, швидшого коду. UB ніколи не слід використовувати.
paulm

1
@paulm морально UB явно поганий, але важко посперечатися з наданням кращих інструментів, таких як статичний аналізатор кланг, щоб допомогти розробникам вловлювати UB та інші проблеми, перш ніж це вплине на виробничі програми.
Шафік Ягмур

1
@ShafikYaghmour Крім того, якщо ваш розробник ігнорує попередження, то які шанси вони звертатимуть на вихідний сигнал? Це питання може бути легко спіймане агресивною політикою "без необгрунтованих попереджень". Кланг доцільно, але не потрібно.
deworde

24

tl; dr Код формує тест, що ціле число + додатне ціле число == від'ємне ціле число . Зазвичай оптимізатор цього не оптимізує, але в конкретному випадку, коли std::endlвін буде використаний далі, компілятор оптимізує цей тест. Я ще не зрозумів, у чому особливе endl.


З коду складання на -O1 та вищих рівнях видно, що gcc відновлює цикл для:

i = 0;
do {
    cout << i << endl;
    i += NUMBER;
} 
while (i != NUMBER * 4)

Найбільша цінність, яка працює правильно 715827882, тобто підлога ( INT_MAX/3). Фрагмент збірки за адресою -O1:

L4:
movsbl  %al, %eax
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
addl    $715827882, %esi
cmpl    $-1431655768, %esi
jne L6
    // fallthrough to "return" code

Відзначимо, що -1431655768це 4 * 715827882в додаток до 2.

Натискання -O2оптимізує це до наступного:

L4:
movsbl  %al, %eax
addl    $715827882, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655768, %esi
jne L6
leal    -8(%ebp), %esp
jne L6 
   // fallthrough to "return" code

Тож оптимізація, яка була зроблена, - це лише те, що addlбуло переміщено вище.

Якщо ми 715827883замість цього перекомпілюємо, то версія -O1 є ідентичною, крім зміненого числа та тестового значення. Однак -O2 потім вносить зміни:

L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2

Там , де було cmpl $-1431655764, %esiна -O1, що лінія була видалена для -O2. Оптимізатор, мабуть, вирішив, що додавання 715827883до %esiніколи не може дорівнювати -1431655764.

Це досить спантеличено. Якщо додати, що це INT_MIN+1 справді, приносить очікуваний результат, тож оптимізатор, мабуть, вирішив, що цього %esiніколи не може бути, INT_MIN+1і я не впевнений, чому це вирішить.

У робочому прикладі здається, що однаково справедливо зробити висновок, що додавання 715827882до числа не може бути рівним INT_MIN + 715827882 - 2! (це можливо лише в тому випадку, коли обертання відбувається насправді), але це не оптимізує лінію в цьому прикладі.


Я використовував код:

#include <iostream>
#include <cstdio>

int main()
{
    for (int i = 0; i < 4; ++i)
    {
        //volatile int j = i*715827883;
        volatile int j = i*715827882;
        printf("%d\n", j);

        std::endl(std::cout);
    }
}

Якщо std::endl(std::cout)видалено, оптимізація більше не відбувається. Насправді, заміна його std::cout.put('\n'); std::flush(std::cout);також призводить до того, що оптимізація не відбудеться, хоча вона std::endlє накресленою.

Встановлення, std::endlздається, впливає на більш ранню частину структури циклу (що я не зовсім розумію, що вона робить, але я опублікую її тут, якщо хтось інший зробить):

З оригінальним кодом та -O2:

L2:
movl    %esi, 28(%esp)
movl    28(%esp), %eax
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    __ZSt4cout, %eax
movl    -12(%eax), %eax
movl    __ZSt4cout+124(%eax), %ebx
testl   %ebx, %ebx
je  L10
cmpb    $0, 28(%ebx)
je  L3
movzbl  39(%ebx), %eax
L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2                  // no test

З mymanual вбудовування в std::endl, -O2:

L3:
movl    %ebx, 28(%esp)
movl    28(%esp), %eax
addl    $715827883, %ebx
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    $10, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    $__ZSt4cout, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655764, %ebx
jne L3
xorl    %eax, %eax

Одна різниця між цими двома полягає в тому %esi використовується в оригіналі та %ebxв другому варіанті; Чи є різниця в семантиці, визначеній між загальним %esiі %ebxзагальним? (Я мало знаю про збірку x86).


Було б добре дізнатись, якою була логіка оптимізатора, на цьому етапі мені незрозуміло, чому деякі випадки тестування оптимізовано, а деякі
НМ

8

Інший приклад повідомлення про цю помилку в gcc - це коли ви маєте цикл, який виконує постійну кількість ітерацій, але ви використовуєте змінну лічильника в якості індексу для масиву, який містить менше, ніж кількість елементів, наприклад:

int a[50], x;

for( i=0; i < 1000; i++) x = a[i];

Компілятор може визначити, що цей цикл намагатиметься отримати доступ до пам'яті поза масивом 'a'. Компілятор скаржиться на це цим доволі виразним повідомленням:

ітерація xxu викликає невизначене поведінку [-Werror = оптимізація агресивного циклу]


Ще більш критичним є те, що повідомлення видається лише тоді, коли ввімкнена оптимізація. Повідомлення M $ VB "Масив не пов'язаний" призначений для муляжів?
Раві Ганеш

6

Що я не можу отримати, чому я значення порушується цією операцією переповнення?

Здається, що ціле переповнення відбувається в 4-й ітерації (для i = 3). signedціле переповнення викликає невизначене поведінку . У цьому випадку нічого не можна передбачити. Цикл може повторюватися лише 4раз, інакше він може йти до нескінченності чи будь-чого іншого!
Результат може відрізнятись від компілятора до компілятора або навіть для різних версій одного компілятора.

C11: не визначена поведінка 1.3.24:

поведінка, щодо якої цей Міжнародний стандарт не пред'являє жодних вимог
[Примітка: Не визначена поведінка може очікуватися, коли цей Міжнародний стандарт не містить явного визначення поведінки або коли програма використовує помилкову конструкцію або помилкові дані. Допустима невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами, до поведінки під час перекладу чи виконання програми в документально підтвердженому для середовища середовищі (з видачею діагностичного повідомлення або без нього), до припинення перекладу чи виконання (з видачею діагностичного повідомлення) . Багато помилкових програмних конструкцій не породжують невизначеної поведінки; їх вимагають діагностувати. —Закінчити примітку]


@bits_international; Так.
хакі

4
Ви маєте рацію, справедливо пояснити, чому я подав заяву. Інформація у цій відповіді правильна, але вона не є навчальною, і вона повністю ігнорує слона в кімнаті: поломка, мабуть, відбувається в іншому місці (стан зупинки), ніж операція, що викликає переповнення. Механіка того, як розбиваються речі в даному конкретному випадку, не пояснюється, навіть якщо це суть цього питання. Це типова погана ситуація викладача, коли відповідь учителя не тільки не стосується ядра проблеми, але відлякує подальші питання. Це майже звучить як ...
Szabolcs

5
"Я бачу, що це невизначена поведінка, і з цього моменту мені байдуже, як і чому вона порушується. Стандарт дозволяє їй зламатись. Ніяких додаткових питань". Ви, можливо, не мали на увазі це так, але це здається таким. Я сподіваюся побачити менше цього (прикро поширеного) ставлення до SO. Це практично не корисно. Якщо ви отримуєте введення користувачем, нерозумно перевіряти наявність переповнення після кожної підписаної цілочисельної операції , навіть якщо стандарт каже, що будь-яка інша частина програми може вибухнути через це. Розуміння того, як це відбувається, допомагає уникнути подібних проблем на практиці.
Szabolcs

2
@Szabolcs: Можливо, найкраще розглядати C як дві мови, одна з яких була розроблена для того, щоб прості компілятори могли досягти розумно ефективного виконуваного коду за допомогою програмістів, які експлуатують конструкції, які були б надійними на їхніх цільових платформах, але не інші, і, отже, були проігноровані Комітетом з стандартів, і другою мовою, яка виключає всі такі конструкції, для яких Стандарт не передбачає підтримки, з тим, щоб дозволити компіляторам застосовувати додаткові оптимізації, які можуть або не переважають тих, що програмісти повинні здаватися.
supercat

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