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)
Ответ на: комментарий от shdown

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

ckotctvo
()

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

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

если в функции пропущен return это ошибка?

Не-а. Канонический пример:

int deref_or_die(int *ptr) {
  if(ptr) return *ptr;
  abort();
}

или можно молча проваливаться куда-то в другую функцию или вообще в данные?

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

#include <stdio.h>

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

int main(void) {
  puts("before f");
  f();
  puts("after f");
  return 0;
}

При сборке же компилятором C++ исходит сам знаешь что.

$ clang -O2 no-return.c -o no-return  
no-return.c:5:1: warning: non-void function does not return a value [-Wreturn-type]
    5 | }
      | ^
1 warning generated.
$ ./no-return 
before f
f called
after f
$ clang++ -O2 no-return.c -o no-return
clang++: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
no-return.c:5:1: warning: non-void function does not return a value [-Wreturn-type]
    5 | }
      | ^
1 warning generated.
$ ./no-return                         
before f
f called
[1]    524805 segmentation fault (core dumped)  ./no-return
yorshka
() автор топика
Последнее исправление: yorshka (всего исправлений: 2)

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

Кому нужно тот делает.
К примеру у меня в C++ полностью управляемая память (вплоть до использования переменных).
Всё в run-time.
Все адресные операции контролируемы и никаких утечек.

Это что другие не могли так слелать?
Могли.

В чём же проблема?

Программируют и разработку ведут по популярным ПОНЯТИЯМ.
Так любопытно, что всё что не сделают декларируют как самое СОВРЕМЕННОЕ и ЛУЧШЕЕ.
И знаете таковые всегда победят во всём и вся.
Почему?
Потому что они ЛУЧШИЕ.
Proto buffer, …, …, … это всё лучшее по мнению таковых.
В чём-то они и правы потому, что другое им неизвестно.
И это как раз к лучшему.

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

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

Он может вернуть открытый файл, может вернуть NULL, может и упасть если ОС настроена убивать процессы, лезущие по каким-то путям, может и зависнуть/ребутнуться комп если попытка обращения к файлу спровоцирует проблемы жёсткого диска.

Нет никаких «адресов»

Это уже выдумки нехороших людей.

Эта тема — про C++

А да, я и забыл. Ну это ты виноват - привёл выше примеры с файлами .c

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

Это вообще не проблема c++ а проблема дегенератов которые портят компиляторы. С какого языка не компилируй, у тебя в каком-то моменте будет либо gimple либо llvm ir. И все обсеризации происходят именно там. Именно так в раст однажды принесли UB с переполнением int.

Никакой проблемы не срать мимо унитаза и не подтирать жопу пальцем нет. Просто народ говнистый по другому не может.

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

Нет никаких «адресов»

Это уже выдумки нехороших людей.

Лол нет. LLVM умеет компилировать в WASM, и в нём нет никаких адресов. И стека нет. И кучи тоже нет.

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

Никакой проблемы не срать мимо унитаза и не подтирать жопу пальцем нет. Просто народ говнистый по другому не может.

Всё много проще.
Разработчиков по пальцам можно пересчитать.
Остальные ЖДУНЫ.

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

Разработчиков по пальцам можно пересчитать.

Вот смотрите.
Чем отличаются нынешние технологии компиляции, да и компиляторы, от тех которые в 60-е разработали?
Да по существу ничем.
Что за 60 лет нельзя было разработать другой подход к разработке алгоритмов, …
Конечно можно, но никто не хочет создавать новые технологии разработки.
И понятно почему …

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

Чем отличаются нынешние технологии компиляции, да и компиляторы, от тех которые в 60-е разработали?

Да по существу ничем.

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

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

Промежуточное представление (Intermediate Representation, IR) как ключевой элемент теории и практики компиляторов стало активно развиваться в 1970–1980-х годах. До этого компиляторы в основном работали напрямую с исходным кодом или языком ассемблера, что усложняло оптимизацию и портирование на разные платформы.

Ключевые этапы развития

1970-е годы: P-code и UCSD Pascal. В этот период появилась концепция P-code (portable code — переносимый код). Компилятор языка BCPL генерировал промежуточный код O-code, который затем интерпретировался на целевой машине. Позже, в конце 1970-х годов, в Калифорнийском университете в Сан-Диего (UCSD) разработали систему UCSD Pascal. Она использовала p-машину, которая интерпретировала сгенерированный p-код, что позволило запускать программы на Pascal на разных компьютерах (Apple II, IBM PC и др.).

