LINUX.ORG.RU

В C++ становится меньше UB

 , ,


2

5

Привет, чят!

В общем, сабж. Начиная с C++26 тривиальные бесконечные циклы больше не будут неопределённым поведением.

Пример такого цикла:

int main() {
  while(true) ;
}

При этом, если в теле цикла содержится выражение или условие цикла не является константным выражением, приводимым к true, то такой цикл всё ещё будет вызывать неопределённое поведение программы:

int main() {
  // UB
  while(true) {
    (void) "Hello LOR!";
  }
}
int main() {
  // UB
  bool done = false;
  while(!done) {}
}
int main() {
  // Тоже UB
  while (true) if constexpr (false) break;
}

И так далее. Как всегда, в C++ что-то починили, по сути ничего не починив. Так и живём, чят.

Ссылка с подробностями: https://www.sandordargo.com/blog/2026/09/16/cpp26-trivial-infinite-loops



Последнее исправление: yorshka (всего исправлений: 1)
Ответ на: комментарий от AndreyKl

Хороший слив у тебя получился.

Но я еще скину кусок от ИИ, даже если ты сам - дундук, то может другим будет интересно разобраться.

Про «бесконечный цикл не заканчивается»

Интуитивно это, конечно, верно. Но из этого не следует, что стандарт обязан гарантировать именно машинное зацикливание.

Главная ошибка Андрея в формулировке:

бесконечный цикл неотличим от завершившейся программы

Нет, отличим. Бесконечный цикл:

  • удерживает процесс живым;
  • может удерживать mutex;
  • занимает поток;
  • может влиять на другие потоки;
  • может позволять обслуживать прерывания;
  • может удерживать устройство в определённом состоянии.

Именно поэтому претензия к старому правилу вполне разумна: оно объявляло осмысленное намерение программиста чем-то, что оптимизатор может трактовать как невозможное.

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

Это рациональная причина, но следствие действительно получилось очень неинтуитивным.

Про embedded

Здесь оппоненты, которые говорят про bare metal, правы. Пустой вечный цикл может быть нормальным:

  • аварийным остановом;
  • последней инструкцией после fatal error;
  • ожиданием прерывания;
  • защитой от выхода из обработчика.

Но while (true); не является переносимым выражением «усыпить CPU до прерывания». На hosted-платформе C++26 его семантика описана через std::this_thread::yield(), а для freestanding-реализаций замена на yield оставлена implementation-defined. 2 (isocpp.org)

То есть в embedded-коде лучше использовать специальный примитив платформы:

for (;;) {
    __WFI();       // условный ARM-пример
}

или аппаратную инструкцию halt/sleep, а не надеяться на смысл пустого C++-цикла.

На обычной ОС для ожидания нужны condition_variable, atomic::wait, системные wait-функции и т. п. Пустой цикл там обычно означает прожигание CPU. Если нужен именно spin, это стоит выразить явно — например, через атомик и платформенный pause/yield.

Про UB

Firkax в сообщении №37 путает термины.

  • Implementation-defined behavior — реализация выбирает вариант и обязана его документировать.
  • Unspecified behavior — выбирается один из разрешённых вариантов, документировать выбор не обязаны.
  • Undefined behavior — стандарт не предъявляет требований к результату выполнения.

UB исторически не означало «вариант зависит от архитектуры, но в целом будет разумным». Это ближе к unspecified или implementation-defined behavior.

При этом лозунг «при UB компилятор может сделать вообще что угодно» тоже не надо понимать буквально как генератор случайных чисел. Реальный компилятор остаётся конкретной программой со своими багами и эвристиками. Но с точки зрения переносимого C++-кода никаких гарантий уже нет, и оптимизатор вправе строить выводы вроде «такого пути в корректной программе не бывает».

Про C и пропущенный return

Здесь yorshka по существу прав.

В C:

int f(void) {
    puts("f called");
}

int main(void) {
    f();   // результат не используется
}

