каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 19:51
Оценка: 158 (14) :)
... где-то форматируется маленький несчастный одинокий винчестер.

winnie надыбал потрясающий пример, как из пня сделать крэй-1.
Я немножко дополировал и заретушировал его.
А теперь внимание, вопрос, где здесь чёрный ящик.
#include <iostream>

int main()
{
    int x = 27;
    for(int i=0; i < 10; ++i)
    {
        std::cout << i << " : " << i*1000000000 << " : " << x << std::endl;
        if(x==1) break;
        x = x%2 ? x*3+1 : x/2;
    }
}
Перекуём баги на фичи!
Re: каждый раз, когда вы пишете i++ + ++i...
От: Sni4ok  
Дата: 18.06.14 19:56
Оценка:
Здравствуйте, Кодт, Вы писали:

К>А теперь внимание, вопрос, где здесь чёрный ящик.

К>
К>        std::cout << i << " : " << i*1000000000 << " : " << x << std::endl;
К>


sizeof(int) на вашей платформе какой?
Re[2]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 20:08
Оценка:
Здравствуйте, Sni4ok, Вы писали:

S>sizeof(int) на вашей платформе какой?


4-байтный.
Перекуём баги на фичи!
Re[3]: каждый раз, когда вы пишете i++ + ++i...
От: watchmaker  
Дата: 18.06.14 20:12
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, Sni4ok, Вы писали:


S>>sizeof(int) на вашей платформе какой?


К>4-байтный.


А байт на вашей платформе какой? :)
Re: каждый раз, когда вы пишете i++ + ++i...
От: watchmaker  
Дата: 18.06.14 20:14
Оценка:
Здравствуйте, Кодт, Вы писали:

К> где здесь чёрный ящик.

  тут
i*1000000000 — переполнение int — UB.

Так, например, в эксперименте с gcc и с INT_MAX == 231-1, компилятор вполне разумно считает, что без переполнения i не может быть больше 2. А раз 2 < 10, то и выход по условию (i < 10) невозможен, так что его и проверять не стоит. Впрочем другие компиляторы по другому чудят.
Re[4]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 20:16
Оценка:
Здравствуйте, watchmaker, Вы писали:

К>>4-байтный.

W>А байт на вашей платформе какой?

Ну если это всё ещё пень, а не крэй, то 8-битный.
Да, там умножение на миллиард, для i=3 происходит целочисленное переполнение.
И?

Подчёркиваю: у меня воспроизвелось в реальных условиях, безо всякого злого колдунства, и хоть диск я успел спасти от форматирования, но эффект наблюдал занимательнейший.
Перекуём баги на фичи!
Re[2]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 20:17
Оценка:
Здравствуйте, watchmaker, Вы писали:

К>> где здесь чёрный ящик.

W>cut=тут

Ты зналъ!
Перекуём баги на фичи!
Re: каждый раз, когда вы пишете i++ + ++i...
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 18.06.14 20:24
Оценка:
Здравствуйте, Кодт, Вы писали:

К>А теперь внимание, вопрос, где здесь чёрный ящик.

  Скрытый текст
К>
К>#include <iostream>

К>int main()
К>{
К>    int x = 27;
К>    for(int i=0; i < 10; ++i)
К>    {
К>        std::cout << i << " : " << i*1000000000 << " : " << x << std::endl;
К>        if(x==1) break;
К>        x = x%2 ? x*3+1 : x/2;
К>    }
К>}
К>


Лопата в переполнении?
Маньяк Робокряк колесит по городу
Re[2]: каждый раз, когда вы пишете i++ + ++i...
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 18.06.14 20:41
Оценка: +1
Здравствуйте, watchmaker, Вы писали:

К>> где здесь чёрный ящик.

W>i*1000000000 — переполнение int — UB.

Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.
Вот за такие шутки авторов таких компиляторов бить по наглым рыжим мордам.
Ну или требовать -fwrapv в обязательные опции по умолчанию.

UPDATE[2019-06-30]: сейчас так не думаю. Считаю, что обязательно ввести в язык (оба, раз речь про C/C++):
— уровень 1: операции типа {add,sub,mul,bsl}_overflow с выставлением флага по факту переполнения; такое же для конверсии со сжатием размера; это уже даст переносимую возможность эффективно проверять такое вручную;
— уровень 2: шаблонные функции типа checked_add_with_flag, truncating_mul;
— уровень 3: синтаксические контексты для выбора характера операции, задаваемые атрибутами выражения или блока, а до конца блока, функции или входного файла — прагмами.

Обоснование: оптимизации на основании возможности компилятору предполагать, что переполнение не планировалось, таки нужны, но в очень малой доле (грубо говоря, 5%) от всего кода, а для остального (то есть по умолчанию) должны быть максимально сильные меры по отлову и немедленной генерации ошибки (исключения).
The God is real, unless declared integer.
Отредактировано 30.06.2019 6:11 netch80 . Предыдущая версия .
Re[2]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 21:04
Оценка:
Здравствуйте, Marty, Вы писали:

M>Лопата в переполнении?


В переполнении только черенок от лопаты, а вот лезвие — в шапке цикла.
Перекуём баги на фичи!
Re[3]: каждый раз, когда вы пишете i++ + ++i...
От: watchmaker  
Дата: 18.06.14 21:06
Оценка:
Здравствуйте, netch80, Вы писали:

N>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.

Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?

N>Вот за такие шутки авторов gcc бить по наглым рыжим мордам.

Какой-то невероятно нелогичный выбор кого бить
Это же комитет сделал в этом месте UB.
А вот gcc наоборот сделал implementation defined вместо undefined, предоставив тот же -fno-strict-overflow. Ну или упомянутый -fwrapv с ещё более строгим поведением.

N>Ну или требовать -fwrapv в обязательные опции по умолчанию.

Ну это же легко исправляется. Собственно, всё равно же в makefile всё вот это нужно писать.
Уж лучше требуй чтобы -Wall включили, или чтобы -std= со стандартным языком включили по умолчанию (а не с той дикой смесью из нескольких стандартов и расширений, что активна по-умолчанию сейчас) — вот где боль.
Re[3]: каждый раз, когда вы пишете i++ + ++i...
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 18.06.14 21:07
Оценка:
Здравствуйте, Кодт, Вы писали:

M>>Лопата в переполнении?


К>В переполнении только черенок от лопаты, а вот лезвие — в шапке цикла.


Мой cl v14 все нормально переполнил и напечатал

Надо было написать, каким компилятором компилить
Маньяк Робокряк колесит по городу
Re[5]: каждый раз, когда вы пишете i++ + ++i...
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 18.06.14 21:08
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Ну если это всё ещё пень, а не крэй, то 8-битный.

К>Да, там умножение на миллиард, для i=3 происходит целочисленное переполнение.
К>И?

К>Подчёркиваю: у меня воспроизвелось в реальных условиях, безо всякого злого колдунства, и хоть диск я успел спасти от форматирования, но эффект наблюдал занимательнейший.


А что произошло, раскрой интригу?
Маньяк Робокряк колесит по городу
Re[2]: каждый раз, когда вы пишете i++ + ++i...
От: smeeld  
Дата: 18.06.14 21:24
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Здравствуйте, Кодт, Вы писали:


К>> где здесь чёрный ящик.

W>
  тут
i*1000000000 — переполнение int — UB.

W>Так, например, в эксперименте с gcc и с INT_MAX == 231-1, компилятор вполне разумно считает, что без переполнения i не может быть больше 2. А раз 2 < 10, то и выход по условию (i < 10) невозможен, так что его и проверять не стоит. Впрочем другие компиляторы по другому чудят.


Что то у меня честно проверял i<10 на каждой итерации.


0 : 0 : 27
1 : 1000000000 : 82
2 : 2000000000 : 41
3 : -1294967296 : 124
4 : -294967296 : 62
5 : 705032704 : 31
6 : 1705032704 : 94
7 : -1589934592 : 47
8 : -589934592 : 142
9 : 410065408 : 71

sizeof(int)==4, arch==x86_64, gcc -4.4.7
Re[3]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 21:25
Оценка:
Здравствуйте, netch80, Вы писали:

N>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.


Ну вот смотри, итерации 0, 1, 2 проходят успешно.
На итерации 3 случилась беда. Дальше может быть всё, что угодно.
Компилятору было угодно не форматировать винчестер, а крикнуть IDDQD.

N>Вот за такие шутки авторов gcc бить по наглым рыжим мордам.

N>Ну или требовать -fwrapv в обязательные опции по умолчанию.

Горе от ума какое-то. Такая агрессивная преждевременная оптимизация, точно по заветам Дейкстры, вымостила дорогу в ад.


Кстати, если там добавить любое условие, — компилятор уже усомнится: а вдруг не все пути проходят через опасную формулу? И не станет вытаскивать бомбу из недр цикла в его шапку.
Перекуём баги на фичи!
Re[3]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 21:27
Оценка:
Здравствуйте, smeeld, Вы писали:

S>sizeof(int)==4, arch==x86_64, gcc -4.4.7


gcc-4.8.2 -O2
Перекуём баги на фичи!
Re[6]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 21:30
Оценка:
Здравствуйте, Marty, Вы писали:

M>А что произошло, раскрой интригу?


Компилятор обнаружил, что все пути в теле цикла проходят через i*миллиард, поэтому — если, конечно, программист не ССЗБ, — i лежит в диапазоне -2..+2, а следовательно, условие в шапке i<10 выполняется всегда. И поставил там true.
Перекуём баги на фичи!
Re[4]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 21:32
Оценка: +1
Здравствуйте, Marty, Вы писали:

M>Мой cl v14 все нормально переполнил и напечатал

M>Надо было написать, каким компилятором компилить