1980-е годы: статическое единственное присваивание (SSA). Эта форма промежуточного представления была разработана исследователями IBM (Рон Сайтрон, Жанна Ферранте, Барри К. Розен, Марк Н. Кеннет Задек). В SSA каждой переменной присваивается значение только один раз — создаётся новая переменная, а не изменяется существующая. Это упростило анализ данных и позволило эффективнее проводить оптимизации.

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

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

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

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

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

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

Это экзотическая платформа с кастомными правилами работы.

Это нифига не экзотическая платформа. У тебя реализация WASM в каждом утюге сейчас.

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

Нативным ничего никто не урезал. Платформ, где указатель – это не просто плоское число, великое множество. Из твоего любимого, x86 в real mode, где два численно разных указателя могут указывать в одну и ту же ячейку памяти. Тегированные указатели, лисп-машины и прочая срань существовали примерно всегда.

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

Не важно плоское или нет, важно что указатель ты даёшь процу, а проц уже (по своей логике + настройкам, сделанным ОС) сам решает как читать по нему данные. То есть задача разыменования указателя лежит вне компетенции языка.

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

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

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

C++
эмбед
бесконечный цикл в ожидании прерывания

- ещё, небось, и один прогон - один такт? А вы уверены, что это тот «C++», который имелся ввиду?

Shadow ★★★★★
()

Нелюбителям UB теперь придется отвечать на собесах, что делает голый while.

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

Не важно плоское или нет, важно что указатель ты даёшь процу, а проц уже (по своей логике + настройкам, сделанным ОС)

А где в языке C или C++ такое написано? Правильно, нигде.

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

Давай таки отделим. Тема пусть будет про С++, shdown меня выше немного запутал начав сишные исходники анализировать. Допускаю, что в С++ указатели могут быть какими-то ненативными, так что обсуждать в этой ветке нечего.

firkax ★★★★★
()

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

То есть, теперь ещё больше условий, чтобы запомнить, что UB, а что не UB… И это помимо того, что надо помнить, что конкретно вот тривиальные бесконечные циклы не UB только начиная с C++26 (пока свежо преданье, запомнить легко, но через 10 лет уже хрен кто вспомнит).

Честно говоря, у меня давно сомнение, что есть хоть один человек на этой планете, который знает C++ на все 100%.

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

Допускаю, что в С++ указатели могут быть какими-то ненативными, так что обсуждать в этой ветке нечего.

Они и в Си могут такими быть. Вообще никто не запрещает.

yorshka
() автор топика
Ответ на: комментарий от yorshka
  • наплевал на ворнинги компилятора – check

  • смешал 2 языка в кучу (чо javac не компильнул для наглядности?) – check

  • проигнорировал явное UB одного из языков назвав это отстрелом жопы – check

Лови клоуна, заслужил!

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

Петровича на них нет.

Бананы жрут и дрищут, всех тварей и не счесть и в жилах у Петровича лишь ярость злость и месть ..

Это буквально неадекватность и/или упоротость в одном флаконе.

Кстати вопрос на миллион: в расте пустой бесконечный цикл это UB? Компиляторы те же самые

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

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

------
пошёл погуглил, гименей подсказывает вызов pause() и обработку сигналов.
для микроконтроллеров переход в режим инзкого энергопотребления и ожидание прерывания.

т.е. ну может есть ещё какая экзотика где пустой цикл может пригодиться, я не знаю. судя по действиям коммитета вероятно есть.

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

в расте пустой бесконечный цикл это UB

Нет, с чего бы это? При каждом таком вопросе учитывай, что на расте не висит дурная сишная наследственность, следовательно, нет необходимости костылять язык такими странными UB. Язык с самого начала проектируется(и именно проектируется, а не лепится как получится в сишках) и под системность, и под оптимизацию современным компилятором.

Компиляторы те же самые

Лол что. Причём тут это. Только если бэкенд, и с этого уровня что-то может просочиться только при ошибке в этом бэкенде. И в любом случае УБ-шное поведение определяется правилами конкретного языка, той самой абстрактной машиной, и компилятор тут зависимая от это всего штука, а не наоборот.

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

на расте не висит дурная сишная наследственность

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

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

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

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

Код после цикла компилятор может убрать, так как он формально не достижим. И то без оптимизации (т.е. с -O0) даже это не должен делать.

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

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

А если цикл не пустой, то и вообще так большинство gui-программ работают. while (true) и внутри опрос состояний.

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

А если цикл не пустой, то и вообще так большинство gui-программ работают. while (true) и внутри опрос состояний.

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

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

ну, уточню, потому что не знаю с кем разговариваю, а сам допускаю неточности в выражениях (следуя за собеседником всё же вероятно стоит использовать правильную терминологию, если получается)

под «непустой» в сообщении выше нужно понимать «продуктивный», конечно же.

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

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

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