имеет специальную разницу с C++. Если значение f() не используется, достижение конца функции в C не даёт того же UB, что в C++. Если значение используется — уже проблема.

В C++ достижение конца функции, возвращающей значение, кроме специальных случаев вроде main, является UB. Предупреждение компилятора желательно, но наличие предупреждения не превращает правило в определённое поведение.

То, что на одной платформе такой код продолжит исполнение в следующую функцию, а на другой упадёт, как раз хорошо иллюстрирует разницу между исходным кодом и машинным кодом.

wandrien ★★★★
()
Ответ на: комментарий от wandrien

В C++ достижение конца функции, возвращающей значение, кроме специальных случаев вроде main, является UB.

«Да что ты, чёрт возьми, такое несёшь?» ©

anonymous
()
Ответ на: комментарий от anonymous

Хороший язык, и правила интересные. :)

wandrien ★★★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

По-моему ты сам своими примерами только подтвердил моё заявление.

Например, деление на 0 - UB, потому что эффект деления на ноль сознательно выведен за пределы модели исполнителя, C++ не хочет иметь с этими деталями реализации процессора и ОС никакого дела.

Я об этом и писал: зависит от архитектуры и не документировано, и не планируется к документированию. Эта зависимость расположена вне языка. Но какое-то поведение там есть. А вот то, что компилятор, увидев деление на 0, может начать портить код - придумали уже позже.

Поэтому реальная проблема не в том, что «UB плохо, давайте уберем из компилятора все UB», а в том, что какого черта вообще бесконечный цикл является UB.

Это тоже, да. Но это другой вопрос, я его в том сообщении не касался.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Я об этом и писал: зависит от архитектуры и не документировано, и не планируется к документированию. Эта зависимость расположена вне языка. Но какое-то поведение там есть.

Нет. В том-то и дело, что в рамках абстрактной машины (модели исполнителя) НИКАКОГО поведения там нет. Оно не «какое-то», а просто невыразимое в понятиях модели исполнителя.

Как вопрос о том, «какое было время до того как возникло время». Никакого даже гипотетического ответа у физики нет.

wandrien ★★★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

В рамках абстрактной машины, как я уже писал, даже результат сисколла невозможно определить, но он от этого UB не становится почему-то. Фиксация на ней это тоже более позднее изобретение. Да, знания языка недостаточно чтобы понять что произойдёт после деления на ноль, в спецификации языка это не указано и не будет указано. Зато это можно узнать из спецификации на процессор и ОС, куда и следует отправиться. А в «современном» понимании UB (особенно шланговом, но и gcc к сожалению уже тоже местами) - раз тут UB то мы вам так всё испортим, что и документация на нижележащие слои не поможет. Вот это уже отвратительный подход.

firkax ★★★★★
()
Ответ на: комментарий от firkax

А в «современном» понимании UB (особенно шланговом, но и gcc к сожалению уже тоже местами) - раз тут UB то мы вам так всё испортим, что и документация на нижележащие слои не поможет. Вот это уже отвратительный подход.

Ну с этим я согласен. Выше пример про возврат из функции - очень типичный. Программист ЗНАЕТ, что это неверный код. Компилятор ЗНАЕТ, что это неверный код. Стандарт ЗНАЕТ, что это неверный код. Вместо того, чтобы остановить компиляцию, компилятор с дефолтными настройками просто даёт варнинг и компилирует мусор. Идиотизм.

Из того, что сейчас натолкали в UB, требуется в явном виде выделить ОШИБОЧНОЕ поведение и заставить компиляторы детектировать его.

wandrien ★★★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

UB, когда его придумали изначально, означало всего лишь то, что реализация может варьироваться между разными архитектурами, компиляторами и версиями компилятора + важное уточнение, что эти вариации никто не планирует документировать.

Это не так.

Это так, с той поправкой, что некоторые отклонения документируются в конкретных реализациях.