Неопределённое поведение — оно такое. Иногда оно кажется нормальным.
Перекуём баги на фичи!
Re[4]: каждый раз, когда вы пишете i++ + ++i...
От: smeeld  
Дата: 18.06.14 21:35
Оценка:
Здравствуйте, Кодт, Вы писали:


К>gcc-4.8.2 -O2


Может быть, результат gcc 4.4.7 c -O2 и -O3. 4.8.2 под рукой нет. Сори
Re[5]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 21:46
Оценка:
Здравствуйте, smeeld, Вы писали:

S>Может быть, результат gcc 4.4.7 c -O2 и -O3. 4.8.2 под рукой нет. Сори


А вы говорите, в седьмой шапочке сорок восьмой гусь... Пропал калабуховский дом!
Перекуём баги на фичи!
Re[7]: каждый раз, когда вы пишете i++ + ++i...
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 18.06.14 21:51
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, Marty, Вы писали:


M>>А что произошло, раскрой интригу?


К>Компилятор обнаружил, что все пути в теле цикла проходят через i*миллиард, поэтому — если, конечно, программист не ССЗБ, — i лежит в диапазоне -2..+2, а следовательно, условие в шапке i<10 выполняется всегда. И поставил там true.


Умный больно у вас компилятор Интересно, почему ему не пришло в голову, что программист-неССЗБ сделал бесконечный цикл с таким странным условием, а не просто for(;) или while(true)?
По хорошему, о таких весьма вольных предположениях надо бы сообщать даже на минимальном уровне варнингов.

