Шокирующая правда про undefined behavior в C/C++ II
Ничо не понял. Условие цикла же только i<16, а i изменяется только инкрементом i++, как цикл может быть бесконечным?
там баг: после инкремента берется a[i] еще до проверки i < 16. А gcc думает, что раз проверки нет, то уже «все проверено до нас» © и вообще не делает эту проверку потом (т.е. условие окончания цикла). Т.е. кроме одного вылезания за границу массива, у нас еще и бесконечный цикл.
непонятно зачем такую фичу вводить таким способом, который поломает то, что работало до, хотя и ошибочно работало.
Кстати, насчёт диск отформатировать:
int f(int i) {
int a[10];
if(i < 10) { exec("format c:"); }
return a[i];
}
int main {
f(15);
}
По идее оптимизация, подобная указанной топикстартером, может заставить эту программу соптимизироваться до безусловного форматирования диска. Компилятор может рассуждать: "возвращаем a[i], значит i < 10 иначе было бы UB. Значит можно не проверять условие в if.".
Компилятор может рассуждать: "возвращаем a[i], значит i < 10 иначе было бы UB. Значит можно не проверять условие в if.".Нет, дружище, из exec возврата не происходит, так что компилятор не может так рассуждать. Вот system, который ты имел в виду — другое дело (но только если компилятору откуда-то известно, что из system _всегда_ происходит возврат (EDIT: что, кстати, неверно, так как вызванная команда может убить породивший её процесс.
Вот компилятор думает:
"если i < 10, то надо сделать exec. А если i>=10, то в следующей строке будет UB, так что всем пофиг, что я сделаю. Вот я и сделаю тоже exec так меньше будет операторов".
Но даже с exec, разве он не может рассуждать так:
Рассмотрим случай, когда i >= 10. Условие if не выполняется, так что управление дойдёт до строки a[ i ], где мы получаем UB. Значит можно предполагать, что на вход функции никогда не будет подаваться число i >= 10. Значит можно не проверять условие if.
Да, хм, наверно я поторопился и был неправ. Сорри
Не, я говорил не про текущую оптимизацию, а про _подобную_, просто не очень удачно сформулировал. Под подобной подразумевал такой "пофигизм" компилятора - жесткая оптимизация путём введения неожиданного поведения в случае UB.
в Ядре как-то был классный баг с зацикливанием перебора страниц в иноде если присутствует самая последняя страница с индексом ULONG_MAX, после неё он переходил на 0 и начинал всё сначала.
и я когда там переделывал итераторы пэйдж-кэша слегка заебался впихивать эту проверку переполнения, пару дней перебирал варианты
непонятно зачем такую фичу вводить таким способом, который поломает то, что работало до, хотя и ошибочно работало.бесконечный цикл или гарантированное падение просто БЕСКОНЕЧНО лучше незаметно подпорченного байта.
Нет, дружище, из exec возврата не происходитНа самом деле иногда происходит, для этого собственно в fork+exec всегда после exec делают exit(-1);
RETURN VALUE
The exec functions only return if an error has have occurred. The return value is -1, and errno is set to indicate the error.
(компиляторам интересны noreturn-функции, такие как abort (гарантированно никогда не возвращающие управление потому что это позволяет выкидывать следующий за ними код как недостижимый)
Лучше бы NetHack запускал...
/* C Compatible Compiler Preprocessor (CCCP)
... [тут текст ранней версии GPL]
In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them. Help stamp out software-hoarding! */
...
/*
* the behavior of the #pragma directive is implementation defined.
* this implementation defines it as follows.
*/
do_pragma
{
close (0);
if (open ("/dev/tty", O_RDONLY) != 0)
goto nope;
close (1);
if (open ("/dev/tty", O_WRONLY) != 1)
goto nope;
execl ("/usr/games/hack", "#pragma", 0);
execl ("/usr/games/rogue", "#pragma", 0);
execl ("/usr/new/emacs", "-f", "hanoi", "9", "-kill", 0);
execl ("/usr/local/emacs", "-f", "hanoi", "9", "-kill", 0);
nope:
fatal ("You are in a maze of twisty compiler features, all different");
}
Теперь станет ещё более очевидно, что undefined behaviour это не просто segfault, а _действительно_ абсолютно любое действие со стороны программы - она может хоть зависнуть, хоть диск отформатировать, хоть пользователя задушить шнуром от мышки.Напиши еще код, удушающий пользователя шнуром от мышки плз. Думаю, многим пригодится
Кстати, насчёт диск отформатировать:
да, тоже подумал, что такой компилятор был бы на вес золота
бесконечный цикл или гарантированное падение просто БЕСКОНЕЧНО лучше незаметно подпорченного байта.Только не на стороне пользователя. Лучше плохо работающая программа, чем вообще не работающая. Проверку правильности результата можно и внешним способом организовать.
ну вот опять. Непадающая != работающая. Замаскированные баги пользователю не нужны. Чувствую завязку разговора про ассерты
тем более, это похоже, что багофича компилятора с юмором небольшим: типа это как бы WAT поведение, которое получается как логичное (?) следствие корректности программы, но фишка в том, что оно еще может быть полезно при отладке, так-то это чисто оптимизация вроде бы, вряд ли это было by design именно для отладки.
Непадающая != работающая.Работу программы можно представить как функцию со входом и выходом.
Обозначим правильную программу как out P(in) = F(in тогда:
программа которая портит байт, оставаясь более-менее работоспособной, можно представить как out P(in)=G(F(in где G - это функция, которая искажает результат на меньше, чем 20%(условно). Где 20% искажения происходит, или по попыткам (каждая пятая возвращает неправильный результат или по объему результата (в каждом результате искажается меньше 20% бит в какой-то разумной топологии или какая-то смесь двух предыдущих сценариев,
программа которая циклится или падает на каждый чих представляется как out P(in) = for(;;); или out P(in)=exit(error_code которую очевидно можно выкинуть, т.к. она не отличима от уже много раз написанной такой же до этого.
Соответственно, первая лучше чем вторая, т.к. в первом случае, из функции G(F(in в значительном кол-ве случаев можно получить функцию F, используя композицию out P(in)=AntiG(G(F(in. И такая композиция часто доступна пользователю.
падающая на каждый чих — это преувеличение, такая не должна была пройти тестирование. в твоем рассмотрении просто завышен отрицательный эффект падения (т.е. чистосердечного признания, что результат не может быть получен для данных входных данных/в данный момент времени/при текущем положении звезд). Я бы вот сказал, что тихая выдача неправильного ответа — это -мыльён в карму. -мыльён * 20% ~ -200 000 в среднем. В общем, это в к вопросу о функции предпочтений клиента. Если речь о космосе там, то будет как у меня, если это контактик, где пускай лучше сиськи леночки будут видны в углу экрана, чем придется напрягать фантазию, то да, падения нехороши.
Я бы вот сказал, что тихая выдача неправильного ответа — это -мыльён в карму. -мыльён * 20% ~ -200 000 в среднем.Когда тоже самое делает человек, а не программа - тебя почему-то это не напрягает. Почему такие двойные стандарты?
Если брать форум, то тут почти все пишут с грамматическими ошибками, неправильно используют термины и т.д. - тебя это вроде не напрягает, по крайней мере, ты едва ли считаешь, что все сообщения где есть грамматические ошибки должны заменяться на сообщение: Грамматическая ошибка (текст сообщения не покажу).
В чем разница?
Если речь о космосе там, то будет как у менят.е. ты предпочтешь находясь в космосе получить ответ от двигателя - я выключаюсь, обратитесь к разработчику, чем хоть как-то работающий двигатель?
Зависит от того, что программа делает. Например если это CAM система, которая считает программу для ЧПУ, то пусть она лучше упадет, чем накосячит, потому что косяк может привести к реальным потерям в будущем.
Когда тоже самое делает человек, а не программа - тебя почему-то это не напрягает. Почему такие двойные стандарты?Двухсотлетний человек ладно, я спать, завтра подумаю
Зависит от того, что программа делает. Например если это CAM система, которая считает программу для ЧПУ, то пусть она лучше упадет, чем накосячит, потому что косяк может привести реальным потерям в будущем.Такие программы можно представить как:
out P(in) = отрежь(отмерь(in где отрежь - это действия, которые приводят к необратимым деструктивным последствиям, а отмерь - действия с обратимыми последствиями.
Для таких программ желательно, чтобы пользователь имел доступ к границе между отрежь и отмерь, или другими словами, чтобы программа представлялась как: out P(in) = отрежь(U(отмерь(in где U - это произвольная пользовательская функция (например, U=попробуй-тысячу-раз-и-проверь-правильность)
При таком представлении хочется, чтобы отмерь не падала при возникновении ошибки, а отрежь - по умолчанию падала, но чтобы при этом была возможность запустить отрежь, несмотря на ошибки.
Так же отмечу, что при таком представлении: код отрежь на несколько порядков меньше и реже меняется, чем код отмерь (и соответственно, его намного проще написать без ошибок).
Чувствую завязку разговора про ассертыAssert хорош в виде монады, которая продолжает выполнение кода, но при этом помечает конечный результат функции, как полученный с нарушением такого-то assert-а.
т.е. лучше на экране увидеть:
результат: 42 (внимание! были нарушены следующие ассерты: bla-bla)
чем
результат: Value does not fall within the expected range exception
т.е. ты предпочтешь находясь в космосе получить ответ от двигателя - я выключаюсь, обратитесь к разработчику, чем хоть как-то работающий двигатель?Находясь в космосе я предпочту двигатель (системы навигации который при возникновении сбоя переключается на запасной, а не впечатывает меня в ближайшее космическое тело, проверяя, а вдруг он все-таки правильный ответ посчитал.
т.е. ты предпочтешь находясь в космосе получить ответ от двигателя - я выключаюсь, обратитесь к разработчику, чем хоть как-то работающий двигатель?Конечно да.
Если брать форум, то тут почти все пишут с грамматическими ошибками, неправильно используют термины и т.д. - тебя это вроде не напрягает, по крайней мере, ты едва ли считаешь, что все сообщения где есть грамматические ошибки должны заменяться на сообщение: Грамматическая ошибка (текст сообщения не покажу).Разница в том, что человек а) может заметить и исправить ошибку из общих соображений, в то время как компьютеры соображают на уровне амебы б) грамматические ошибки не влекут почти никаких последствий большую часть времени с) человека все равно не переделать, но не надо переносить его откровенные слабости на другие системы.
В чем разница?
Соответственно, первая лучше чем вторая, т.к. в первом случае, из функции G(F(in в значительном кол-ве случаев можно получить функцию F, используя композицию out P(in)=AntiG(G(F(in. И такая композиция часто доступна пользователю.Ты опять бредишь и это тем более удивительно, что у тебя вроде бы не филологическое образование и ты должен понимать, что ошибки не есть какие-то 20% искажения - любая ошибка - это 100% искажение и невозможно предсказать, как она повлияет на общий результат. Какие для данной программы установлены критерии оценки результата.
Я об этом, собственно.
Влияние остальные ошибок намного лучше предсказывается, т.к. их влияние локализовано отдельной функций, отдельным объектом и т.д.
который при возникновении сбоя переключается на запаснойа если нет запасного? или он падает на той же ошибке?
Не стыдно такое писать? Изменение одного байта может привести к каким угодно последствиям в неопределенном будущем.
Не стыдно такое писать? Изменение одного байта может привести к каким угодно последствиям в неопределенном будущем.И как из этого утверждения следует отсутствие предсказуемости?
В контексте темы все просто — оптимизации делаются для того, чтобы быстрее работали правильные программы, а не для того, чтобы сохранять поведение неправильных. Специально для этого (в значительной степени) существуют языковые стандарты. Хотите, чтобы программа вела себя ровно как написано? Включите -O0 (без гарантий, просто в gcc очень мало при -O0 происходит, но с каждым годом это меняется или включите строго интерпретацию в яве, проиграйте разы в производительности. Хотите, чтобы быстро было? Пишите в соответствии со стандартом, скажите компилятору про оптимизации. Хотите и того, и другого, и чтобы компилятор еще угадывал, как вы имели в виду, но не написали? Напишите свой компилятор или пройдите на хуй из профессии.
В контексте темы все просто — оптимизации делаются для того, чтобы быстрее работали правильные программы, а не для того, чтобы сохранять поведение неправильных.Проблема только в том, что большие программы 100%-но правильными не бывают.
Пишите в соответствии со стандартом, скажите компилятору про оптимизации.Мне просто интересно, ты когда-нибудь компилировал хоть один крупный проект с максимальным уровнем предупреждений гцц?
Я вот знаю, что, например, boost, qt, qxt с высоким уровнем предупреждений не соберутся. А ты про оптимизации и стандарты.
Мне просто интересно, ты когда-нибудь компилировал хоть один крупный проект с максимальным уровнем предупреждений гцц?Я вот знаю, что, например, boost, qt, qxt с высоким уровнем предупреждений не соберутся. А ты про оптимизации и стандарты.вот только не надо -Werror лепить в общую кучу. включение его подефолту я его считаю большим злом — гавнокодеры вместо того чтоб подумать начинают судорожно менять код чтоб варнинг исчез.
линусово ядро с нормальными конфигами собирается почти всегда без варнингов, но они там конечно не все включены по дефолту.
warning'и опять же в основном не про несоответствия стандарту, а еще про всякие другие вещи типа сравнения знаковых-беззнаковых и прочая.
другие вещи типа сравнения знаковых-беззнаковыхА это типа не ошибка? В 95% случаев это повод задуматься над кодом.
ну я к тому, что это не к "пишите по стандарту", это больше. И к оптимизациям это не имеет отношения, потому что поведение известно.
А это типа не ошибка? В 95% случаев это повод задуматься над кодом.в 95% случаев это не ошибка
в 95% случаев это не ошибкав 95% случаев ошибка в другом месте
У нас (Яндекс.Карты) все проекты собираются с -Wall -Wextra -Werror. В исключительных случаях добавляем опции -Wno-*.
Про boost точно вранье.А с -Wunreachable-code?
По теме - лично меня не раз спасали от дурацких труднонаходимых ошибок предупреждения о сравнении беззнакового со знаковым.
А с -Wunreachable-code?это не ошибка
add_definitions(
-Wall
#-Weffc++ # breaks Qt
-Wstrict-null-sentinel
#-Wold-style-cast # breaks Qt
-Woverloaded-virtual
-Werror
-Wextra
#-Wshadow # breaks QxtCommandOptions
#-pedantic # breaks Qt and Qxt
-Wctor-dtor-privacy
-Wnon-virtual-dtor
-Winit-self
#-Wunreachable-code # breaks boost and Qt
#-Wconversion # breaks Qt
)
Такие дела.
это не ошибкаЭто может быть ошибкой и этого нужно избегать.
Это может быть ошибкой и этого нужно избегать.портабельный или статически настраиваемый код не может обойтись без недостижимого кода,
иначе он будет состоять на половину из макроопераций (в случае c++ из больных шаблонов из ада)
иначе он будет состоять на половину из макроопераций (в случае c++ из больных шаблонов из ада)Лично я знаю два способа написать портабельный код:
1. Использовать пресловутые макрооперации
2. В зависимости от архитектуры подключать разные файлы с имплементацией.
Оба эти способа подразумевают, что мёртвый код в любом случае в компиляцию не попадает.
Если ты знаешь другие способы, чтобы, скажем, в винде использовать HANDLE и _beginthreadex, а в юниксах — pthread_t и pthread_create, поделись.
Конечно, в основном тот же гцц. Да, я считаю, что в нормальном проекте предупреждения с высоким уровнем true positive и -Werror должны быть включены по умолчанию. Да, я считаю, что программист должен знать свои инструменты и отличать предупреждения, которые часто верны, от тех, которые нет. У больших, близких к системным проектов всегда будет свой набор включенных предупреждений. Ломается код, кривое левое предупреждение? Возьмите с vozbu пример, зашлите багрепорт.
Хотя, конечно, с точки зрения компиляторщика предупреждения — достаточно занудная вещь, требуется аккуратность, а качественно сделать отнюдь не всегда можно. Например, нормальный -Wuninitialized требует хорошего анализа, в результате выдаваемые предупреждения с -O0 и -O2 могут отличаться, пользователей это вымораживает. Вроде бы это пофиксили уже.
Оставить комментарий
procenkotanya
Транк GCC научили выводить ограничения на диапазон переменных в зависимости от границ массивов, которые ими индексируются. Таким образом, если имеется следующий код:GCC замечает, что после cur+=a[i] i гарантированно меньше 16, что делает цикл бесконечным. Инкремент i в цикле конечно никуда не девается, и, таким образом, циклы, ранее содержащие сравнительно безобидные off-by-one баги будут фигачить до сегфолта. Caveat programmator!
К сожалению, в данный момент соответствующего варнинга нет (его не так просто запрогать).