3.5.3
undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which
this document imposes no requirements
Note 1 to entry: Possible undefined behavior ranges from ignoring the situation completely with unpredictable
results, to behaving during translation or program execution in a documented manner characteristic of the
environment (with or without the issuance of a diagnostic message), to terminating a translation or execution
(with the issuance of a diagnostic message).

to behaving during translation or program execution in a documented manner


А Undefined behavior - это нарушение согласованного состояния модели исполнителя.

4.2 же.

Добрая половина undefined behavior приходится не на фазу выполнения, а на фазу трансляции:

6.4
Lexical elements
...
 If a ’ or a " character matches the last category, the behavior is undefined.

6.4.3
Identifiers
...
Any identifiers that differ in a significant character are different identifiers. If two identifiers differ
only in nonsignificant characters, the behavior is undefined.


6.4.8
Header names
...
If the characters ’, \, ", //, or /* occur in the sequence between the < and > delimiters, the behavior
is undefined. Similarly, if the characters ’, \, //, or /* occur in the sequence between the " delimiters,
the behavior is undefined.77)

...

6.4.10
Comments
...
#include "//e" // undefined behavior
LamerOk ★★★★★
()
Ответ на: комментарий от wandrien

Ну с возвратом есть одна ситуация, где оно может получиться не по злому умыслу: если сверху расположен какой-то нетривиальный алгоритм, по которому компилятор не может понять, может ли ход выполнения дойти до конца или нет. Ну например switch с return-ами из каждого case, но без default, про который программист знает что default-ветка точно не потребуется, а компилятор нет. И тут два варианта - либо компилятор дописывает в конец не сделанный return на всякий случай (или int 3 хотя бы), либо экономит этот байт (или больше, может ещё освобождение стека быть нужно) из предположения что раз нет return значит программист решил что выполнение сюда не попадает.

На мой взгляд, экономия одного или нескольких байт не стоит того, лучше оставить ловлю потенциальных багов (я даже в свитчах как описан выше всегда ставлю что-то в default, например exit(-1) с логом ошибки), но авторы шланга, во-первых, решили байт всё-таки сэкономить (но почему-то не на macos-таргете, как выше выяснилось), а во-вторых пойти ещё дальше, и экономить его даже тогда, когда компилятору очевидно что выполнение туда добраться может.

Хотя, учитывая поведение void-функций, всё вышеописанное выглядит всё равно неконсистентно (там ret добавляется вне зависимости от наличия return;), так что можно считать что это в целом выдуманное на ровном месте ub. gcc для c++ кода добавляет return сам, а в msvc5 вроде это ошибкой компиляции по по крайней мере по умолчанию было.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 4)
Ответ на: комментарий от LamerOk

Добрая половина undefined behavior приходится не на фазу выполнения, а на фазу трансляции:

Хорошая поправка.

Думаю, тогда нужно разделить на категории всё то, что умники натолкали в undefined behavior. Как минимум:

  • Состояния за пределами описания средствами абстрактной машины, такие как обращение к освобожденному указателю.
  • То, что должно быть очевидным erroneous behavior, но какого то черта попало в undefined behavior.
  • Кейсы, когда комитетчики сами себе насоздавали неоднозначностей с парсингом текста и отказались их решать.
wandrien ★★★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от firkax

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

Таким образом «switch с return-ами из каждого case, но без default» - позволяет потоку управления провалиться вниз и требует от программиста какой-то реакции. Например, программист может поставить там abort().

У меня так выбраны трейд-оффы.

wandrien ★★★★
()
Ответ на: комментарий от unC0Rr

С хера ли вдруг нет валидного способа скомпилировать while (1); Можно разными способами это сделать, но конструкция вполне однозначная

anonymous
()
Ответ на: комментарий от anonymous

Да. while (1); это очевидный пример bottom-типа, ака noreturn в D, ака [[noreturn]] в C++, ака never в TypeScript.

Вопрос «С хера ли вдруг нет валидного способа скомпилировать это» абсолютно разумный и вскрывает реальную проблему в спецификации языка, а вовсе не в логике программистов, язык использующих.