ЗЫ А старичек cl v14 меня пока не подводит
ЗЫЫ Я Qt поставил новый, а с ним gcc 4.8, это что, лучше переустановить с 4.7ым? ;(
ЗЫЫЫ А какие страшные последствия в результате получились?
Маньяк Робокряк колесит по городу
Re[8]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 22:22
Оценка:
Здравствуйте, Marty, Вы писали:

M>ЗЫЫЫ А какие страшные последствия в результате получились?


Сделал 112 итераций вместо 10, — и то, потому что 3x+1 для 27 имеет длину 112.
Именно эту последовательность я выбрал, потому что она, с одной стороны, легко делается, а с другой — по-прежнему не решена, и компилятору не хватит ума раскрутить цикл.

Если, например, вместо 3x+1 сделать
vector<int> v;
for(int i=0; i<10; ++i)
  v.push_back(i * 1000000000);

то последствия окажутся куда более жестокими. Процесс выжрет всю память. Причём на 32-битной машине он просто немножко потупит, когда в своп залезет, а вот на 64-битной — пока 16 терабайт не съест, не отступится. Что будет с остальными процессами и с диском, на котором своп-файл живёт, — лучше не думать...
Перекуём баги на фичи!
Re[9]: каждый раз, когда вы пишете i++ + ++i...
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 18.06.14 22:32
Оценка:
Здравствуйте, Кодт, Вы писали:

К>то последствия окажутся куда более жестокими. Процесс выжрет всю память. Причём на 32-битной машине он просто немножко потупит, когда в своп залезет, а вот на 64-битной — пока 16 терабайт не съест, не отступится. Что будет с остальными процессами и с диском, на котором своп-файл живёт, — лучше не думать...


Как страшно жить
Не очень в курсе, как там на 64битах, но что, нет по умолчанию никаких разумных ограничений на ресурсы, предоставляемые процессам?
Маньяк Робокряк колесит по городу
Re[10]: каждый раз, когда вы пишете i++ + ++i...
От: watchmaker  
Дата: 18.06.14 23:02
Оценка:
Здравствуйте, Marty, Вы писали:


M>Не очень в курсе, как там на 64битах, но что, нет по умолчанию никаких разумных ограничений на ресурсы, предоставляемые процессам?


Эта проблема вообще имеет довольно слабое отношения к разрядности. Какая-нибудь банальная fork-бомба (которой вообще всё равно какая в системе разрядность) способна нанести куда больше разрушений чем выделение большого объёма памяти одиночным процессом.

Ну и да, для предотвращения вот этого вот всего в современных ОС есть способы ограничить число процессов, число файловых дескрипторов, объёмы памяти, и разные другие ресурсы. И для устойчивой работы системы это стоит делать (опять же вне зависимости от разрядности ОС).
Re[11]: каждый раз, когда вы пишете i++ + ++i...
От: Кодт Россия  
Дата: 18.06.14 23:26
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Эта проблема вообще имеет довольно слабое отношения к разрядности. Какая-нибудь банальная fork-бомба (которой вообще всё равно какая в системе разрядность) способна нанести куда больше разрушений чем выделение большого объёма памяти одиночным процессом.


Я на работе изредка на 16-гектарном компьютере расходую 14 гектар на один процесс.
Система, только-только проваливаясь в своп, виснет на полминуты. И может не отвиснуть вообще, если в это время она занималась чем-то, с её точки зрения, очень нужным, но низкоприоритетным.

W>Ну и да, для предотвращения вот этого вот всего в современных ОС есть способы ограничить число процессов, число файловых дескрипторов, объёмы памяти, и разные другие ресурсы. И для устойчивой работы системы это стоит делать (опять же вне зависимости от разрядности ОС).


Кстати, не знаешь, как в виндах устанавливать из командной строки квоту конкретному процессу?
Перекуём баги на фичи!
Re[12]: каждый раз, когда вы пишете i++ + ++i...
От: watchmaker  
Дата: 19.06.14 00:02
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, watchmaker, Вы писали:


W>>Эта проблема вообще имеет довольно слабое отношения к разрядности. Какая-нибудь банальная fork-бомба (которой вообще всё равно какая в системе разрядность) способна нанести куда больше разрушений чем выделение большого объёма памяти одиночным процессом.


К>Я на работе изредка на 16-гектарном компьютере расходую 14 гектар на один процесс.

К>Система, только-только проваливаясь в своп, виснет на полминуты.
Это всё понятно. Просто аналогичный эффект и на древнем 32-битном PentiumPro можно получить — в нём же тоже 64 гигабайта адресовать можно. Просто сложно найти человека, который бы поставил столько памяти в такую древнюю машину. Тут проблема со своппингом связана в первую очередь не с тем, что разрядность процессора поменялась, а с тем что банально память стала дешевле, её у компьютеров стало больше, а программы стали прожорлевее. А вместе с тем скорость работы дисковой подсистемы не так значительно выросла — из-за этого несоответствия между увеличивающимися объёмами и непоспевающей за ними скоростью и возникает ощущение что swapping тормозит, равно как и запись многогигабайтного core-файла Но вот адаптируй свой процесс под PentiumPro — уверяю, тормозить и своппится будет куда как заметнее. Так что я бы наоборот сказал, что переход на 64-бита дал тут ускорение или может это от частоты зависит?

W>>Ну и да, для предотвращения вот этого вот всего в современных ОС есть способы ограничить число процессов, число файловых дескрипторов, объёмы памяти, и разные другие ресурсы. И для устойчивой работы системы это стоит делать (опять же вне зависимости от разрядности ОС).


К>Кстати, не знаешь, как в виндах устанавливать из командной строки квоту конкретному процессу?

Не... Вот в linux есть cgroups, и там как раз через команду cgexec можно процесс с потомками всячески попритеснять, в том числе и по объёму памяти. Это, кстати, вполне удобно для запуска всяких ненадёжных программ с непомерными требованиями — в случае нехватки ресурсов процесс будет заперт внутри контейнера, практически не затронув работу остальной системы.
Re: каждый раз, когда вы пишете i++ + ++i...
От: jazzer Россия Skype: enerjazzer
Дата: 19.06.14 01:32
Оценка: +1
Здравствуйте, Кодт, Вы писали:

К>... где-то форматируется маленький несчастный одинокий винчестер.


Кстати, если объявить unsigned int i, то все нормально: переполнение беззнаковых — не UB (вернее, его вообще не бывает)
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[13]: каждый раз, когда вы пишете i++ + ++i...
От: ned Австралия  
Дата: 19.06.14 02:33
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>А вместе с тем скорость работы дисковой подсистемы не так значительно выросла — из-за этого несоответствия между увеличивающимися объёмами и непоспевающей за ними скоростью и возникает ощущение что swapping тормозит, равно как и запись многогигабайтного core-файла


Почему же не поспевают? SSD уже давно не роскошь и в девелоперские системы их ставят по-умолчанию. Разве нет?
Re[4]: каждый раз, когда вы пишете i++ + ++i...
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.06.14 04:29
Оценка: 9 (1)
Здравствуйте, Кодт, Вы писали:

S>>sizeof(int)==4, arch==x86_64, gcc -4.4.7

К>gcc-4.8.2 -O2

-O2 это не один уровень на всех, это описанный в документации набор опций, которыми можно управлять и отдельно. Данная конкретная опция называется "strict-overflow", вот описание из info:

`-fstrict-overflow'
Allow the compiler to assume strict signed overflow rules, depending on the language being compiled. For C (and C++) this means that overflow when doing arithmetic with signed numbers is undefined, which means that the compiler may assume that it does not happen. This permits various optimizations. For example, the compiler assumes that an expression like `i + 10 > i' is always true for signed `i'. This assumption is only valid if signed overflow is undefined, as the expression is false if `i + 10' overflows when using twos complement arithmetic. When this option is in effect any attempt to determine whether an operation on signed numbers overflows must be written carefully to not actually involve overflow.

This option also allows the compiler to assume strict pointer semantics: given a pointer to an object, if adding an offset to that pointer does not produce a pointer to the same object, the addition is undefined. This permits the compiler to conclude that `p + u > p' is always true for a pointer `p' and unsigned integer `u'. This assumption is only valid because pointer wraparound is undefined, as the expression is false if `p + u' overflows using twos complement arithmetic.

See also the `-fwrapv' option. Using `-fwrapv' means that integer signed overflow is fully defined: it wraps. When `-fwrapv' is used, there is no difference between `-fstrict-overflow' and `-fno-strict-overflow' for integers. With `-fwrapv' certain types of overflow are permitted. For example, if the compiler gets an overflow when doing arithmetic on constants, the overflowed value can still be used with `-fwrapv', but not otherwise.

The `-fstrict-overflow' option is enabled at levels `-O2', `-O3', `-Os'.


Опция эта существует очень давно, как минимум в 4.2 я её вижу, дальше лень копать. Конкретное её проявление специфично уже для версии. До 4.8 описанный тобой эффект не замечается.

Кроме того, на неё есть предупреждения — опция -Wstrict-overflow[=N], но у меня на этот пример кода ни один уровень не сработал. Видимо, это уже недоработка компилятора.

Если будет ещё настроение, раскопаю логику компилятора.
The God is real, unless declared integer.
Re[4]: каждый раз, когда вы пишете i++ + ++i...
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.06.14 04:39
Оценка: 33 (2)
Здравствуйте, watchmaker, Вы писали:

N>>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.

W>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?

Не видел. Спасибо, забавно. Но это не оправдывает всех замешанных.
Начиная с того, что в данном случае "undefined" или "implementation defined" должно было свестись к тому, что результат выполнения конкретного оператора является значением определённого параметрами и оператором типа, но с неизвестным значением. А не просто "UB".

N>>Вот за такие шутки авторов gcc бить по наглым рыжим мордам.

W>Какой-то невероятно нелогичный выбор кого бить
W>Это же комитет сделал в этом месте UB.

Да. Но комитет это заведомо сборище старых маразматиков, управляемых своими заказчиками. А вот заказчики должны были подумать (как минимум!) о том, чтобы само понятие UB здесь ограничить только эффектами, компилируемыми в рамках текущего контекста. А не пытаться получить себе кусок воли по принципу "ну мы же всё равно действуем в рамках законодательства РФ стандарта C".

N>>Ну или требовать -fwrapv в обязательные опции по умолчанию.

W>Ну это же легко исправляется. Собственно, всё равно же в makefile всё вот это нужно писать.
W>Уж лучше требуй чтобы -Wall включили, или чтобы -std= со стандартным языком включили по умолчанию (а не с той дикой смесью из нескольких стандартов и расширений, что активна по-умолчанию сейчас) — вот где боль.

Про -Wall в принципе согласен. Про std — нет. По крайней мере в Posix системах для стандартных случаев есть врапперы типа c89, c99. Кто не рассчитывает на абсолютные знания gcc, а хочет поближе к стандарту — должен использовать их.
The God is real, unless declared integer.
Re[4]: каждый раз, когда вы пишете i++ + ++i...
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.06.14 05:45
Оценка:
Здравствуйте, Кодт, Вы писали:

N>>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.

К>Ну вот смотри, итерации 0, 1, 2 проходят успешно.
К>На итерации 3 случилась беда. Дальше может быть всё, что угодно.

Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору:
  • выполнить операцию в соответствии с правилами платформы
  • выполнить операцию в соответствии с общепринятой арифметикой в дополнительном коде
  • выполнить операцию, отдав в качестве результата зависящее от реализации значение
  • по зависящим от реализации правилам выполнить действие, предусмотренное для особой ситуации типа "целочисленное переполнение"

    причём первые два предпочтительны, почему и выделены явно (хотя входят в третье в качестве частных случаев).
    И тут уже нет варианта "выполнить ХЗ что, зато повлиять на совершенно сторонний оператор".

    Да, это в какой-то мере из категории мечтаний, но если стандартизаторы этого не делают, то это задача авторов компилятора — не делать явные диверсии.

    N>>Вот за такие шутки авторов gcc бить по наглым рыжим мордам.

    N>>Ну или требовать -fwrapv в обязательные опции по умолчанию.
    К>Горе от ума какое-то. Такая агрессивная преждевременная оптимизация, точно по заветам Дейкстры, вымостила дорогу в ад.

    Что ты назвал "оптимизацией"? Такая опция, наоборот, устраняет оптимизации. Если ты её имел в виду, то используй, пожалуйста, более адекватные термины.
    Далее, с чего это тут дорога в ад?

    К>Кстати, если там добавить любое условие, — компилятор уже усомнится: а вдруг не все пути проходят через опасную формулу? И не станет вытаскивать бомбу из недр цикла в его шапку.


    И правильно делает. Если бы ещё в простом случае не проявлял неестественного интеллекта...
  • The God is real, unless declared integer.
    Re: каждый раз, когда вы пишете i++ + ++i...
    От: k55 Ниоткуда  
    Дата: 19.06.14 07:07
    Оценка: 1 (1)
    Кодт,

    Поясни пожалуйста, каким образом переполнение в

    std::cout << i << " : " << i*1000000000 << " : " << x << std::endl;

    влияет на

    for(int i=0; i < 10; ++i)


    Я не могу понять, ведь ни где значение i не меняется (кроме шапки цикла).
    Если есть желание — найдется 1000 возможностей.
    Если нет желания — найдется 1000 причин.
    Re[2]: Произвол компилятора
    От: Qbit86 Кипр
    Дата: 19.06.14 07:19
    Оценка:
    Здравствуйте, k55, Вы писали:

    k55>Поясни пожалуйста, каким образом переполнение...

    k55>влияет на
    k55>for(int i=0; i < 10; ++i)

    Компилятор вместо «i < 10» статически подставляет «true», потому что полагает i не выходящем из диапазона [-2, 2].
    А полагает он это, потому что может. Это его способ отрицания реальности. Если закрыть глаза на ++i, то переполнение исчезнет. Закрыть глаза — одна из возможных реализаций неопределённого поведения.
    Глаза у меня добрые, но рубашка — смирительная!
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: Don Reba Канада https://stackoverflow.com/users/49329/don-reba
    Дата: 19.06.14 07:25
    Оценка: 1 (1)
    Здравствуйте, netch80, Вы писали:

    N>Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору:

    N>• выполнить операцию в соответствии с правилами платформы
    N>• выполнить операцию в соответствии с общепринятой арифметикой в дополнительном коде
    N>• выполнить операцию, отдав в качестве результата зависящее от реализации значение
    N>• по зависящим от реализации правилам выполнить действие, предусмотренное для особой ситуации типа "целочисленное переполнение"

    Предположение, что переполнения быть не может в принципе позволяет комиляютору соптимизировать выражение x * 2 / 2. А ограничения выше — нет.
    Ce n'est que pour vous dire ce que je vous dis.
    Re[3]: Произвол компилятора
    От: pagid Россия  
    Дата: 19.06.14 07:29
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    Q>Компилятор вместо «i < 10» статически подставляет «true», потому что полагает i не выходящем из диапазона [-2, 2].

    Q>А полагает он это, потому что может. Это его способ отрицания реальности. Если закрыть глаза на ++i, то переполнение исчезнет. Закрыть глаза — одна из возможных реализаций неопределённого поведения.

    Позволили делать ему столь странное предположение не от большого ума совершенно напрасно. Хотя интеллектуальные услилия несомненно приложили.
    ... << RSDN@Home 1.2.0 alpha 5 rev. 1495>>
    Re[3]: Произвол компилятора
    От: k55 Ниоткуда  
    Дата: 19.06.14 07:39
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    Q>Компилятор вместо «i < 10» статически подставляет «true», потому что полагает i не выходящем из диапазона [-2, 2].

    Q>А полагает он это, потому что может. Это его способ отрицания реальности. Если закрыть глаза на ++i, то переполнение исчезнет. Закрыть глаза — одна из возможных реализаций неопределённого поведения.

    Яснее не стало.
    Какого лешего он полагает что "i не выходящем из диапазона [-2, 2]", если i изменяется только тут ++i??

    Т.е. UB в любом месте кода, даже если оно случилось во временной переменной, может повлиять вооообще на все что угодно?
    Если есть желание — найдется 1000 возможностей.
    Если нет желания — найдется 1000 причин.
    Re[4]: Произвол компилятора
    От: Don Reba Канада https://stackoverflow.com/users/49329/don-reba
    Дата: 19.06.14 07:55
    Оценка:
    Здравствуйте, k55, Вы писали:

    k55>Т.е. UB в любом месте кода, даже если оно случилось во временной переменной, может повлиять вооообще на все что угодно?


    А ты думал, насчёт форматирования диска — шутка была?
    Ce n'est que pour vous dire ce que je vous dis.
    Re[4]: Произвол компилятора
    От: Qbit86 Кипр
    Дата: 19.06.14 08:01
    Оценка: 10 (1)
    Здравствуйте, k55, Вы писали:

    k55>Какого лешего он полагает что "i не выходящем из диапазона [-2, 2]", если i изменяется только тут ++i?? :maniac:


    Ты полагаешь, что последовательность рассуждений компилятора над разными частями кода привязана к тому порядку, в котором предполагается выполнение в рантайме. А это не так, компилятор может хоть задом наперёд твой код анализировать.

    В частности он навешивает атрибут статически вычесленных возможных значений на переменные во время этого анализа. Вот он видит переполнение, там кругом литералы, он может статически вычислить возможные значения переменной, при которых переполнения нет, запоминает. При генерации кода для «i < 10» схлопывает его до «true» на основании этого атрибута. При генерации кода для «++i» так его и оставляет, например.
    Глаза у меня добрые, но рубашка — смирительная!
    Re[5]: Произвол компилятора
    От: k55 Ниоткуда  
    Дата: 19.06.14 08:30
    Оценка:
    Здравствуйте, Don Reba, Вы писали:

    DR>Здравствуйте, k55, Вы писали:


    k55>>Т.е. UB в любом месте кода, даже если оно случилось во временной переменной, может повлиять вооообще на все что угодно?


    DR>А ты думал, насчёт форматирования диска — шутка была?

    Вначале так и думал, а оказалось что шутка-то с двойным дном.
    Если есть желание — найдется 1000 возможностей.
    Если нет желания — найдется 1000 причин.
    Re: каждый раз, когда вы пишете i++ + ++i...
    От: Tilir Россия http://tilir.livejournal.com
    Дата: 19.06.14 08:30
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>А теперь внимание, вопрос, где здесь чёрный ящик.


    Хм... Ну UB очевидно, но на GCC 4.6.2 (и O1 и O2 и O3) полет нормальный:

    g++ (SUSE Linux) 4.6.2
    Copyright (C) 2011 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    > g++ -O3 kodt1.cpp -o kodt1.x
    > ./kodt1.x
    0 : 0 : 27
    1 : 1000000000 : 82
    2 : 2000000000 : 41
    3 : -1294967296 : 124
    4 : -294967296 : 62
    5 : 705032704 : 31
    6 : 1705032704 : 94
    7 : -1589934592 : 47
    8 : -589934592 : 142
    9 : 410065408 : 71


    gcc-4.7.2 ведёт себя так же как 4.6.2

    gcc 4.8.2 вычисляет цикл до i = 111, это несколько смешней, но бесконечного цикла воспроизвести не удалось. Я должен взять нечто совсем древнее для этого?
    Re[5]: Произвол компилятора
    От: k55 Ниоткуда  
    Дата: 19.06.14 08:33
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    Q>Ты полагаешь, что последовательность рассуждений компилятора над разными частями кода привязана к тому порядку, в котором предполагается выполнение в рантайме. А это не так, компилятор может хоть задом наперёд твой код анализировать.


    Q>В частности он навешивает атрибут статически вычесленных возможных значений на переменные во время этого анализа. Вот он видит переполнение, там кругом литералы, он может статически вычислить возможные значения переменной, при которых переполнения нет, запоминает. При генерации кода для «i < 10» схлопывает его до «true» на основании этого атрибута. При генерации кода для «++i» так его и оставляет, например.


    Т.е. компилятор ошибочно исходит из того что "программист не хотел переполнения".
    Если есть желание — найдется 1000 возможностей.
    Если нет желания — найдется 1000 причин.
    Re[2]: каждый раз, когда вы пишете i++ + ++i...
    От: Qbit86 Кипр
    Дата: 19.06.14 08:34
    Оценка: 6 (1)
    Здравствуйте, Tilir, Вы писали:

    T>gcc 4.8.2 вычисляет цикл до i = 111, это несколько смешней, но бесконечного цикла воспроизвести не удалось.


    Это потому что «if(x==1) break;»
    Глаза у меня добрые, но рубашка — смирительная!
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: Tilir Россия http://tilir.livejournal.com
    Дата: 19.06.14 08:38
    Оценка:
    Здравствуйте, netch80, Вы писали:

    N>Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору:

    N>
  • выполнить операцию в соответствии с правилами платформы
    N>
  • выполнить операцию в соответствии с общепринятой арифметикой в дополнительном коде
    N>
  • выполнить операцию, отдав в качестве результата зависящее от реализации значение
    N>
  • по зависящим от реализации правилам выполнить действие, предусмотренное для особой ситуации типа "целочисленное переполнение"

    Без возможности сделать на этапе компиляции arithmetical reassociations (а ваши условия их убивают) вы платите производительностью за то, чего не заказывали. Это противоречит философии C++. Убирать такие вещи под опции -- верное решение. Если вы сомневаетесь и производительность не критична -- подавайте -fwrapv и будет счастье.
  • Re[3]: каждый раз, когда вы пишете i++ + ++i...
    От: Tilir Россия http://tilir.livejournal.com
    Дата: 19.06.14 08:41
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    Q>Это потому что «if(x==1) break;»


    Спасибо, протупил.
    Re[6]: Произвол компилятора
    От: Qbit86 Кипр
    Дата: 19.06.14 09:11
    Оценка:
    Здравствуйте, k55, Вы писали:

    k55>Т.е. компилятор ошибочно исходит из того что "программист не хотел переполнения".


    Компилятор резонно исходит из того, что «программист не хотел переполнения». Ведь обычно программисты не хотят переполнения, когда пишут код типа «x + 15» или «y * 2». И более того, обычно знают, что переполнения там нет, исходя из недоступной компилятору информации об исполнении в рантайме (например, x означает минуты, y — скорость). В данном случае компилятор решил, что программист знает больше него об условиях выхода из цикла; скажем, программист рассчитывает, что цикл завершится по break раньше переполнения, и поэтому можно упразднить условие в шапке.
    Глаза у меня добрые, но рубашка — смирительная!
    Re[7]: Произвол компилятора
    От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
    Дата: 19.06.14 09:21
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    Q>Компилятор резонно исходит из того, что «программист не хотел переполнения». Ведь обычно программисты не хотят переполнения, когда пишут код типа «x + 15» или «y * 2». И более того, обычно знают, что переполнения там нет, исходя из недоступной компилятору информации об исполнении в рантайме (например, x означает минуты, y — скорость). В данном случае компилятор решил, что программист знает больше него об условиях выхода из цикла; скажем, программист рассчитывает, что цикл завершится по break раньше переполнения, и поэтому можно упразднить условие в шапке.


    break вставил Кодт во избежание, как я понимаю, оригинал скорее всего был без break'а и успешно форматировал диск в бесконечном цикле — т.е. компилятор не мог видеть никакого другого условия выхода из цикла, кроме i<10, но не капли не сомневаясь, сделал цикл бесконечным.
    Маньяк Робокряк колесит по городу
    Re[8]: Произвол компилятора
    От: Qbit86 Кипр
    Дата: 19.06.14 09:31
    Оценка:
    Здравствуйте, Marty, Вы писали:

    M>break вставил Кодт во избежание, как я понимаю, оригинал скорее всего был без break'а... т.е. компилятор не мог видеть никакого другого условия выхода из цикла, кроме i<10


    У Винни в оригинале код был без брейка, но с «std::cout <<». Компилятор не может знать, не выпадет ли оттуда исключение.
    Глаза у меня добрые, но рубашка — смирительная!
    Re[13]: каждый раз, когда вы пишете i++ + ++i...
    От: Кодт Россия  
    Дата: 19.06.14 09:35
    Оценка:
    Здравствуйте, watchmaker, Вы писали:

    К>>Я на работе изредка на 16-гектарном компьютере расходую 14 гектар на один процесс.

    К>>Система, только-только проваливаясь в своп, виснет на полминуты.
    W>Это всё понятно. Просто аналогичный эффект и на древнем 32-битном PentiumPro можно получить — в нём же тоже 64 гигабайта адресовать можно. Просто сложно найти человека, который бы поставил столько памяти в такую древнюю машину. Тут проблема со своппингом связана в первую очередь не с тем, что разрядность процессора поменялась, а с тем что банально память стала дешевле, её у компьютеров стало больше, а программы стали прожорлевее. А вместе с тем скорость работы дисковой подсистемы не так значительно выросла — из-за этого несоответствия между увеличивающимися объёмами и непоспевающей за ними скоростью и возникает ощущение что swapping тормозит, равно как и запись многогигабайтного core-файла Но вот адаптируй свой процесс под PentiumPro — уверяю, тормозить и своппится будет куда как заметнее. Так что я бы наоборот сказал, что переход на 64-бита дал тут ускорение или может это от частоты зависит?

    Это зависит от политики свопа, особенно — с хитрым виндовским префетчем.

    W>>>Ну и да, для предотвращения вот этого вот всего в современных ОС есть способы ограничить число процессов, число файловых дескрипторов, объёмы памяти, и разные другие ресурсы. И для устойчивой работы системы это стоит делать (опять же вне зависимости от разрядности ОС).


    К>>Кстати, не знаешь, как в виндах устанавливать из командной строки квоту конкретному процессу?

    W>Не... Вот в linux есть cgroups, и там как раз через команду cgexec можно процесс с потомками всячески попритеснять, в том числе и по объёму памяти. Это, кстати, вполне удобно для запуска всяких ненадёжных программ с непомерными требованиями — в случае нехватки ресурсов процесс будет заперт внутри контейнера, практически не затронув работу остальной системы.

    Теоретически, можно написать утилиту, которая будет создавать Job, выдавать квоты, всячески SetProcessMemoryLimit для свежезапущенного CreateProcess... Но это ещё надо писать.
    Я думал, а вдруг есть какая-нибудь готовая, типа юниксовой nice или виндовой start /affinity-goes-here /priority-goes-here
    Перекуём баги на фичи!
    Re: ругающим компилятор
    От: jazzer Россия Skype: enerjazzer
    Дата: 19.06.14 09:42
    Оценка: 57 (5) +1
    что он-де предполагает чего не должен.
    Т.е. предполагает, что знаковые целые никогда не переполняются.
    Так вот, он делает это на основании вашего же обещания. Это вы сами при помощи опции -O2/O3/Os (это просто (почти) комбинации других опций) пообещали ему, что переполнений нет и что он может, исходя из этого обещания, оптимизировать программу как хочет:

    -fstrict-overflow

    Allow the compiler to assume strict signed overflow rules, depending on the language being compiled.
    For C (and C++) this means that overflow when doing arithmetic with signed numbers is undefined, which
    means that the compiler may assume that it does not happen. This permits various optimizations.
    For
    example, the compiler assumes that an expression like "i + 10 > i" is always true for signed "i". This
    assumption is only valid if signed overflow is undefined, as the expression is false if "i + 10"
    overflows when using twos complement arithmetic. When this option is in effect any attempt to
    determine whether an operation on signed numbers overflows must be written carefully to not actually
    involve overflow.

    This option also allows the compiler to assume strict pointer semantics: given a pointer to an object,
    if adding an offset to that pointer does not produce a pointer to the same object, the addition is
    undefined. This permits the compiler to conclude that "p + u > p" is always true for a pointer "p" and
    unsigned integer "u". This assumption is only valid because pointer wraparound is undefined, as the
    expression is false if "p + u" overflows using twos complement arithmetic.

    See also the -fwrapv option. Using -fwrapv means that integer signed overflow is fully defined: it
    wraps. When -fwrapv is used, there is no difference between -fstrict-overflow and -fno-strict-overflow
    for integers. With -fwrapv certain types of overflow are permitted. For example, if the compiler gets
    an overflow when doing arithmetic on constants, the overflowed value can still be used with -fwrapv,
    but not otherwise.

    The -fstrict-overflow option is enabled at levels -O2, -O3, -Os.


    Так что, если вы хотите все оптимизации, кроме этой, надо либо
    1) отозвать обещание явно при помощи -fno-strict-overflow (т.е. переполнения возможны); либо
    2) определить арифметику для знаковых так же, как и для беззнаковых, чтобы переполнения не происходило (а все вычисления просто шли по модулю 2), при помоши -fwrapv.

    Ну и третье решение — использовать беззнаковые типы для переменных, которые не предполагают отрицательных значений.

    ЗЫ Правильной практикой является изучение того, какие оптимизации включаются на каждом уровне, и проверка, что ваш код требованиям этих оптимизаций соответствует. Либо неиспользование "сборных" уровней оптимизаций и вместо этого использование ручного списка индивидуальных опций — тогда апгрейд компилятора вам ничего не поломает.

    ЗЗЫ Беда только в том, что "сборные" опции — это не только индивидуальные опции, там есть еще некий темный набор действий, опциями не покрытый — я на это напарывался: включаешь -O2, отключаешь все опции, из которых он, вроде бы, состоит — и все равно поведение компилятора отличается.
    jazzer (Skype: enerjazzer) Ночная тема для RSDN
    Автор: jazzer
    Дата: 26.11.09

    You will always get what you always got
      If you always do  what you always did
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: Кодт Россия  
    Дата: 19.06.14 09:44
    Оценка:
    Здравствуйте, netch80, Вы писали:

    N>Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору:


    То есть, поменять undefined на unspecified, а если заплатить комитету стандартизации много пожертвований, то вообще на implementation-defined (чтобы авторы компиляторов были вынуждены внести эффекты ещ1 и в свою документацию).


    К>>Горе от ума какое-то. Такая агрессивная преждевременная оптимизация, точно по заветам Дейкстры, вымостила дорогу в ад.


    N>Что ты назвал "оптимизацией"? Такая опция, наоборот, устраняет оптимизации. Если ты её имел в виду, то используй, пожалуйста, более адекватные термины.

    N>Далее, с чего это тут дорога в ад?

    Оптимизация, конечно: вместо test+jnz сделал jmp
    Выточил трансформатор из дуба, потому что никто читать не будет... наивный.
    А дорога в ад — согласно поговорке, вымощена благими намерениями. И преждевременными оптимизациями.
    Перекуём баги на фичи!
    Re[2]: каждый раз, когда вы пишете i++ + ++i...
    От: Кодт Россия  
    Дата: 19.06.14 09:46
    Оценка:
    Здравствуйте, Tilir, Вы писали:

    T>gcc 4.8.2 вычисляет цикл до i = 111, это несколько смешней, но бесконечного цикла воспроизвести не удалось. Я должен взять нечто совсем древнее для этого?


    Я этому крэю палки в колёса воткнул. Убери break, будет тебе до бесконечности.
    Перекуём баги на фичи!
    Re[14]: каждый раз, когда вы пишете i++ + ++i...
    От: visual_wind  
    Дата: 19.06.14 09:48
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>Теоретически, можно написать утилиту, которая будет создавать Job, выдавать квоты, всячески SetProcessMemoryLimit для свежезапущенного CreateProcess... Но это ещё надо писать.

    К>Я думал, а вдруг есть какая-нибудь готовая, типа юниксовой nice или виндовой start /affinity-goes-here /priority-goes-here

    Насколько мне известно, подобное в данный момент существует только для серверных версий виндов. Вот здесь чел перечислил некоторые и даже описал работу с ThreadMaster.
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: jazzer Россия Skype: enerjazzer
    Дата: 19.06.14 09:55
    Оценка:
    Здравствуйте, netch80, Вы писали:

    N>-O2 это не один уровень на всех, это описанный в документации набор опций, которыми можно управлять и отдельно. Данная конкретная опция называется "strict-overflow", вот описание из info:


    о, ты уже написал про это Какой удар от классика
    jazzer (Skype: enerjazzer) Ночная тема для RSDN
    Автор: jazzer
    Дата: 26.11.09

    You will always get what you always got
      If you always do  what you always did
    Re[7]: Произвол компилятора
    От: k55 Ниоткуда  
    Дата: 19.06.14 09:58
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    Q>Здравствуйте, k55, Вы писали:


    k55>>Т.е. компилятор ошибочно исходит из того что "программист не хотел переполнения".


    Q>Компилятор резонно исходит из того, что «программист не хотел переполнения». Ведь обычно программисты не хотят переполнения, когда пишут код типа «x + 15» или «y * 2».


    Я бы ожидал от компилятора предупреждения о переполнении при заданных условиях, в конце концов он же "надзорный орган".
    Если есть желание — найдется 1000 возможностей.
    Если нет желания — найдется 1000 причин.
    Re[4]: каждый раз, когда вы пишете i++ + ++i...
    От: monah_tuk Пират http://htrd.su
    Дата: 19.06.14 10:00
    Оценка:
    Здравствуйте, Marty, Вы писали:

    M>Здравствуйте, Кодт, Вы писали:


    M>>>Лопата в переполнении?


    К>>В переполнении только черенок от лопаты, а вот лезвие — в шапке цикла.


    M>Мой cl v14 все нормально переполнил и напечатал


    M>Надо было написать, каким компилятором компилить


    Та же фигня на gcc 4.8.2

    UB на то и UB.
    Re[4]: каждый раз, когда вы пишете i++ + ++i...
    От: Fagin  
    Дата: 19.06.14 10:35
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    N>>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.


    К>Ну вот смотри, итерации 0, 1, 2 проходят успешно.

    К>На итерации 3 случилась беда. Дальше может быть всё, что угодно.
    К>Компилятору было угодно не форматировать винчестер, а крикнуть IDDQD.

    Хуже. Если в программе где-либо UB то "всё, что угодно" может произойти со старта программы.

    N>>Вот за такие шутки авторов gcc бить по наглым рыжим мордам.


    К>Горе от ума какое-то. Такая агрессивная преждевременная оптимизация, точно по заветам Дейкстры, вымостила дорогу в ад.


    Я так понимаю ты поддерживаешь "по наглым рыжим мордам". Такой же вывод я делаю из заголовка. И присоединяюсь.
    Re: каждый раз, когда вы пишете i++ + ++i...
    От: Xeor Россия  
    Дата: 19.06.14 10:51
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>winnie надыбал потрясающий пример, как из пня сделать крэй-1.


    Обещают в 4.8.3 пофиксить: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58143
    Re[6]: каждый раз, когда вы пишете i++ + ++i...
    От: netch80 Украина http://netch80.dreamwidth.org/
    Дата: 19.06.14 13:07
    Оценка:
    Здравствуйте, Don Reba, Вы писали:

    N>>Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору:

    N>>• выполнить операцию в соответствии с правилами платформы
    N>>• выполнить операцию в соответствии с общепринятой арифметикой в дополнительном коде
    N>>• выполнить операцию, отдав в качестве результата зависящее от реализации значение
    N>>• по зависящим от реализации правилам выполнить действие, предусмотренное для особой ситуации типа "целочисленное переполнение"
    DR>Предположение, что переполнения быть не может в принципе позволяет комиляютору соптимизировать выражение x * 2 / 2. А ограничения выше — нет.

    Это решается банально, если в выражениях следить за доменами возможных значений параметров. При этом x*2 может, в зависимости от того, откуда взялось, иметь разную семантику. Если это x*2 от пользователя, то оно само по себе уже типа int. Если же оно возникло от индексации массива short'ов, то оно является чем-то вроде int33_t.
    Для компилятора уровня gcc следить за такими вещами тривиально — он и в значительно более сложных материях много гитик.
    The God is real, unless declared integer.
    Re[5]: Произвол компилятора
    От: TarasB  
    Дата: 19.06.14 13:07
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    Q>При генерации кода для «++i» так его и оставляет, например.


    При генерации кода "++i" он мог бы запомнить этот атрибут и воткнуть туда проверку, чтоб i не стало больше 2. А, ну да, это ж оптимизация...
    Re[4]: каждый раз, когда вы пишете i++ + ++i...
    От: TarasB  
    Дата: 19.06.14 13:08
    Оценка:
    Здравствуйте, watchmaker, Вы писали:

    W>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?


    Не могу вообще понять логику компилятора.
    Re[9]: каждый раз, когда вы пишете i++ + ++i...
    От: TarasB  
    Дата: 19.06.14 13:16
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>Причём на 32-битной машине он просто немножко потупит, когда в своп залезет,


    А может и не немножко. Я вот забыл перевести миллиметры в метре, нажал на запуск, программа запросила в 1000 раз больше памяти, чем надо, система встала раком, ctrl+shift+esc не работает, потому что комбинация вызова диспетчера задач имеет тот же приоритет, что и вызов просмотрщика фотографий котиков, охеренно мудрое решение от микрософт, жму ресет, посылаю лучи ненависти микрософту.
    Re[6]: каждый раз, когда вы пишете i++ + ++i...
    От: netch80 Украина http://netch80.dreamwidth.org/
    Дата: 19.06.14 13:17
    Оценка:
    Здравствуйте, Tilir, Вы писали:

    N>>Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору:

    N>>* выполнить операцию в соответствии с правилами платформы
    N>>* выполнить операцию в соответствии с общепринятой арифметикой в дополнительном коде
    N>>* выполнить операцию, отдав в качестве результата зависящее от реализации значение
    N>>* по зависящим от реализации правилам выполнить действие, предусмотренное для особой ситуации типа "целочисленное переполнение"

    T>Без возможности сделать на этапе компиляции arithmetical reassociations (а ваши условия их убивают) вы платите производительностью за то, чего не заказывали. Это противоречит философии C++.


    http://www.rsdn.ru/forum/cpp/5654364.1
    Автор: netch80
    Дата: 19.06.14

    Вы почему-то предполагаете, что типы значений внутренних промежуточных действий должны быть точно такие же, как в случае явно определённых автором кода операций. Это ограничение насильственно и неадекватно.

    T> Убирать такие вещи под опции -- верное решение. Если вы сомневаетесь и производительность не критична -- подавайте -fwrapv и будет счастье.


    Если Вы посмотрите на исходное, увидите, что там не было никакой видимой причины выставлять wrapv поведение, никакой арифметики по модулю 2**32 не предполагалось. Это как раз совершенно неожиданный удар от компилятора туда, где не предполагалось защиты.
    The God is real, unless declared integer.
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: TarasB  
    Дата: 19.06.14 13:18
    Оценка:
    Здравствуйте, TarasB, Вы писали:

    TB>Здравствуйте, watchmaker, Вы писали:


    W>>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?


    TB>Не могу вообще понять логику компилятора.

    Ааа, дошло, указатель на функцию изначально нулевой, а вызов нулевого указателя — это УБ, поэтому компилятор вправе поставить туда что угодно? Весело получается. Компилятор вправе запустить ядерную ракету, если увидел в коде УБ, а что — всё по стандарту.
    Re[4]: каждый раз, когда вы пишете i++ + ++i...
    От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
    Дата: 19.06.14 13:18
    Оценка:
    Здравствуйте, watchmaker, Вы писали:

    W>Здравствуйте, netch80, Вы писали:


    N>>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.

    W>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?

    Это из-за того, что Do не инициализирована?
    Какой добрый компилятор
    Маньяк Робокряк колесит по городу
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: Don Reba Канада https://stackoverflow.com/users/49329/don-reba
    Дата: 19.06.14 13:31
    Оценка:
    Здравствуйте, TarasB, Вы писали:

    TB>Не могу вообще понять логику компилятора.


    Компилятор видит, что есть только один способ придать этому указателю валидное значение. Поэтому, когда компилятор видит вызов, он предполагает, что программист не использует неопределённое поведение вызова нулевого указателя, а присвоил валидное значение где-то ранее; затем, следующим проходом, компилятор инлайнит функцию.
    Ce n'est que pour vous dire ce que je vous dis.
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: watchmaker  
    Дата: 19.06.14 13:39
    Оценка:
    Здравствуйте, Marty, Вы писали:

    W>>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang? :)


    M>Это из-за того, что Do не инициализирована?


    Нет. Тем более, что в данном случае переменная Do гарантированно инициализирована перед запуском программы (значением NULL).
    Re[7]: каждый раз, когда вы пишете i++ + ++i...
    От: Tilir Россия http://tilir.livejournal.com
    Дата: 19.06.14 13:41
    Оценка:
    Здравствуйте, netch80, Вы писали:

    N>http://www.rsdn.ru/forum/cpp/5654364.1
    Автор: netch80
    Дата: 19.06.14

    N>Вы почему-то предполагаете, что типы значений внутренних промежуточных действий должны быть точно такие же, как в случае явно определённых автором кода операций. Это ограничение насильственно и неадекватно.

    Это совсем не банально. GCC для reassociations работает с SSA-представлением, там нет никакого различия между переменными которые программист завел лично и переменными, которые ему сделал компилятор как временные.

    Мало того, я могу эквивалентно переписать этот код, добавив туда явное определение:

    #include <iostream>
    
    int main()
    {
        int x = 27;
        for(int i=0; i < 10; ++i)
        {
            int t = i*1000000000;
            std::cout << i << " : " << t << " : " << x << std::endl;
            if(x==1) break;
            x = x%2 ? x*3+1 : x/2;
        }
    }


    Gimple для этого кода будет идентичен для приведенного в начале топика.
    Если компилятор будет себя вести не идентично в первом и втором случае -- это откроет двери ада.

    N>Если Вы посмотрите на исходное, увидите, что там не было никакой видимой причины выставлять wrapv поведение, никакой арифметики по модулю 2**32 не предполагалось


    Умножение явно переполняющее int -- достаточно серьёзное основание.
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: wander  
    Дата: 19.06.14 15:45
    Оценка:
    Здравствуйте, netch80, Вы писали:

    N>Что ты назвал "оптимизацией"? Такая опция, наоборот, устраняет оптимизации. Если ты её имел в виду, то используй, пожалуйста, более адекватные термины.

    N>Далее, с чего это тут дорога в ад?

    Вообще-то он с тобой согласился
    Т.е. ответ про "горе от ума" был не в противовес твоему, а в подтверждение. Фразой "дорога в ад" характеризовались разработчики компилятора, а не ты.

    PS.
    Все-таки, замечаю и тут, и в том числе и на себе: у программеров больное самолюбие, чуть что не так — сразу в штыки — надо с этим как-то бороться.
    Re[6]: каждый раз, когда вы пишете i++ + ++i...
    От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
    Дата: 19.06.14 15:56
    Оценка:
    Здравствуйте, watchmaker, Вы писали:

    W>>>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?


    M>>Это из-за того, что Do не инициализирована?


    W>Нет. Тем более, что в данном случае переменная Do гарантированно инициализирована перед запуском программы (значением NULL).


    Ну, я подразумевал, что она явно не инициализирована каким-то значением. То, что она инициализируется нулем, я в курсе. То, что разыменование 0 это UB, я тоже.
    Беда именно поэтому происходит, или я что-то не увидел еще?

    Кстати, баловался с реализацией property:
        #define CONTAINING_RECORD(address, type, field) ((type *)( \
                                                          (PCHAR)(address) - \
                                                          (ULONG_PTR)(&((type *)0)->field)))
    
    
        #define DECLARE_PROPERTY_RW(OWNERNAME, TYPE, NAME)                   \
        struct propclass_##NAME {                                                \
            DWORD dummy;                                                         \
            inline operator TYPE() {                                             \
                return CONTAINING_RECORD(this, OWNERNAME, NAME)-> get_##NAME ();    \
            }                                                                    \
            inline void operator=(const TYPE &src) {                             \
                CONTAINING_RECORD(this, OWNERNAME, NAME)-> set_##NAME (src); \
            }                                                                    \
        } NAME;
    
    struct SomeStruct{
                    UINT get_Uint( )
                       {
                        return (UINT)1;
                       }
                    
                    void set_Uint( UINT Uint
                                     )
                       {
                        // ...
                       }
                    
                    DECLARE_PROPERTY_RW(SomeStruct, UINT, Uint );
    };
    
    SomeStruct s;
    s.Uint = 3; // set_Uint() called


    Это тоже UB и компилятор может придумать все, что ему угодно? Или будет работать?

    Такая обманка не поможет?
        #define CONTAINING_RECORD(address, type, field) ((type *)( \
                                                          1+(PCHAR)(address) - \
                                                          (ULONG_PTR)(&((type *)1)->field)))
    Маньяк Робокряк колесит по городу
    Re[6]: каждый раз, когда вы пишете i++ + ++i...
    От: Кодт Россия  
    Дата: 19.06.14 15:59
    Оценка:
    Здравствуйте, watchmaker, Вы писали:

    W>Нет. Тем более, что в данном случае переменная Do гарантированно инициализирована перед запуском программы (значением NULL).


    Вот именно, что она инициализирована нулём. А разыменование нуля (вызов функции по этому адресу) — неопределённое поведение.
    Перекуём баги на фичи!
    Re: каждый раз, когда вы пишете i++ + ++i...
    От: netch80 Украина http://netch80.dreamwidth.org/
    Дата: 19.06.14 16:03
    Оценка: 45 (2)
    Здравствуйте, Кодт, Вы писали:

    Тут с коллегой прокопали ещё пару фактов.

    1. Кроме -fno-strict-overflow, проблема лечится -fno-aggressive-loop-optimizations. Последний появился в 4.8 и формально включен на всех уровнях (а реально — RTFS'ить надо).

    2. А вот ещё более суровый вариант:

    #include <stdio.h>       
    
    int main()
    {
        int i, j, x = 27;
        for (i=0, j=0; i < 10; ++i, ++j)
        {
            printf("%d : %d : %d\n", i, j*1000000000, x);
            if (x==1) break;
            x = x%2 ? x*3+1 : x/2;
        }
        return 0;
    }


    Запускаем (4.8 или 4.9, -O2):

    $ ./kodt 
    0 : 0 : 27
    1 : 1000000000 : 82
    2 : 2000000000 : 41


    это всё, оно остановилось.
    The God is real, unless declared integer.
    Re[8]: каждый раз, когда вы пишете i++ + ++i...
    От: netch80 Украина http://netch80.dreamwidth.org/
    Дата: 19.06.14 16:11
    Оценка:
    Здравствуйте, Tilir, Вы писали:

    N>>http://www.rsdn.ru/forum/cpp/5654364.1
    Автор: netch80
    Дата: 19.06.14

    N>>Вы почему-то предполагаете, что типы значений внутренних промежуточных действий должны быть точно такие же, как в случае явно определённых автором кода операций. Это ограничение насильственно и неадекватно.
    T>Это совсем не банально. GCC для reassociations работает с SSA-представлением, там нет никакого различия между переменными которые программист завел лично и переменными, которые ему сделал компилятор как временные.

    Вот это и не адекватно.

    T>Мало того, я могу эквивалентно переписать этот код, добавив туда явное определение:

    [...]
    T> int t = i*1000000000;
    T>Gimple для этого кода будет идентичен для приведенного в начале топика.
    T>Если компилятор будет себя вести не идентично в первом и втором случае -- это откроет двери ада.

    В этом примере по сравнению с предыдущим я согласен, что они должны вести себя идентично — потому что в любом случае i*1000000000 написал автор кода.
    А вот для сгенерированных компилятором промежуточных переменных (например, p[i] превращается в t1=p+i; *t1) это уже не так, нет никакой причины требовать от них принадлежности тем же доменам.

    N>>Если Вы посмотрите на исходное, увидите, что там не было никакой видимой причины выставлять wrapv поведение, никакой арифметики по модулю 2**32 не предполагалось

    T>Умножение явно переполняющее int -- достаточно серьёзное основание.

    Тут — да. А если константа в миллиард пришла из заголовочного файла цепочкой в 10 включений и константные вычисления — нет, недостаточно.
    The God is real, unless declared integer.
    Re[6]: каждый раз, когда вы пишете i++ + ++i...
    От: netch80 Украина http://netch80.dreamwidth.org/
    Дата: 19.06.14 16:12
    Оценка:
    Здравствуйте, wander, Вы писали:

    N>>Что ты назвал "оптимизацией"? Такая опция, наоборот, устраняет оптимизации. Если ты её имел в виду, то используй, пожалуйста, более адекватные термины.

    N>>Далее, с чего это тут дорога в ад?
    W>Вообще-то он с тобой согласился
    W>Т.е. ответ про "горе от ума" был не в противовес твоему, а в подтверждение. Фразой "дорога в ад" характеризовались разработчики компилятора, а не ты.

    Ну это было совсем не очевидно.

    W>PS.

    W>Все-таки, замечаю и тут, и в том числе и на себе: у программеров больное самолюбие, чуть что не так — сразу в штыки — надо с этим как-то бороться.

    Вообще-то я не вышел за пределы просьбы уточнения в заведомо сомнительном контексте.
    The God is real, unless declared integer.
    Re[7]: каждый раз, когда вы пишете i++ + ++i...
    От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
    Дата: 19.06.14 16:16
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    W>>Нет. Тем более, что в данном случае переменная Do гарантированно инициализирована перед запуском программы (значением NULL).


    К>Вот именно, что она инициализирована нулём. А разыменование нуля (вызов функции по этому адресу) — неопределённое поведение.


    Ну это не оправдывает "хитрый" компилятор, который видя нулевой указатель, подсовывает ему первую попавшуюся похожую функцию. Лучше бы уж честно вызывал код по адресу 0. Так бы просто SEGFAULT получился. Что за умники там этот clang и gcc пишут?
    Маньяк Робокряк колесит по городу
    Re[6]: каждый раз, когда вы пишете i++ + ++i...
    От: netch80 Украина http://netch80.dreamwidth.org/
    Дата: 19.06.14 16:18
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    N>>Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору:

    К>То есть, поменять undefined на unspecified, а если заплатить комитету стандартизации много пожертвований, то вообще на implementation-defined (чтобы авторы компиляторов были вынуждены внести эффекты ещ1 и в свою документацию).

    Ну в общем-то да. Только не заплатить, а шантажировать.
    И документировать лучше за счёт включения определений, проверяемых хотя бы через #if, какая локальная политика (например, что платформа с дополнительным кодом и переполнения в явно заданных юзером операциях приводят к усечению).

    К>>>Горе от ума какое-то. Такая агрессивная преждевременная оптимизация, точно по заветам Дейкстры, вымостила дорогу в ад.

    N>>Что ты назвал "оптимизацией"? Такая опция, наоборот, устраняет оптимизации. Если ты её имел в виду, то используй, пожалуйста, более адекватные термины.
    N>>Далее, с чего это тут дорога в ад?
    К>Оптимизация, конечно: вместо test+jnz сделал jmp

    Понятно, спасибо. А в случае последнего посланного мной примера (где добавлена переменная j), очевидно, сработало в противоположную сторону.
    The God is real, unless declared integer.
    Re[7]: каждый раз, когда вы пишете i++ + ++i...
    От: watchmaker  
    Дата: 19.06.14 16:38
    Оценка: :)
    К>Вот именно, что она инициализирована нулём. А разыменование нуля (вызов функции по этому адресу) — неопределённое поведение.

    Кодт, тебя КО укусил? Что там UB было написано изначально. Прямо теряюсь, когда мне объясняют моё же пример — как автор, я то уже знаю в чём там секрет
    Re[7]: каждый раз, когда вы пишете i++ + ++i...
    От: watchmaker  
    Дата: 19.06.14 16:45
    Оценка:
    Здравствуйте, Marty, Вы писали:

    M> То, что разыменование 0 это UB, я тоже.

    M>Беда именно поэтому происходит, или я что-то не увидел еще?
    Именно так.

    M>Это тоже UB и компилятор может придумать все, что ему угодно? Или будет работать?


    Используй стандартный offsetof. Ну и прочитай про границы его применимости. В принципе, на некоторых компиляторах offsetof реализован практически как и в твоём коде. Соответственно и работать, и ломаться будут они примерно в одинаковых ситуациях.
    Re[2]: каждый раз, когда вы пишете i++ + ++i...
    От: watchmaker  
    Дата: 19.06.14 16:52
    Оценка:
    Здравствуйте, Xeor, Вы писали:

    X>Обещают в 4.8.3 пофиксить: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58143


    Не, это совершенно другой баг. Там проблема в том, что UB просачивается через unreachable code. Формально это означает, что компилятор находит UB там где стандарт говорит, что поведение определено. Конечно, это не связано с исходным примером, где UB есть уже по стандарту.
    Re[6]: каждый раз, когда вы пишете i++ + ++i...
    От: netch80 Украина http://netch80.dreamwidth.org/
    Дата: 19.06.14 16:54
    Оценка:
    Здравствуйте, jazzer, Вы писали:

    J>Здравствуйте, netch80, Вы писали:


    N>>-O2 это не один уровень на всех, это описанный в документации набор опций, которыми можно управлять и отдельно. Данная конкретная опция называется "strict-overflow", вот описание из info:


    J>о, ты уже написал про это Какой удар от классика


    Да, но акценты у меня совсем другие. Компилятор не должен гнать бред только потому, что -O2.
    The God is real, unless declared integer.
    Re: каждый раз, когда вы пишете i++ + ++i...
    От: Шахтер Интернет  
    Дата: 19.06.14 17:11
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>... где-то форматируется маленький несчастный одинокий винчестер.


    Мне представляется, что в современных C/C++ слишком много UB не по делу.
    Это следовало бы пофиксить. Т.е. разумным образом доопределить семантику языка, что бы убрать ряд дурацких UB.

    В данном конкретном случае всё-таки следует доопределить свойства знаковых целочисленных типов, так что бы они реализовывали обычную 2'c арифметику.
    А всякую экзотику типа 1'c, 0'c и арифметику с насыщением сделать опциональной, если она поддерживается железом, с помощью соответствующих встроенных типов
    типа int_1c, int_0c, int_sat и.т.п.
    В XXI век с CCore.
    Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
    Re[3]: каждый раз, когда вы пишете i++ + ++i...
    От: netch80 Украина http://netch80.dreamwidth.org/
    Дата: 19.06.14 19:07
    Оценка:
    Здравствуйте, watchmaker, Вы писали:

    X>>Обещают в 4.8.3 пофиксить: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58143

    W>Не, это совершенно другой баг.

    Как минимум Comment 9 как раз про идентичный баг.
    The God is real, unless declared integer.
    Re: Не используйте int там где он не нужен
    От: Serg27  
    Дата: 20.06.14 09:07
    Оценка:
    У меня есть старое правило — не использовать int там где он не нужен. В частности для счетчиков циклов я всегда использую size_t. Это позволяет избежать кучу проблем, на которые можно наступить. Это код — как раз пример такой проблемы. Изменение типа счетчика цикла с int на unsigned int полностью убирает проблему — здесь
    Я тут не спорю об правильности/неправильности поведения компилятора и всяких других интересных вещах, о которых интересно поговорить/почитать. Я тут говорю о некоторых простых правилах, которые позволяют избежать проблем при написании производственного кода. Правило "Не используйте int там где он не нужен" одно из них. Все же арифметика unsigned величин значительно проще чем для signed и гораздо предсказуемее. В качестве иллюстрации приведу статью на codeproject — здесь В ней автор "оптимизирует" руками вычислительный алгоритм. Мне его методы показались подозрительными (это умеют делать компиляторы уже лет 40) и подозрительным показалось разница в производительности x86 и 64-битного кода. И действительно замена типа переменной с long long на unsigned long long сразу дала нужный прирост производительности. Можно посмотреть ветку "best optimization" в обсуждении этой статьи. (здесь)
    Re: каждый раз, когда вы пишете i++ + ++i...
    От: /aka/ СССР  
    Дата: 20.06.14 09:12
    Оценка: 14 (2)
    Здравствуйте, Кодт, Вы писали:

    К>Я немножко дополировал и заретушировал его.


    Убрал ретушь. gcc 4.8.2, вызываю так: g++ -O2 1.cpp -Wall

    #include <iostream>
    #include <stdio.h>
    
    int main()
    {
        for(int i=0; i < 10; ++i)
        {
            std::cout << i << " : " << i*1000000000 << " : " << std::endl;
        }
    }


    Нет варнинга, тихо компилируется и виснет в бесконечном цикле. И следующее:

    #include <iostream>
    #include <stdio.h>
    
    int main()
    {
        for(int i=0; i < 10; ++i)
        {
            printf ("%d %d\n", i, i*1000000000);
            std::cout << i << " : " << i*1000000000 << " : " << std::endl;
        }
    }


    Тоже компилируется без варнинга и тоже виснет.

    Однако же если не пользоваться богомерзкими стримами, оставить один тёплый ламповый printf:

    #include <iostream>
    #include <stdio.h>
    
    int main()
    {
        for(int i=0; i < 10; ++i)
        {
            printf ("%d %d\n", i, i*1000000000);
        }
    }


    Получаю то, что должен получить:

    aka@c8:/tmp$ g++ -O2 1.cpp -Wall
    1.cpp: In function ‘int main()’:
    1.cpp:8:43: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
             printf ("%d %d\n", i, i*1000000000);
                                               ^
    1.cpp:6:5: note: containing loop
         for(int i=0; i < 10; ++i)
         ^


    Плюсы зло
    Re[2]: Google C++ Style Guide
    От: Qbit86 Кипр
    Дата: 20.06.14 09:20
    Оценка: +1
    Здравствуйте, Serg27, Вы писали:

    S>У меня есть старое правило — не использовать int там где он не нужен. В частности для счетчиков циклов я всегда использую size_t. Это позволяет избежать кучу проблем, на которые можно наступить.


    «Document that a variable is non-negative using assertions. Don't use an unsigned type.» Google C++ Style Guide
    Глаза у меня добрые, но рубашка — смирительная!
    Re[3]: Google C++ Style Guide
    От: Serg27  
    Дата: 20.06.14 09:51
    Оценка: 5 (1) +1
    > «Document that a variable is non-negative using assertions. Don't use an unsigned type.» Google C++ Style Guide

    ну я прошел по ссылке. В приведенном ими примере использовать int нужно (с unsigned будет просто обычная ошибка программиста). Документировать, что величина должна быть положительной действительно нужно с помощью assert. С чем тут спорить? Как это соотносится с моим утверждением — "Не используйте int там где он не нужен" я не понял. У вас какая мысль то была?

    P.S.
    Вообще-то Вы дали ссылку на корпоративные правила. Их очень много в мире. Сочиняются они вполне по конкретным (в каждом случае) причинам. Не понимая этих причин использовать их как истину в последней инстанции довольно не умно. Ну, например, в этих же правилах написано "We do not use C++ exceptions. ". И что мы сейчас срач поднимем по этому поводу?
    P.P.S
    у них там есть довольно полное объяснение почему они написали "We do not use C++ exceptions.". Почитать это интересно, но это не означает, что в своем коде надо отказаться от exceptions, только потому что так сделал сам велиrий Google.
    Re[4]: каждый раз, когда вы пишете i++ + ++i...
    От: Xeor Россия  
    Дата: 20.06.14 10:07
    Оценка:
    Здравствуйте, netch80, Вы писали:

    N>Как минимум Comment 9 как раз про идентичный баг.


    Да и описание фикса подходит под проблему:

        * tree-ssa-loop-im.c (arith_code_with_undefined_signed_overflow):
        New function.
        (rewrite_to_defined_overflow): Likewise.
        (move_computations_dom_walker::before_dom): Rewrite stmts
        with undefined signed overflow that are not always executed
        into unsigned arithmetic.
    Re[2]: каждый раз, когда вы пишете i++ + ++i...
    От: Xeor Россия  
    Дата: 20.06.14 10:10
    Оценка:
    Здравствуйте, /aka/, Вы писали:

    A>Однако же если не пользоваться богомерзкими стримами, оставить один тёплый ламповый printf

    A>Получаю то, что должен получить

    Богомерзкие стримы вносят в тело цикла неявный выход через throw std::bad_cast. Из-за этого он становится слишком сложный для того анализа, что задействуется без лишних выходов, как в случае с printf.
    Re[2]: каждый раз, когда вы пишете i++ + ++i...
    От: Xeor Россия  
    Дата: 20.06.14 10:15
    Оценка:
    Здравствуйте, netch80, Вы писали:

    N>2. А вот ещё более суровый вариант

    N>это всё, оно остановилось.

    В обоих случаях gcc из-за знакового переполнения считает, что цикл будет выполнен не более 3 раз. Просто иногда (в первом случае) это приводит к выкидыванию проверки на превышение 10 итераций, а иногда (как в этом случае) gcc считает цикл пригодным для loop unroll-а, и делает анролл на 3 итерации, т.к. уверен что больше точно не будет.
    Re: каждый раз, когда вы пишете i++ + ++i...
    От: ononim  
    Дата: 20.06.14 12:41
    Оценка:
    ...Unbelivable Behaviour
    Как много веселых ребят, и все делают велосипед...
    Re[5]: каждый раз, когда вы пишете i++ + ++i...
    От: watchmaker  
    Дата: 20.06.14 14:39
    Оценка:
    X>Здравствуйте, netch80, Вы писали:

    N>>Как минимум Comment 9 как раз про идентичный баг.


    Безусловно, это так. Но это не меняет того, что ни описанная проблема, ни патч никоим образом не относятся к комментарию №9 — там просто нерелевантные рассуждения.

    Патч именно исправляет проблему с UB в мёртвом коде, который считается живым из-за ошибки в оптимизаторе циклов. Так, до патча компилятор выносил инвариант из цикла, который мог приводить к UB, но не приводил из-за того, что цикл никогда не выполнялся (а в исходном примере темы ситуация, грубо говоря, обратная — цикл-то выполняется). После патча компилятор по прежнему может выносить инвариант из цикла, но только предварительно преобразовав его в вариант без UB.

    А саму проблему, о которой рассказано в комментарии №9, там не чинят, ибо она уже давным-давно разрешена. И решение — использовать -fno-strict-overflow.

    Здравствуйте, Xeor, Вы писали:

    X>Да и описание фикса подходит под проблему:


    Ну как будто ты не видел ни одного репозитория с commit message вида "fix", "bugfix" или "small imrovement" Невозможно без контекста понять о чём они. А главное, по описанию они тоже подходят под проблему
    У gcc, конечно, с комментариями всё намного лучше, но всё равно контекст важен. Так что в случае сомнений нужно смотреть код. А из кода патча видно, что он даже не срабатывает на исходном примере этой темы.
    Re[6]: каждый раз, когда вы пишете i++ + ++i...
    От: Xeor Россия  
    Дата: 20.06.14 15:03
    Оценка:
    Здравствуйте, watchmaker, Вы писали:

    W>Патч именно исправляет проблему с UB в мёртвом коде, который считается живым из-за ошибки в оптимизаторе циклов. Так, до патча компилятор выносил инвариант из цикла, который мог приводить к UB, но не приводил из-за того, что цикл никогда не выполнялся (а в исходном примере темы ситуация, грубо говоря, обратная — цикл-то выполняется). После патча компилятор по прежнему может выносить инвариант из цикла, но только предварительно преобразовав его в вариант без UB.


    W>А саму проблему, о которой рассказано в комментарии №9, там не чинят, ибо она уже давным-давно разрешена. И решение — использовать -fno-strict-overflow.


    Да, действительно, там сначала запостили фикс, который починил бы эту проблему, но потом сделали по-другому, а я решил что довели до ума.
    Re[3]: Произвол компилятора
    От: ononim  
    Дата: 20.06.14 18:36
    Оценка:
    Q>Компилятор вместо «i < 10» статически подставляет «true», потому что полагает i не выходящем из диапазона [-2, 2].
    Q>А полагает он это, потому что может. Это его способ отрицания реальности. Если закрыть глаза на ++i, то переполнение исчезнет. Закрыть глаза — одна из возможных реализаций неопределённого поведения.
    Я в асм листинг не смотрел, но первое что в голову пришло, это примерно такой вот псевдокод:
    for(int i=0; i < 10; ++i)
    {
    //блаблабла
    i*= 1000000000;
    //блаблабла
    i/= 1000000000;
    //блаблабла
    }

    — типа сэкономили на памяти
    Как много веселых ребят, и все делают велосипед...
    Re[4]: каждый раз, когда вы пишете i++ + ++i...
    От: enji  
    Дата: 24.07.14 11:57
    Оценка:
    Здравствуйте, watchmaker, Вы писали:


    N>>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.

    W>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?

    Охренеть

    А из каких позывов компилер дергает EraseAll? Do не может быть 0, следовательно есть внешний код, который дергает NeverCalled?
    Re[4]: Произвол компилятора
    От: alzt  
    Дата: 26.07.14 18:37
    Оценка:
    Здравствуйте, k55, Вы писали:

    k55>Яснее не стало.

    k55>Какого лешего он полагает что "i не выходящем из диапазона [-2, 2]", если i изменяется только тут ++i??

    k55>Т.е. UB в любом месте кода, даже если оно случилось во временной переменной, может повлиять вооообще на все что угодно?


    Если ты пишешь
    x = y/z;
    if(!z)
      func(0);

    то компилятор видит, что z либо равно нулю и тогда произойдёт ужасное, либо z не равно нулю и тогда функция func никогда не будет вызвана и код можно выкинуть. Смысла делить на ноль всё равно нет, поэтому можно этот факт распространить дальше и оптимизировать другие числа, которые как-то зависят от z.
    Re[3]: каждый раз, когда вы пишете i++ + ++i...
    От: Vain Россия google.ru
    Дата: 27.07.14 09:38
    Оценка:
    Здравствуйте, Xeor, Вы писали:

    A>>Однако же если не пользоваться богомерзкими стримами, оставить один тёплый ламповый printf

    A>>Получаю то, что должен получить
    X>Богомерзкие стримы вносят в тело цикла неявный выход через throw std::bad_cast. Из-за этого он становится слишком сложный для того анализа, что задействуется без лишних выходов, как в случае с printf.
    ещё один фейл в копилку уродливых стримов
    [In theory there is no difference between theory and practice. In
    practice there is.]
    [Даю очевидные ответы на риторические вопросы]
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.