В этом смысле компилятор должен быть как идеальный раб: если господин приказал пойти и утопиться, значит должен пойти и утопиться. Максимум переспросить точно ли именно это имел ввиду (warning выдать).

anonymous
()

Это проблемы шланга. Гцц никогда пустые циклы не выкидывал. Это стандартная вещь для эмбедед.

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

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

Это не так.

То, что ты описываешь, называется Unspecified behavior.

Например, порядок вычисления арифметического выражения a + b - c - d «какой-то существует», но никто не гарантирует, какой именно. Компилятор сам решает, какой.

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

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

Обращение к освобожденному указателю - UB, потому что детали реализации кучи тоже выведены за пределы модели исполнителя.

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

Очень согласен с вот этой репликой пользователя @ckotctvo:

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

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

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

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

if (false) { body }
. Ну просто потому что они встречаются. И разработчики компилятора говорят, ну зачем этот лишний if, там ведь инструкция тратится. И все согласны. Т.е. вот почему if убрать можно а цикл нельзя? тем более что в цикле много инструкций тратится.

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

При сборке же компилятором C++ исходит сам знаешь что.

Дебилы, ей-богу.

Детектировать ситуацию они могут, а решить - не могут. Абсурд.

У меня даже в наколеночном хобби-компиляторе такие кейсы ловит конечный автомат и заставляет пользователя расставлять return-ы.

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

Потому что убирание такого примера if НЕ МЕНЯЕТ поведение программы. А убрать бесконечный цикл - МЕНЯЕТ.

Я правда не знаю, скольки пядей во лбу требуется быть для мысли:

Бесконечный цикл не заканчивается.

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

Детектировать ситуацию они могут, а решить - не могут. Абсурд.

Зато ПАСТАНДАРТУ!

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


int f(void) {
  // UB в C++
}

int main(void) {
  int i = f(); // UB в Си
}

То есть, если ты в сишечке результат функции никуда не сохраняешь, то UB и нету. Сишечка рулет! А плюсеки – шоколад.

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

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

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

Пипец. У меня слов нет. Так что же она тогда не закончилась, эта программа из примера, при достижении бесконечного цикла? А пошла вместо этого исполнять недостижимый код?

Это же тогда получается ОПРЕДЕЛЁННОЕ поведение – завершение по abort() или exit(0).

Я уже как-то устаю от этих обсуждений на пустом месте, где приходится обсуждать понятия уровня букваря. Щас обзор ситуации от ИИ, скину, он как раз идеально подходит для объяснения прописных истин.

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

ИИ:

1. Суть проблемы: Forward Progress Guarantee (о чем спорят, но не называют своими именами)

Корень всех бед и оптимизаций, которые так бесят участников вроде ckotctvo и ant1, кроется в концепции Forward Progress Guarantee (Гарантия прямого продвижения), появившейся в C++11 вместе с многопоточностью.

  • Логика компилятора: Чтобы агрессивно оптимизировать код (например, удалять пустые циклы или выносить инварианты), компилятору нужно математическое право предполагать, что любой поток выполнения рано или поздно завершится, сделает I/O или операцию синхронизации.
  • Старое правило (до C++26): Если цикл бесконечный и не имеет побочных эффектов (side effects), он нарушает эту гарантию. Следовательно, программа невалидна с точки зрения стандарта. А раз она невалидна — это Undefined Behavior (UB).
  • Следствие UB: Компилятор исходит из презумпции «программист не мог написать UB». Значит, такого цикла не существует. Компилятор смело удаляет цикл, а заодно считает весь код после него недостижимым (unreachable) и тоже удаляет (или заменяет на ud2 / trap, как показал shdown на macOS).

Что изменили в C++26? Комитет по стандартизации наконец признал реальность embedded-разработки и системного программирования. Тривиальные циклы вроде while(true); часто используются для ожидания прерываний (IRQ), работы watchdog-таймеров или как намеренная «остановка» ядра. Комитет сделал исключение: строго тривиальные бесконечные циклы больше не нарушают Forward Progress. Но как только в цикле появляется хоть какое-то выражение (даже (void)"string";), он перестает быть «тривиальным» с точки зрения Абстрактной Машины, и на него снова распространяется старое правило UB. Именно это имеет в виду автор топика (yorshka), когда с сарказмом говорит, что «починили, по сути ничего не починив».

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

Пипец. У меня слов нет. Так что же она тогда не закончилась, эта программа из примера, при достижении бесконечного цикла? А пошла вместо этого исполнять недостижимый код?

предлагаю тебе подумать над этим на досуге. мне дальше не интересно общаться, прости.

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