wandrien ★★★★
()
Ответ на: комментарий от AndreyKl

поведение программы в бесконечном цикле не отличимо от закончившейся программы.

А процессор жрать кто будет? Пушкин? Я может хочу чтобы она жрала процессор(не давала частотам снижаться например) и для этого и бесконечный цикл влепил? Или мне нужно, чтобы только по Ctrl-C программу завершали, а не она сама(явное действие пользователя для завершения). Вполне отличимо даже на глаз, а про «не отличимо» это сродни философствованиям о полезном действии.

Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

Оно отличимо хотя бы в том, что бесконечный цикл не приводит к вызову обработчиков atexit(), не вызывает деструкторы при раскручивании стека и т.п.

Чел там чушь написал и отказался читать описание того, в чем он не прав.

wandrien ★★★★
()
Ответ на: комментарий от firkax

которому компилятор не может понять, может ли ход выполнения дойти до конца или нет.

llvm понять как раз может. у него есть machine basic block у которого есть «выходы». если выходов нет и последняя операция не return и не call в функцию с noreturn эта ситуация легко отслеживается. даже ошибку можно сделать.

ckotctvo
()
Ответ на: комментарий от wandrien

тогда нужно разделить на категории всё то, что умники натолкали в undefined behavior.

Смысл? Умники напихали туда ровно то, что ведёт себя по разному в двух и более имплементациях и никто не будет править свою.

Туда входит всё буквально от конкатенации имени макросом препроцессора до особенностей арифметики всех видов.

Кейсы, когда комитетчики сами себе насоздавали

Прекрати быть firkax’ом, фантазирующим о что-то создающих "комитетчиках". Комитет только согласовывает наибольший общий делитель для существующих реализаций. Он ничего не создаёт. У них давно уже правило - для принятия запроса на изменение должна быть хоть одна реализация.

LamerOk ★★★★★
()
Последнее исправление: LamerOk (всего исправлений: 4)
Ответ на: комментарий от LamerOk

Он ничего не создаёт.

Он создаёт ФОРМАЛЬНОЕ ОБОСНОВАНИЕ.

Ты плохо понимаешь, как работают системы с обратной связью.

Как только нечто зафиксировано правилом, правило становится обоснованием.

В результате доходит до абсурда типа такого:

  • В документации musl было описано различие в поведении между musl и glibc, не покрываемое стандартом.
  • Я нашел еще некоторые отличие в поведении между musl и glibc (и еще дополнительно проверил, как с этим дела в Solaris и разных BSD).
  • Пришел в багтрекер их документации, говорю, вот есть еще такой кейс, его тоже неплохо бы добавить к уже существующему описанию.
  • Баг закрыли с формулировкой «Не покрывается стандартом, не будем это документировать».

Это называется ВЕРА В ТО, ЧТО НАПИСАНО НА БУМАЖКЕ.

И все в мире обязаны знать эту священную бумажку.

Вот кто-то из команды, кто писал уже существующий список расхождений – понимал, что кроме священной бумажки есть еще и практика, с которой сталкиваются программисты, и для их кейсов уместно написать более подробные уточнения. А тот, кто закрывал баг - не понимал. (При чем это может быть даже один и тот же человек, просто в разных настроениях - мы не знаем.)

А в случае с компиляторами, если ты придёшь жаловаться на любую форму поведения, которая связана с UB, то твой баг закроют как NOTABUG/WONTFIX. Потому что «так положено». Никто не будет с ноунеймом дистикутировать о таких решениях, даже если решения откровенно плохие.

Смотри выше кейс UB при выходе из функции. Или кейс с заменой «неопределенного» на «ошибочное» поведение для неициализированных переменных в свежих версиях стандарта – то, что давно пора было сделать.

wandrien ★★★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

Баг закрыли с формулировкой «Не покрывается стандартом, не будем это документировать».

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

Никто не будет с ноунеймом дистикутировать о таких решениях

