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, это несколько смешней, но бесконечного цикла воспроизвести не удалось. Я должен взять нечто совсем древнее для этого?
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.