Разумеется. С чего бы это они должны? Эти решения уже когда-то были приняты людьми, работающими над компилятором исходя из их соображений. Это был их выбор, они его сделали. Переделывать всё под каждого залётного ноунейма конечно же никто ничего не будет. У тебя есть выбор пользоваться их компиляторами или нет.

Ты плохо понимаешь, как работают системы с обратной связью.

Это ты плохо понимаешь, как это работает. Это не "обратная связь". Это люди, принявшие решение как работает компилятор, потом фиксируют это решение в стандарте или блокируют попытки эту часть стандарта изменить. Их решение сделать компилятор таким образом и текст стандарта - это одно решение плюс-минус одних и тех же людей.

Грустная история с realloc’ом нулевого размера - наглядная иллюстрация.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Не вижу проблемы.

Это и печально. Кроме документированной логики, любой сложный компонент системы содержит кучу имплицитно заложенной логики, на которую опять же имплицитно полагаются другие компоненты системы. И если ты этот компонент заменяешь, вылазит много сюрпризов. Уделять внимание явному документированию ГРАНИЦ - это хорошо и правильно.

«Не вижу проблемы» это и есть вера в официальную бумажку, в данном случае твоя.

Их решение сделать компилятор таким образом и текст стандарта - это одно решение плюс-минус одних и тех же людей.

Это никак не отменяет описанного процесса. Под общий термин UB сваливаются решения, имеющие разную природу, а затем сам ярлык UB становится самодостаточным.

Мы генерируем заведомый мусор, который надежно может быть детектирован оптимизатором как мусор? Пофиг, так положено! Страдайте.

Грустная история с realloc’ом нулевого размера - наглядная иллюстрация.

Да, это херовая история.

wandrien ★★★★
()
Ответ на: комментарий от wandrien

Не вижу проблемы.

Это и печально. Кроме документированной логики, любой сложный компонент системы содержит кучу имплицитно заложенной логики,

А вот тут ты уже начинаешь плыть. Это не "имплицитно заложенная логика", а "выбранная в конкретный момент под конкретную платформу", которая может поменяться в настоящем или будущем в т.ч. со сменой платформы. И да, ты не в праве на неё закладываться, иначе окажешься разработчиком флеш-плеера под Linux (история с memcpy).

Ты в праве закладываться только на публично документированные инварианты, а разработчики libc вправе решать, какие дополнительные гарантии сверх описанных в стандарте предоставлять, а какие - нет. Не нравится - пиши свою libc. Можешь начать с форка, благо лицензия позволяет.

Я не понимаю претензий по этому поводу, так эти все условия известны ещё на берегу - в момент чтения букваря по си.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Молодец.

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

wandrien ★★★★
()
Ответ на: комментарий от LamerOk

Я не понимаю претензий по этому поводу, так эти все условия известны ещё на берегу - в момент чтения букваря по си.

Вот это отдельный рофл, учитывая, насколько больше API специфицирует POSIX по сравнению с огрызком libc из требований самого Си, и сколько еще реальных практически нужных функций специфицирует любая реальная библиотека Unix-like системы по сравнению с POSIX.

Ох уж эти эксперты с ЛОР, которые знают все тонкости всех libc как букварь.

wandrien ★★★★
()
Ответ на: комментарий от wandrien

если священная бумажка содержит внутреннее логическое противоречие

Бумажку фиксят, а тем временем выбирают один из вариантов, документируя. Мир не идеален, да. У тебя есть какой-то другой волшебный манямирок, где ты живёшь, или что?

Вот это отдельный рофл, учитывая, насколько больше API
сколько еще реальных практически нужных функций специфицирует любая реальная библиотека

А рофл-то в чём? Голоса в твоей голове говорят тебе, что стандарт на язык должен себе жопу порвать и включить в него все спеки на все библиотеки мира или что? В чём противоречие?

эти эксперты с ЛОР, которые знают все тонкости всех libc как букварь.

Не проецируй.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Ты себе в своём манямирке способен представить такую ситуацию, где расходится документированное поведение glibc и POSIX? Или такое, где оба документа прописаны достаточно невнятно, чтобы быть прочитанными однозначно? Или где специфицированное поведение расходится с реальной практикой, и все де-факто забили на эту часть спецификации?

Я вот такие кейсы встречал за время работы.

Человеку, который обеспечивает работу приложения на musl, на что ориентироваться в своей работе, на воздушные пузыри Single Unix Specification, или на реальную библиотеку, с которой он работает? Вот тебе и ответ в самом вопросе.

wandrien ★★★★
()
Ответ на: комментарий от wandrien

Ты себе в своём манямирке способен представить такую ситуацию,

Я себе в своём реальном мирке встречал неоднократно ситуацию, где поведение отличается от задокументированного. И что? К чему все эти тоскливые беспредметные подвывания?

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

LamerOk ★★★★★
()
Последнее исправление: LamerOk (всего исправлений: 1)
Ответ на: комментарий от anonymous

while(true) {

__pause(); // rep nop (x86)

}

HPW-dev
()
Последнее исправление: HPW-dev (всего исправлений: 1)
Ответ на: комментарий от LamerOk

Что-то ты поплыл. (с)

Мы начинали эту часть разговора с банальности о том, что как в пословице: в теории между теорией и практикой нет разницы, а на практике разница есть. К этому и пришли.

Молиться на бумажки сами по себе смысла нет. А вот понимать практические эффекты за пределами абстрактных бумажек - желательно.

wandrien ★★★★
()
Ответ на: комментарий от wandrien

Молиться на бумажки сами по себе смысла нет.

Молиться - нет. Понимать разницу между гарантированным бумажкой и негарантированным - да. Иначе см флеш-плеер под линукс.

Там, кстати, хороший пост в конце, воспользуюсь случаем его скопипастить:

David Schwartz 2017-08-03 21:27:54 UTC

"Congratulations, you have knowingly made the system more unreliable in order to gain nothing."

Actually, it’s not nothing.

Several bugs were discovered as a result of this change and are now being fixed. This seems to be a somewhat common bug and if it has no consequences, it will continue to become more and more widespread until the day it does have consequences. The sooner that day is, the less it will break.

In addition, in the future if modifications to memcpy do make significant optimizations possible, they won’t break things. One of the obstacles to innovation is dealing with code that abuses undocumented behavior and that can lead to a culture of having to maintain bug-for-bug compatibility.

Потому что bug-for-bug compatibility заканчивается вот так:

WTS_SESSIONSTATE_UNLOCK (1 (0x1))
The session is unlocked.

Windows Server 2008 R2 and Windows 7:  Due to a code defect, the usage of the WTS_SESSIONSTATE_LOCK and WTS_SESSIONSTATE_UNLOCK flags is reversed. That is, WTS_SESSIONSTATE_LOCK indicates that the session is unlocked, and WTS_SESSIONSTATE_UNLOCK indicates the session is locked.

https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wtsinfoex_level1_w

LamerOk ★★★★★
()

Смешно до слёз. Это как если бы при signed переполнениях зафиксировали поведение (т.е. убрали UB) при std::max<T> + 1, но (std::max<T> - 1) + 2 остался бы UB. Совершенно некомпетентный, неадекватный и прогнивший комитет, и убогий устаревший язык которому в современной разработке места нет.

anonymous
()
Ответ на: комментарий от LamerOk

История с memcpy - наглядный пример идиотизма свидетелей стандартов. Все знали, что memcpy копирует снизу вверх, правильно было бы внести это поведение в их графоманию, но они решили сделать наоборот - испортить memcpy чтобы сломать кучу софта, полагавшегося на общеизвестное поведение.

firkax ★★★★★
()
Ответ на: комментарий от ckotctvo

Бесконечный цикл делает очевидный эффект: он прекращает дальнейшее выполнение. Но только не для дегенератов которые решили что это индульгенция.

Это не эффект.

Бесконечный цикл не заканчивается. Я не знаю насколько альтернативным мышлением надо обладать чтоб этого не понимать.

Попробую тебе описать это альтернативное (кстати, полностью согласен с тобой тут) мышление: в точки зрения языка Си (как следствие, эта шиза распространяется и на C++), поведение программы, которое должно сохраняться при её трансформации компилятором, выражается в виде последовательности т.н. побочных эффектов. Побочные эффекты здесь – достаточно ограниченный список событий, среди которых есть ввод-вывод, модификация volatile переменных и всякое разное типа abort(). Вот после того, как эту логику кто-то смог родить и возвести в абсолют, бесконечный цикл без эффектов стал UB.

Отдельная писечка лежит в том, что в самом Си эту логику таки подправили и начиная с C11 можно обмазываться while(true) {} пока не надоест, а вот до плюсистов только сейчас доходить начинает. К вопросу о нужности новых стандартов Си, ага.

cc @wandrien

P.S. если кто-то хочет поспорить с изложенным выше, то пишите комитетчикам, не мне.

yorshka
() автор топика
Ответ на: комментарий от yorshka

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

А вот как стандарт крестов докатился до жизни такой, что пришлось накладывать заплатку из ОП, да еще и применять это как defect report к уже выпущенным релизами стандарта - это другой вопрос…

wandrien ★★★★
()
Ответ на: комментарий от yorshka

Про Си да, хорошо подмечено. Там это предусмотрели давно, в то время как кресты игрались в игру «это мешает нам код оптимизировать для всяких кейсов типа GPU».

wandrien ★★★★
()
Ответ на: комментарий от wandrien

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

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

А вот как стандарт крестов докатился до жизни такой, что пришлось накладывать заплатку из ОП, да еще и применять это как defect report к уже выпущенным релизами стандарта - это другой вопрос…

Примерно так же, как уже 6 лет модули не могут нормально сделать. Или так же, как родился синтаксис рефлекции, на который без крови из глаз не взглянешь. Или так же, как функции объявляют deprecated в одном стандарте, а в следующем убирают этот флаг, потому что функция оказалась нужной.

Это просто шизовая разработка комитетом, где куча людей пихают всё подряд без оглядки на других и никто при этом ни за что не отвечает. Wayland вот так же сейчас развивается.

yorshka
() автор топика
Ответ на: комментарий от wandrien

Нет, отличим. Бесконечный цикл:

удерживает процесс живым;

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


может удерживать mutex;

это случай «используйте правильные инструменты для ожидания», если я верно понимаю


занимает поток;

либо «используйте правильные инструменты для ожидания», либо нет смысла.


может влиять на другие потоки;

либо «используйте правильные инструменты для ожидания», либо нет смысла.


может позволять обслуживать прерывания;

либо «используйте правильные инструменты для ожидания», либо нет смысла.


может удерживать устройство в определённом состоянии.

либо «используйте правильные инструменты для ожидания», либо на вашей платформе их нет, тогда цикл, но я об этом уже говорил.

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

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)
Ответ на: комментарий от Loki13

случай «чтобы жрало процессор» я не обдумал, каюсь. действительно, не вижу альтернатив. гм-гм. в программировании много тонкостей..

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 2)
Ответ на: комментарий от firkax

История с memcpy - наглядный пример идиотизма свидетелей стандартов. Все знали, что memcpy копирует снизу вверх, правильно было бы внести это поведение в их графоманию, но они решили сделать наоборот - испортить memcpy чтобы сломать кучу софта, полагавшегося на общеизвестное поведение.

Они сделали всё правильно. Копить костыли в стандартах - это как раз то из-за чего мы видим смерть C++, в частности на примере этой темы, в то время как питон процветает, сломав, если говорить твоими словами, переходом с 2 на 3 версию вообще всё на нём написанное. И ничего, как-то у всех живых проектов нашлись силы 2to3 запустить, а заброшенный не обновлявшийся > 5 лет мусор и без этого бы сломался, по причине депрекейшнов или обновления зависимостей.

anonymous
()
Ответ на: комментарий от AndreyKl

Очевидную ерунду травлю ИИшницей и не вижу с этим проблем.

Если охота упражняться в бессмысленных абстракциях, ради бога.

А я практик. Теоретические построения нужны постольку, поскольку решают практические задачи.

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

Если же тебе так нужны теоретические идеи, то вот тебе для обдумывания:

Программа, которая завершается за конечное количество шагов по своему потоку управления, в конечном счёте приходит тем или иным образом к вызову exit или abort. Это является наблюдаемым побочным эффектом.

Программа же, которая за конечное число шагов не завершается, она приводит… к чему-то?

wandrien ★★★★
()
Последнее исправление: wandrien (всего исправлений: 2)
Ответ на: комментарий от anonymous

Копить костыли в стандартах - это как раз то из-за чего мы видим смерть C++, в частности на примере этой темы, в то время как питон процветает

это питон умирает судя по графикам tiobe..

dynamic_cast
()
Ответ на: комментарий от dynamic_cast

судя по графикам tiobe..

АХАХАХАХАХАХАХАХАХАХАХАХАХАХХАХАХААХАХААХАХАХАХХАХАХАХАХА

yorshka
() автор топика
Ответ на: комментарий от AndreyKl

пользоваться надо правильными инструментами. и да, в программировании много тонкостей
это случай «используйте правильные инструменты для ожидания», если я верно понимаю
либо «используйте правильные инструменты для ожидания», либо нет смысла.
либо «используйте правильные инструменты для ожидания», либо нет смысла.
либо «используйте правильные инструменты для ожидания», либо нет смысла.

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

Почему struct{} занимает 1 байт? Почему я могу сделать delete new int[10] и delete[] new int? Можно долго рассказывать про то у каждого объекта должно быть адрес, а зачем - а затем что такова модель памяти поскольку она должна поддерживать такие-то инварианты арифметики указателей и никак нельзя чтобы адреса разных объектов были одинаковые, и такова система типов, поскольку она должна быть совместима с тем-то и тем-то тянущимся уже из C и ну никак не может быть в ней выражено как именно аллоцировался указатель.

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

Только во втором случае неминуемо приходит какой-нибудь сраный rust и внезапно проводит по губам тем что почему-то и size_of::<()>() == 0 может быть, и даже Box<T> != Box<[T]>, и программы от такого богохульства, о ужас, не разваливаются, а только логичнее и быстрее становятся. И, чсх, loop {} там работает просто как бесконечный цикл, без UB и исключений из UB, и даже оптимизациям это не мешает.

anonymous
()
Ответ на: комментарий от dynamic_cast

Не берусь судить о репрезентативности данных с tiobe, и не вижу смысла обсуждать текущую динамику. Поинт в том что если бы слом совместимости в python3 представлял реальную проблему - python 3 просто не взлетел бы, а он взлетел сразу, и очень высоко.

anonymous
()
Ответ на: комментарий от anonymous

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

и на этом уровне мне решения кажутся понятными. с теми исходными которые есть. вот просто по факту. комментарии «жетских практиков» кажутся забавными. вопрос «почему вернули пустой цикл» кажется объяснённым тем что я уже сказал (ну и выше человек привёл пример «необходимо кушать процессор», я его как то упустил из виду).

но это для меня вопрос праздного любопытства на данный момент, не более того. может через месяц будет вопросом работы. а может и нет.

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 3)
Ответ на: комментарий от wandrien

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

вот смотри, программа зависла в цикле. это значит что никогда дальше чем цикл она не попадёт. т.е. она всегда будет на этом месте и это и есть её конец. а цикл ничего не делает. ну значит цикл можно убрать.

то что программа после этого начинает завершаться или там вселенная коллапсирует - это личные трудности не слишком грамотных программистов, не более того.

-----
с дисклеймером о платформах и о необходимости кушать цпу.

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария