Как стать автором
Обновить

Комментарии 168

Тащить все в стандартную библиотеку? А потом все будет как в Go, где все более хорошие альтернативы так и так пользуют. Лучше менеджер зависимостей сделать стандартный.

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

В прикрепленном видео оба эти вопроса задавали. И ответы Антона меня удивили.

  1. Стандартный менеджер пакетов. Ответ примерно такой: вот есть уже 10 разных пакетных менеджеров под разные наборы платформ, поэтому (?) мы лучше затащим все в стандартную библиотеку. А если кто не может на своей платформе STL, то мы сделаем для нее подмножество, которое точно можно реализовать.

  2. Переписать logstash на Java. Кто-то когда-то пробовал, получилось медленнее.

Последние 3 пишу только на Го. Решил посмотреть, чем живет современный C++, может стоит вкатиться на C++20. Что могу сказать, проблемы в общем-то все те же: попытка угодить всем сразу и при этом сохранить легаси, отказ от инфраструктурных компонент, которые нужны всем (пакетный менеджер), зато стремление поддержать ОЧЕНЬ узкую категорию платформ, на которых всего 120байт памяти или нет оперативки. Что могу сказать, я рад, что больше в этом не варюсь.

зато стремление поддержать ОЧЕНЬ узкую категорию платформ, на которых всего 120байт памяти или нет оперативки. Что могу сказать, я рад, что больше в этом не варюсь.

скорее стремление поддерживать в первую очередь самые востребованные для плюсов платформы. Так то с с++ и в веб можно, но кому это надо? А ембедщикам кроме си и плюсов то и сунуться некуда, не учитывать их мнение значит ударить по целому классу устройств.

Насколько я понимаю, в Яндексе весь веб бэкэнд на C++. И программистов там больше, чем во всем ембеде в России. И что, ембедщикам пакетный менеджер не нужен?

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

Говоря про веб я конечно же имел в виду фронтенд, а за яндекс не переживайте - монорепа с in-house системой сборки не шибко нуждается в пакетном менеджере.

И что, ембедщикам пакетный менеджер не нужен?

Проблема в том что пакетных менеджеров уже есть некоторое количество (навскидку могу вспомнить conan, vcpkg, cget, nuget - это только навскидку, так их еще больше). Ну будет yet another package manager ¯ \ _ (ツ) _ / ¯

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

Для поиска специалистов, Вам стоит обратить внимание на Киев.
В Киеве, на зарплату чистыми 5-6к usd воронка кандидатов умеющих писать на с++ превышает пропускную способность собеседующего.

Вакансии закрываются быстро. Тоже самое и с вакансиями на с++, их тут очень много, работу можно найти практически в любой сфере, которая нравится.

Дело не в наших специалистах, а в коде, написанном третьими лицами, сторонних библиотеках. Что не STL и не за авторством FAANG, то чаще всего кошмарно.

Почти любой код на C++ вызывает кровь из глаз. Разве не так? :)


Что не STL и не за авторством FAANG

Как будто на код из STL или, например, Boost не больно смотреть. Любое метапрограммирование вызывает кровь из глаз. Лучше уж на "C с классами писать". :)


У Google неплохой Code Style Guide, многое запрещает. Но в любом случае, этому языку уже ничего не поможет, а стандарт от редакции к редакции будет пухнуть, пухнуть и пухнуть. А вместе со стандартом будут пухнуть головы разработчиков, а тех, кто умеет писать будет всё меньше и меньше.

std::thread t([&lamp]() { 
   char c;
   while (std::cin >> c) { 
       lamp.change_mode(); 
    } 
});

Хммм, мне кажется здесь дата рейс ? std::cin связан с std::cout. Нужно делать что-то типа cin.tie(nullptr), наверное.

Э-э-э, а как он связан?

Я так понимаю std::cin вызывает flush для std::cout - https://en.cppreference.com/w/cpp/io/basic_ios/tie

that is, flush() is called on the tied stream before any input/output operation on *this

Охх, т.е., sync_with_stdio(true) делает больше чем "синхронизирует standard C streams". Спасибо.

В случае с терминалом проблема глубже. Абрстракция 'потока' плюсов не очень хорошо совместима с абстракцией 'терминала' Unix. Да, абстракции немного пересекаются (и это позволяет с натяжкой считать std::cin потоком), но терминал - это не просто поток, он может делать намного больше, см. termio/termios/tty_ioctl (на этом построен, например, job control в юниксовых CLI и т.д.). По идее, вам надо использовать неблокирующий non-canonic input режим терминала. Но насколько помню, у плюсов такого функционала нету, придется использовать POSIX-совместимые вызовы.

Ну естественно у плюсов такого нету… Тут надо глубже смотреть. Помнится в досе в сишной библиотеке была волшебная функция kbhit() которая проверяла есть ли что-то в буфере клавиатуры. И там все было просто —

if (kbhit()) ch = getch();


Т.е. если в буфере клавиатуры что-то есть — читаем оттуда. Нет — идем дальше.
Уверен, что для никсового терминала можно аналогичное найти.

Но это же не по плюсовому…

Тут есть ещё одна проблема — проверка kbhit() в цикле лишь съест процессорное время. Поток не должен гонять цикл, поток должен ждать — вот только ждать придётся, в большинстве программ, появления символа в консоли ИЛИ другого события. В итоге всё опять сводится к абстракции над epoll/IOCP, которой в языке нету.


Ждём Networking TS, может после её принятия и появится нормальный консольный ввод через какое-то время…

Но в елочке мы же все равно гоняем цикл, чтобы обновлять лампочки. В конце каждого цикла проверить клавиатуру — если кнопка нажималась за последний кадр, то в следующем кадре рисуем поновому.
Главная проблема в C++ что в него постоянно запихивают грабли
int fn() { int x; return x || !x; }

Например почему clang с чистой совестью возвращает из fn 0. Почему стандарт ему позволяет такое делать?
И чего молча минусуем clang 5-12. Что сказать нечего?

А что здесь нужно сказать? Чтение значения из неинициализированной переменной - UB. Clang может возвращать и 0, и 42, и 322, он в своём праве. Неудивительно, что вас минусуют.

А вас ничего не смущает. Не инициализированная переменная может иметь одно из 2^32 (если int 32bit) значений при любом возможном значении результат работы функции 1. Но clang выбирает наименее часто встречающийся (а именно 0 раз) вариант и бодро возвращает 0, не 42 и не 322 которые тоже 0 раз встречаются во всех возможных исходах. Правила формальной логики можно смело выкидывать — устарели видимо. Так?

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

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

Зачем вы пытаетесь обосновать, бардак и не согласованность? И зачем эти магические модели и ритуалы оторванные от реального железа? Вы правда не видите к каким это может привести последствиям на практике?
Если у вас что-то меняется со временем то это следует указывать явно. например есть модификатор volatile. Но тут явное нарушение всех правил. При это всегда находятся люди которые как сектанты с пеной у рта будут доказывать что это правильно, «потому что так сказал Хэнк это UB» и луна из зеленого сыра компилятор должен сгенерировать максимально еб@#&тый не логичный код. Ведь Хэнк всегда прав Смотрите стандарт.

Вот как раз правила логики и позволяют программе так вести себя. Утверждение "произошло UB" всегда ложно, это аксиома. А из лжи следует что угодно.

А в чем здесь аксиома?

То есть конечно мы можем формально высказать какое-то абстрактное, если для undefined(x) существует definition(x), то undefined(x) устраняется. При этом под definition(x) тут формально подходит любая отсылка к undefined(x), что выполняется априорно в момент описания undefind(x) в спецификации. Но без индукции, выходящей за пределы классической логики - это не аксиома логики, это определение абсурда.

Думаю, тут все сильно проще можно попробовать объяснить: спецификация сообщает о том как обеспечить совместимость, и где воспользоваться свободным пространством для внедрения произвольного кода. Про цель предоставить свободу компиляторам, есть в спецификации. Компиляторы сосредоточиваются на оптимизациях, плюс дают низкоуровневые интерфейсы программистам. Программисты пишут эффективный код под железо. Статья как раз о цене, за все это. А тред о том, что маленькая формальность, не может является значимой в сложившейся ситуации. И это ни разу не является утверждением привлекательности C++.

Это одно из определений UB — UB никогда не присутствует в корректной программе (а поведение некорректной программы нас не интересует). Поэтому утверждение "произошло UB" всегда ложно.

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

Спасибо за разъяснение. Захотелось теперь попробовать написать компилятор, как раз можно будет приспособить к ассемблеру, который где-то у меня валяется.

Хм, интересно, кажется clang ничего не выбирает вообще и не анализирует как используется переменная (правила формальной логики), анализатор говорит что это просто:

Branch condition evaluates to a garbage value

и дальше с этим ничего не делается, а просто считается false (имеет право, потому что UB). Судя по всему в версия 4.0.1 и еще в 5.0.0 с этим были баги

https://godbolt.org/z/EPd37cjfW

<source>:4:38: warning: Branch condition evaluates to a garbage value
int check_compiler() { int i; return i || !i; }
                                     ^
<source>:5:39: warning: Branch condition evaluates to a garbage value
int check_compiler1() { int i; return !i || i; }
                                      ^~
<source>:10:7: warning: Branch condition evaluates to a garbage value
  if (x) // expected-warning{{Branch condition evaluates to a garbage value}}
      ^
<source>:17:10: warning: Branch condition evaluates to a garbage value
  return x ? 1 : 0; // expected-warning{{Branch condition evaluates to a garbage value}}
         ^
4 warnings generated.

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

Так почему не возвращает? И главное почему такое поведение поощряет стандарт?

В стандарте ясно сказано, что поведение таких переменных не определенно. Компилятор что хочет, то делает с ними. Чтобы ее инициализировать, необходимо присваивание. Причины, очевидно, в оптимизациях.

Значение функции не зависит от переменной. Оно при любых допустимых значениях равно 1.
x or not x = 1 всегда независимо от x
Или вы можете привести контрпример? Так притензии именно к стандарту что именно он порождает такой бардак, который ставит под сомнение применение «С++ на практике».

Неопределенным поведением не решалась проблема лексики. Этим решались проблемы чтения/записи, ввода/вывода, срабатывание на прерывания, перепайки проводов на стендах и все прочего, что не относится к формальной логике.

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

В стандарте есть примерно 100500 уточнений, что неопределенное поведение, это значит неопределенное поведение.

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

Тогда почему он возвращается 0, а не исключение? Исключение не разрушает логику, в то время как 0 разрушает.

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

А 0 скорее всего вывелся как результат `undefined && !undefined = undefined` с преобразованием у нулю в конце. Вполне корректная конструкция для работы с некорректным кодом, ведь так любую цепочку вычислений можно свести до бесплатной в рантайме константы. И для ннпривязанного никуда int это будет 0.

А ничего что вы, на ровном месте, порождаете сущность не предстваимую данным типом данных. Вас это не смущает? Почему не double(3.14) не комплексную единицу или вообще контекст потока? Почему undefined -> int не равно throw exception? Почему выбрали undefined -> 0 — это явное нарушение логики.

Тут такая же ущербная логика?
unsigned inc(unsigned x) { unsigned u; return 1u+x+u-u; }

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

Почему undefined -> int не равно throw exception?

Потому, что это сломается в большем количестве мест, чем вы ожидаете

Почему выбрали undefined -> 0 — это явное нарушение логики.

Вы ожидаете, что undefined подчиняется правилам арифметики целых чисел? Но же бессмысленно, если речь идёт о потенциально некорректном коде.

И вообще радуйтесь, что у вас 0, а не `rm -rf /`

Еще раз. Тут логическая ошибка в компиляторе. Он успользует не верные предпосылки, на основе которых делает абсолютне не верные выводы. И это стандарт считает нормальным. — Вы слепые?
x=undefined
y=undefined
Так вот во первых эти undefined разные, во вторых они могут быть разных типов и в третьих для них справедливы математические операции которые используют эти типы, не смотря на то что они undefined

int x=undefined
x*0=0
0*x=0
x-x=0
x||!x=1
x==x
int y=undefined
x и y это разные undefined
Тут же просто всё упростили в ввели обычный «undefined» которые ни типа ни операций не имеет.
Даже банально засунуть несколько случайных чисел и провести расчет, то можно получить более вменяемый результат.

Те кто пишет стандарты, вы какие наркотики используете, что бы это не замечать? Какой может быть практика использования C++ если у вас нет комилятор использует не консистентую математику. И нет иструмента который может сказать что программа будет испорчена компилятором, потому где-то в недрах был UB. И после этого это язык программирования?
И самое главное все ошибки компилятора списываются под ковёр на UB.

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

Это всё по вашей логике которую вы хотели бы видеть, но в чужой монастырь так сказать.
Вы хотите
int x=undefined
x*0=0
0*x=0
x-x=0
x||!x=1
x==x

а по факту (что я считаю имеет смысл)
int x=undefined
x*0=undefined
0*x=undefined
x-x=undefined
x||!x=undefined
x==x = undefined

Любое взаимодействие с undefined должно приводить результат к undefined

undefined != random number
По факту это детская ошибка. Сделанная гуманитарием, слабо понимающий математику. И вообще не понимающего последствий сделанного.
Вот что происходит. Всё банально до ужаса.

int x;

Значение x определяется как специальное значение undefined.

Дальше веселей для undefined введён ряд операций
например: godbolt.org/z/YMqz3hxzd
undefined&0=0
undefined|x=-1 (если x!=0)
undefined*0=0
0*undefined=0
undefined -> int -> no_value или 0 как повезет
value + undefined = undefined
undefined==undefined — false
undefined!=undefined — false
undefined<=undefined — false
undefined>=undefined — false
undefined+undefined = undefined
undefined-undefined = undefined
undefined*x = undefined — где x!=0
и еще куча несуразицы.

И компилятор имея эту мякоть взятую с потолка не проверенную и не согласованную (ничего общего не имеющей со здравым смыслом, математикой и тем что происходит в железе). Выполняет преобразования по этим правилам. Результат предсказуем получается лютая хрень, в зависимости от порядка действий. Но исправлять лень не хочется, поэтому договорились этот(у) баг фичу списывать на UB и все кто в это не верит — еретики и нечего не понимают.
Так что не надо говорить о высоких материях, что это не коректная программа на C++. Это банально баг компилятора и ошибка в логике преобразований.

ps: Смотрю тут как-то мало истенно верующих в стандарт. Хэнк негодует. Как-то вяло минусуете. Еще раз такой стандарт который оправдывает такие тупые и примитивные ошибки (которые столько времени в поде) нахрен не нужен, более того он вреден, тем более платный. Вы за#6&тесь устаните отлавливать подобные хитро спрятанные UB в большой кодовой базе.

Так вот во первых эти undefined разные, во вторых они могут быть разных типов и в третьих для них справедливы математические операции которые используют эти типы, не смотря на то что они undefined

Те кто пишет стандарты, вы какие наркотики используете, что бы это не замечать? Какой может быть практика использования C++ если у вас нет комилятор использует не консистентую математику.

ознакомьтесь например с арифметикой над NaN по IEEE754, оно полностью попадает под ваше определение

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

Неинициализированная переменная получит значение в рантайме, а не при компиляции. Просто это значение может быть произвольным, т.к. во время компиляции компилятору ничего не известно о содержимом стека или регистров. Следуя формальной логике и правилам языка С, результат выражения x || !x вообще не зависит от х и должен быть всегда равен true. В этом случае компилятор может оптимизировать локальную переменную, убрав её полностью. И заменить результат выражения константой времени компиляции. Поэтому функция должна вернуть значение, отличное от 0 (пусть и не обязательно 1).

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

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

Именно это UB и означает, что произойти может что угодно.

Потому что так сказал Хэнк, а то что говорит Хэнк правда. :)

Я отказываюсь в это верить. Куда машинный код катится...

К сожалению, корректность программы не является вопросом веры.

Практика ISO C++ 14 Std, MSVS2019 (v142)!

int main() { int i; i++; printf("%d\n", i); }

Дизассемблер:

int i; i++;

012D2125 mov eax,dword ptr [i]
012D2128 add eax,1
012D212B mov dword ptr [i],eax
printf("%d\n", i);

Результат: -858993459

int main() { int i=666; i++; printf("%d\n", i); }

Дизассемблер:

int i=666;
003A2125 mov dword ptr [i],29Ah
i++;
003A212C mov eax,dword ptr [i]
003A212F add eax,1
003A2132 mov dword ptr [i],eax
printf("%d\n", i);

Результат: 667

Я же говорю, всё нормально будет!

Ну и к чему вы это привели?

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

Но вы этого не доказали. Вы просто продемонстрировали что иногда неопределённое поведение совпадает с вашими ожиданиями.

Ну на самом деле, если программист хочет посмотреть, что в памяти до инициализации, он должен иметь вариант не инициализировать переменные!

Мы можем быть не очень счастливы от такого поведения компилятора, поскольку его предположения на счёт вывода реального значения указателя на функцию оказались ошибочными. Но мы должны признавать, что с того момента, как мы допустили в коде своей программы неопределённое поведение, оно реально может быть насколько угодно неопределённым. https://m.habr.com/en/company/infopulse/blog/338812/

Фигово, чо...

Плохо, что clang все соптимизировал к xor eax,eax.

Мой вариант такой...

00F61EA5 cmp dword ptr [x],0
00F61EA9 jne __$EncStackInitStart+31h (0F61EBDh)
00F61EAB cmp dword ptr [x],0
00F61EAF je __$EncStackInitStart+31h (0F61EBDh)
00F61EB1 mov dword ptr [ebp-0D0h],0
00F61EBB jmp __$EncStackInitStart+3Bh (0F61EC7h)
00F61EBD mov dword ptr [ebp-0D0h],1
00F61EC7 mov eax,dword ptr [ebp-0D0h]

Результат: 1

Следуя формальной логике компилятор может оптимизировать UB. В данном случае проще всего вывести неопределенный результат операции как 0, вероятно, как int по умолчанию. В каком-то другом случае оптимальным может быть что-то иное

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

В примере из комментария примерно такое же неверное поведение компилятора. результат выражения x || !x для параметра типа int по правилам языка от х не зависит вообще. Поэтому корректно будет либо выдать ошибку, либо выполнить оптимизацию в виде return int(true), что для int вернёт число, отличное от 0. Локальная переменная в этом случае смысла вообще не имеет и может быть редуцирована.

Компилятор действовал в соответствии со стандартом языка.


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

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

Нет, это вы не понимаете что такое неопределённое поведение. Оно и в самом деле никак не определено, произойти (согласно стандарту) может что угодно.


Не бывает корректного и некорректного неопределённого поведения.

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

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

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

С чего бы обращение к переменной должно что-то там триггерить?


Возьмём вот такую программу:


int foo(int x, int y) {
    int z = x+y;
    return z;
}

Переменная z тут локальная? Да. Обращение к ней есть? Есть. Память под неё выделять нужно? Нет, не нужно.

В примере память под z не пригодилась и её можно не выделять, а в примере с UB память пригодилась - читать-то надо откуда-то!

Но ведь из переменной z мы тоже читаем.

Но у нас же оптимизирующий компилятор, если читаем и выходим, то даже не пишем и, тем более, не храним, сразу результат на foo и на выход. А с UB, переменную нужно выделить, т.к. читать приходится полюбому и пользоваться содержимым в дальнейших вычислениях.

Написанное вами прямо противоположно смыслу UB.

Ну, конечно! Это всего лишь моё переосмысление UB. Я же не настоящий программист - промавтоматика :D

То есть вы согласны, что если вы допустите опечатку в коде, не инициализировав явно какую либо переменную, то компилятор имеет право вызвать Format c:/ на вашей машине, со всеми вытекающими? Это тоже будет соответствовать стандарту? UB ж, что хочу, то и вызываю, так?

Ну, вообще-то да. Это одна из причин по которым я не пишу на С++.

А если посмотреть очень строго, то применение логических операций к арифметическим типам напрямую невозможно. Всегда следует скрытое преобразование типа, в данном случае (int) в (bool).
Для сравнения можно попробовать исходный пример с булевым типом.

auto buf = std::unique_ptr<uint8_t>(new uint8_t[10 * 1024 * 1024]);

Тоже предлагаете нулями инициализировать?

Да, всё инициализировать нулями пока не сказано обратное.
Вот nullptr ввели, что мешает ввести noinit.
auto buf = std::unique_ptr<uint8_t>(new<std::noinit> uint8_t[10 * 1024 * 1024]);

ps: И главное. Антон вот на этом моменте www.youtube.com/watch?v=g2iyNH2Gh1k&t=1257s говорит очень интересную вещь, при этом, он даже не поморщился. «Казалось бы — но нет». Нет надо вхардкоидить оптимизации для конкретных типов char и int8_t. Где логика? А точно, меморимапа не хватает же.
Вот что мешает ввести явные модификаторы работы с целыми числами помимо restrict, short и long
modular int
saturated int
overflow_check int

И убрать эти UB
Никто не будет писать просто так без причин «overflow_check int» вместо каждого «int» как здесь и в тысяче других мест: `for (overflow_check int i = 0; i < n; ++i)`. А где будет осознанно написано — так человек уже понимает чем это может быть плохо.

а если никто не будет писать специально, значит ничего не изменится. Если поменять молча «int» на «overflow_check int», то производительность внезапно может где деградировать. Куда не плюнь — будут отрицательные side-effects.
Вы не поверите, но люди пишут template<typename T>…
или даже так typedef overflow_check int int_s;
не говоря уже о том, что в цикле надо использовать не int, а что-то типа std::ssize_t так как sizeof(int)<sizeof(ssize_t) на нынешних платформах.
Не по умолчанию надо сделать модульную арифметику. Тем кому понадобиться математика с насыщением или проверкой переполнения, вот они пусть явно это указывают. Тоже самое и для инициализации 0 — тем кому не важно что в переменной будет мусор, пусть пишут это явно. Зато по умолчанию так будет 0 и куча UB и ошибок уйдет само собой, даже кода меньше придётся писать.

Тоже самое и для инициализации 0 — тем кому не важно что в переменной будет мусор, пусть пишут это явно.

Тем кому важно, что в переменной будет мусор, пусть явно инициализируют 0.

Зато по умолчанию так будет 0 и куча UB и ошибок уйдет само собой, даже кода меньше придётся писать.

По умолчанию я не хочу платить перфомансом и больше писать кода.

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

И сколько вы заплатили за последний месяц?

Нисколько.

Наоборот это приводит к большим потерям, кучи UB и потерям человекочасов на поиск ошибок.

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

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

Симметрично: вам лень думать, либо вы не способны это понять

Можно сказать, что вы выбрали для себя не тот ЯП, на котором вам было бы комфортно работать.

C++ was designed with an orientation toward system programming and embedded, resource-constrained software and large systems, with performance, efficiency, and flexibility of use as its design highlights.

C++ was designed

Вот именно был, а превратили в то на что противно смотреть.
Какие нафиг large systems если в тривиальных преобразованиях он делает недопустимые ошибки.

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

Если хотите, что бы стандарт учитывал ваши хотелки, пишите proposal.

Ну или по заветам Бендера

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

ps: Есть еще вариант использовать не сломанные компиляторы.

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

Вы выдаете желаемое за действительное. Тут нет никакой критической ошибки и вы упорно отрицаете стандарт.

Вы действительно считаете, что если написать такой пропозал, то "вот эти люди" будут способны договориться? Судя по тому что они выпускают, Я вот сильно сомневаюсь.

Ваши хотелки, это только ваши хотелки. И я уверен, что комитет их не примет, т.к. профит неочевиден.

ps: Есть еще вариант использовать не сломанные компиляторы.

А еще можно форкнуть компилятор (и стандарт за одно). xD

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

вот раньше можно было о переполнении судить по флагам OF, CF процессора. Сейчас уже не прокатит?

В Вас говорить перфекционист. Задайте один простой вопрос — кто за это заплатит? За ту работу, которая требуется, чтобы корректно выпилить UB? И кто будет платить за то падение производительности кода, которое возникнет в результате, хотя бы на первых порах? И ради чего? Чтобы кто-то меньше задумывался, когда пишет на с++? Ну, если хочется меньше думать над кодом, можно выбрать другой язык программирования.

Касательно же формальной логики и того, что компилятор должен оптимизировать по ее правилам и отказаться от UB… ну, а условное f(x) = f(x) || !f(x) Вы тоже предложите свернуть по правилу исключения третьего? Как бы ни было удивительно, но даже в насквозь дискретном, сиречь цифровом, программировании возможен парадокс Рассела.

Потому что чтение из неинициализированной переменной есть Undefined Behavior.

А UB подразумевает под собой любое поведение скомпилированного, в результате, кода.

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

Извините, что лезу с джавошный рылом во ваш сиплюплюшный ряд, но во это UB — это поведение чего? Получившейся программы или компилятора?

Если компилятора — то да, всё нормально — он «пошёл в разнос», имеет право. Но в этом сильно сомневаюсь, т.к. этак и вставку трояна в любое место UB можно оправдать (а это, если сделано сознательно автором компилятора — уголовная статья в кодексах всех развитых стран).

А вот если это UB получившейся программы — то это всё же косяк и переоптимизация компилятора. Т.к. задача компилятора трансляция кода с одного языка на другой, то как и всякий переводчик он ответственности за смысл не несёт: его задача чтобы «реальная процессорная» машина, делала в точности тоже, что идеальная «С++ машина» (т.е. непосредственно исполняющая С++ код).
Оптимизации допустимы в тех местах, где компилятор знает как оптимизировать. А если не знает — он ничего оптимизировать права не имеет. Это простой здравый смысл!
Очевидно, что UB относится к последнему случаю. Т.е. нормальный компилятор при виде UB должен либо прекращать компиляцию (программа ведь некорректна! Нахрена её компилировать?), либо переходить к буквальной компиляции. Но никак не делать ничего третьего.

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

P.S. сам пишу в том числе и транслитераторы (с «мёртвого языка» на «полумёртвый»), знаком с подобными проблемами не по наслышке.

Т.е. нормальный компилятор при виде UB

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

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

WTF? Прикольно делать выводы на не доказанных утверждениях. Более того ложных утверждениях.
1. программист не допускает ошибок — поржал, это вообще фичА стандарта.
2. выполнять все оптимизации в расчёте на UB которую которую компилятор не может обнаружить. Но если обнаружил то молчит и делает максимально через ж#пу или на оте%&сь.

Грубо говоря, недопущение подобных ошибок ложится на программиста, а не на компилятор.

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

Если брать пример с неинициализированной переменной, то в общем случае UB обнаружить нельзя,

Ложь и лицемерие. Он тупо использует элемент который не принадлежит множеству значений переменной, и использует правила которые ни имеют никакого отношения к типу переменной.
Более того никакого выигрыша в производительности не получает. Вернуть правильный ответ или не правильный по скорости не отличается. Вот если бы он выкинул всю программу, то да — идеальная оптимизация. Но пока это слишком. Может быть позже.

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

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

Я смотрю Вы всегда говорите правду, даже если для этого приходится врать.

"Вам, товарищ майор, хорошо: рот закрыли - рабочее место убрано" (C).

WTF? Прикольно делать выводы на не доказанных утверждениях. Более того ложных утверждениях.

это не "не доказанные утверждения", это аксиомы, исходя из которых проектируется компилятор.

программист не допускает ошибок — поржал, это вообще фичА стандарта.

Вообще, мягко говоря это очень плохая практика сваливать ответственность с компилятора на самых безответственных людей — программистов

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

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

тут нет "правильного" ответа.

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

Почему в списке аксиом по которым проектируется компилятор нет аксиомы не противоречивости аксиоматики?

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

И хуже того, вы выбрали очень плохой пример для иллюстрации проблем с UB в C/C++, и поэтому ваше поведение выглядит как война с ветрянными мельницами

А в каком месте она противоречива?

Из всех возможных исходов он молча выбирает исход которого нет.
Она контринтуитивна
это не важно, важно что она противоречива.
выбрали очень плохой пример для иллюстрации проблем с UB
этот самый простой.
Основная притензия в том что смотря на кусок программы вы не можете сказать что она делает. Вообще. Вам надо вкапываться во все детали, а лучше сразу смотреть ассемблерный код. Например вы видите цикл, в нём условие. Но его компилятор может выкинуть, если внутри цикла вызывается функция которая может быть заинлайнена, при этом в функции окажется UB с переполнением int. Еще веселей если это еще закопано в многоэтажных шаблонах. Просто нечаенная опечатка или вызов логирования, способны развалить рабочий код. Это просто достойно всяческих похвал.

Просто нечаенная опечатка или вызов логирования (логгирование без ошибок не сломает код), способны развалить рабочий код. Это просто достойно всяческих похвал.

ошибки программистов ломают код (ваш кэп), UB это класс ошибок программиста, отличающийся лишь тем, что ломает код не гарантированно или не определенным образом. Оно блин в честь этого и названо undefined behavior. Ждать от UB гарантированного поведения глупо по определению.

Да, я в целом согласен, что на не инициализированную переменную можно было бы и ошибку компиляции выдавать, но, такого поведения можно добиться от любого с++ компилятора (например -Werror -Wuninitialized в clang/gcc).

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

Ну а еще это UB ловится любыми компиляторами и линтерами, так что конкретно его опасность сильно вами преувеличена

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

Чтение из неинициализированной переменной подразумевает Undefined Behavior по отношению к возвращаемым данным (Data arbitrary), но не Control flow violation.

То есть грубо говоря, код типа

int f () { unsigned char x; return x * 10; }

должен вернуть целое число в диапазоне от 0 до 2550. Потому что значение х не определено, Но явно определён его диапазон и операции над ним. И в целях оптимизации компилятор имеет право всегда возвращать, скажем, 0 или 1000. Но никогда -1. Это будет некорректным поведением априори.

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

Чтение из неинициализированной переменной подразумевает Undefined Behavior по отношению к возвращаемым данным (Data arbitrary), но не Control flow violation.

В стандарте понятие UB трактуется значительно шире, при этом UB не делится на подвиды. Там нет таких понятий как "UB по отношению к возвращаемым данным" или "Control flow violation".

С UB, приводящем к "Control flow violation", я сталкивался лично, правда, на C.

должен вернуть целое число в диапазоне от 0 до 2550. Потому что значение х не определено, Но явно определён его диапазон и операции над ним. И в целях оптимизации компилятор имеет право всегда возвращать, скажем, 0 или 1000. Но никогда -1. Это будет некорректным поведением априори.

Это больше похоже на Unspecified Behavior. В случае же UB результирующая программа, если скомпилировалась, имеет в месте возникновения UB поистине безграничные полномочия: может не только вернуть любое значение int, но даже не вернуть никакое, например, завершившись.

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

Что касается control flow violation, это уже указывает, как правило, на серьёзные ошибки в компиляторе. (Компиляторами заниматься приходилось, так что я знаю, о чём говорю)

В данном примере UB относится только к значению переменной. Весь остальной код полностью валиден согласно стандарта и должен исполняться соответственно.

тогда в стандарте было бы написано "yields unspecified value" а не "undefined behavior". Я еще раз приведу пример с санитайзером: при чтении значения из не инициализированной памяти программа падает, и это полностью соответствует стандарту. А то, о чем вы говорите - то, как компилятор пытается обрабатывать такое чтение по умолчанию.

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

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

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

Вы слишком вольно трактуете понятие UB.

Ну, что ж, придётся обратиться к последнему прибежищу интеллектуала, то есть, к первоисточнику (draft стандарта C++17):

3.27
[defns.undefined]
undefined behavior
behavior for which this document imposes no requirements

"Поведение, к которому данный документ не предъявляет вообще никаких требований" (форма отрицания с частицей "no" -- более жёсткая, чем просто отрицательная форма; в русском варианте она соответствует фразе, в которую добавлено слово "вообще" или "абсолютно").

Если к поведению не предъявлено вообще никаких требований, то такое поведение может быть абсолютно любым (это же верное логическое следование?).

По этой причине я принципиально не могу трактовать понятие UB "слишком вольно", потому что не существует ещё более вольной трактовки по сравнению с "не предъявляет вообще никаких требований". То есть, возможность трактовать понятие UB слишком вольно у меня отсутствует даже теоретически, не говоря уже о практике.

В данном примере UB относится только к значению переменной. Весь остальной код полностью валиден согласно стандарта и должен исполняться соответственно.

С момента возникновения UB не имеет никакого значения степень валидности остального кода.

Вы пытаетесь предъявить требования к коду на поведение при UB, когда утверждаете, что "... остальной код полностью валиден согласно стандарта и должен исполняться соответственно". "Код должен" -- это прямое требование к некоторому определённому поведению кода при данном UB.

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

Что касается control flow violation, это уже указывает, как правило, на серьёзные ошибки в компиляторе.

Характерно, что как только UB в том моём случае был устранён, так control flow violation сразу же исчез совершенно чудесным и непостижимым образом.

Кстати, control flow violation "укладывается" в допустимое поведение при UB, поэтому, выходит, что компилятор в том случае был абсолютно прав.

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

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

Соответственно, изменение Control flow это проблема конкретного компилятора. Получается так.

Вы как-то странно понимаете отсутствие требований. Отсутствие требований к компилятору означает, что любая проблема из-за наличия UB в программе — вина программиста.

А кто должен программисту сообщить о потенциальном UB, как не компилятор? Если он этого не сделал, то это вина производителя - это раз.

Во вторых, программист никак не может влиять на сгенерированный компилятором код. А значит, все сайд-эффекты от выполнения сгенерированого компилятором кода - тоже ответственность производителя.

Так что подход "что не запрещено, то разрешено" очевидно не верен.

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

Так для тех UB, которые очевидны — компилятор обычно сообщает.


Проблема в том, что в языке С++ есть много мест, где потенциальное UB может быть в каждой строчке кода.

Если подумать, то на самом деле "неопределенного поведения" в принципе не существует. Каждый раз компилятор выполняет вполне определенный код: для неинициализированной переменной при чтении из неё вернётся мусор, при делении на ноль произойдёт исключение, при выходе за пределы массива - ошибка доступа к памяти и пр. Эти случаи достаточно однозначны. Всё, что выходит за типичное поведение в подобных ситуациях, должно являться (и по сути, является) некорректным поведением.

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

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

Не вижу противоречий. Платформенно-зависимые вещи должны стандартизироваться соответственно особенностям платформы. Как например размерность int.

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

За однозначностью лучше идти в управляемые языки или в Rust.

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

Если поведение, которое сейчас не определено, доопределить, то стандарт не станет проще, а наоборот, сложнее и толще.

... компилятор некорректно обработал UB, ...

вы можете чутка поднатужить мозг и понять наконец что значит слово undefined в термине undefined behavior?

Я думаю, что ваш уровень некомпетентности в вопросах с++ не даёт вам права кому-либо что-либо советовать в данной теме. Уровень вашего хамства впрочем тоже.

Итак, давайте еще раз пройдемся по тезисам. Undefined behavior является "неопределенным" по определению этого термина, приведенному в стандарте языка с++. С точки зрения стандарта языка с++ программа, содержащая UB, является некорректной, это факт. Компиляторы имеют право доопределять некоторые виды UB, однако в большинстве случаев вместо этого оптимизируют исходя из аксиомы о том, что программа корректна, то есть программист не допустил ошибку. Если ошибка есть, значит компилятор исходит из неправильного предположения и результат оптимизаций является непредсказуемым. Что, опять же, полностью удовлетворяет отсутствующим требованиям к UB.

Вы в одном лишь этом треде успели поспорить и с фактами, и с аксиомами, и с "по определению". Вы не осилили понять аргументацию как меня, так и других комментаторов. Обвинять кого-либо в некомпетентности с такой позиции равносильно росписи под диагнозом Даннинга-Крюгера. А то что я хам даже соглашусь. Опять же, я не обязан быть вежливым с людьми, исчерпавшими кредит моего уважения.

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

Почему "как считаю я"?
Разве в стандарте неоднозначно написано?

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

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

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

Компилятор не имеет даже теоретической возможности неверно "обработать UB", ибо любая теоретически возможная "обработка" соответствует стандарту и поэтому является верной.

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

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

Соответственно, изменение Control flow это проблема конкретного компилятора. Получается так.

Нет, получается не так.

Это проблема программиста, а не компилятора.

Компилятор в случае UB приобретает "абсолютные права" на всё, что угодно и что неугодно, в том числе, на нарушение Control flow, и никто не может ограничить его в этом "праве": для компилятора нет никого и ничего выше стандарта.

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

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

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

Компилятор в случае отсутствия UB теряет те самые "абсолютные права" на всё, что угодно и неугодно, в том числе, и "право" на нарушение Control flow.

Вот, как получается.

Если к поведению не предъявлено вообще никаких требований, то такое поведение может быть абсолютно любым (это же верное логическое следование?).

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

Извините, а какие ещё документы предъявляют требования к компиляторам С++?

Заодно какие документы предъявляют требования к программистам использующим C++?

Как минимум, правила языка и техническая спецификация компилятора, например?

"Правила языка" — это и есть стандарт, который цитировался выше.


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

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

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

Но это не значит, что они поэтому могут быть вообще любыми.

Откуда это следует?

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

По трудовому кодексу -- означает. По трудовому кодексу -- можно. Нельзя -- по другому кодексу.

Если и проводить аналогии с юриспруденцией, то стандарт соответствует совокупности всех кодексов.

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

Почему стандарт ему позволяет такое делать?

Если вам интересно почему там UB, то это потому, что реализация, которая будет крашить программу при обращении к не иницализированной памяти, соответствует стандарту. Это нужно для того же memory sanitizer'а.

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

Garbage in — garbage out. clang видит, что вы написали глупость и этот код не может выполнять никакую полезную функцию, а вернуть что-то из функции всё равно нужно. Поэтому он смело возвращает первую попавшуюся констатнту типа int. Логично, что на эту роль выбран 0. А стандарт это разрешает с очень важной целью — это открывает простор для оптимизаций. Не конкретно в этом примере, а в корректных программах на С++, конечно. К которым ваш пример не относится.
Вот именно. Стандарт позволяет делать garbage практически на ровных местах. Более того культивирует такую практику. Нельзя допускать введения ничем не обоснованных правил, взятых с потолка. Они всегда будут приводить к противоречиям и костылям.

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

ps: И главное если компилятор явно видит garbage in то зачем делать garbage out? Для чего выходить за множество значений типа к объектам другого множества без единого обоснования корректности данной операции. Потом приходится переубеждать толпы идиотов, которые с горящими глазами говорят что это по стандарту и ведёт к охренительному ускорению кода и бесплатно. И главное доказательств им не требуется, только вера в непогрешимость стандарта.

Ускорение достигается за счёт в том числе обрезания того кода, который "не может" выполняться. Вот, например, статья, где показано, как компилятор вырезает целые куски с UB. Вам может не нравиться эта философия языка, но с тем же успехом можно не любить философию Java, Smalltalk или Forth. Непонятно зачем кого бы то ни было переубеждать - просто пользуйтесь другими языками, благо, их тысячи.

Нет — У семи нянек детё без глаза.

ps: Любые неявные предположения — мать всех ошибок.

чтобы возвращать не 0 компилятор должен уметь символьные вычисления: слишком сильное требование

Можете пояснить? Не понял каким образом это должно работать?

логика ub такова, что в с точки зрения компилятора никогда не выполняется ub код. с точки зрения c++ _любое_ выражение с неинициализированной переменной (пусть она называется uninit) - это ub. математически некоторые выражения с uninit определены, например, uninit || !uninit равно 1, а uninit*0 равно 0. поэтому, чтобы гарантировать, что ub код не выполняется, компилятор должен уметь различать выражения:

  1. uninit || !uninit

  2. uninit*0

  3. uninit*2

в случаях 1 и 2 компилятор должен знать, что там гарантированно нет ub и поэтому код может выполняться, как он записан в исходнике. в случае 3 есть ub, поэтому (учитывая что ub код не выполняется) он не достижим и просто выбрасывается. если код нельзя выбросить, например, потому что он является аргументом return, то его выбрасывание - это замена на 0.

проблема в том, что компиляторы не настолько умны, чтобы различать случаи 1,2 и 3 и вынуждены следовать логике: если я не могу доказать, что нет ub, значит это ub. поэтому любое выражение с uninit - это ub. если бы компиляторы умели делать символические вычисления, то они могли бы доказать, что в 1 и 2 нет ub и выполнять их как есть.

Есть файл с ASCII-графикой, и мы его никак не меняем — из него нужны только байты. Но в С++ нельзя работать с файлом как с массивом байт: придётся открыть поток, считать информацию из потока в контейнер (string или vector) и только тогда работать с этим контейнером.
А почему нельзя сделать матрицу и массив лампочек (ссылки на элементы матрицы), менять только массив, зачем из файла читать, если только первый раз.
Можно просто заинклудить данные в строку/массив
stackoverflow.com/a/25021520
Поэтому сейчас мы примерно в 100 раз быстрее Java, что более-менее приемлемо, но все равно недостаточно.

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

потому что:

Logstash постоянно что-то сует в какие-то конкурентные ассоциативные контейнеры, в которых тратится огромное количество времени.

Мне было непонятно, является ли это какой-то принципиальной проблемой джавы, или это просто особенность реализации в Logstash?

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

потому что альтернатива сильно моложе доклада

Блок отвечает за управление лампочками, но это приходится делать в отдельном потоке, так как в C++ нельзя одновременно мигать лампочками и смотреть, нажал ли пользователь клавишу. Чтение из потока — блокирующее.

Неясно, что мешало использовать неблокирующие методы, тот же readsome(&c, 1).

Но в С++ нельзя работать с файлом как с массивом байт: придётся открыть поток, считать информацию из потока в контейнер (string или vector) и только тогда работать с этим контейнером.

Тут совсем претензии непонятны: простейшая обёртка пишется в несколько строк. Количество строк на небольших программках показателем не является, особенно в C++ - это не язык для написания скриптов на несколько десятков строк(хотя он даже с этим справляется).

Ну и сравнение некорректно - сравнивать куда более ограниченную модель bash/perl/etc и обобщённую модель C++ нельзя - здесь не учитывается количество возможностей, предоставляемых языком. Как минимум, пример нужно брать другой, какая-нибудь простенькая СУБД, например.

Неясно, что мешало использовать неблокирующие методы, тот же readsome(&c, 1).

Его поведение не переносимо, на многих платформах не работает, https://en.cppreference.com/w/cpp/io/basic_istream/readsome :

"Likewise, a call to std::cin.readsome() may return all pending unprocessed console input, or may always return zero and extract no characters."

Тут совсем претензии непонятны: простейшая обёртка пишется в несколько строк.

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

Функция, принимающая char8_t* на треть короче.

Какие компиляторы делают лишний код [unsigned] char?

Когда вы работаете с unsigned char*, char* или byte*, компилятор думает, что char* может указывать на все, что угодно: на integer, string или пользовательскую структуру.

Это худо-бедно смотрелось бы в первом выпуске K&R, но при чем здесь современный C++? Еще в ANSI C для "чего угодно" использовался void *. А компилятор здесь вообще не должен ни о чем думать, а должен работать с указанным типом, как положено по стандарту.

И когда компилятор видит, что на вход передается char* и что-то извне посылки, то думает, что любая модификация этой переменной может поменять те данные, на которые указывает char*

И правильно думает - здесь прежде всего нужен const. А сам по себе char8_t не предполагает никакой подобной магии.

Это худо-бедно смотрелось бы в первом выпуске K&R, но при чем здесь современный C++? Еще в ANSI C для "чего угодно" использовался void *. А компилятор здесь вообще не должен ни о чем думать, а должен работать с указанным типом, как положено по стандарту.

Ну так именно что стандарт и разрешает char* указывать на что угодно.


И правильно думает — здесь прежде всего нужен const. А сам по себе char8_t не предполагает никакой подобной магии.

А как тут const поможет?

стандарт и разрешает char* указывать на что угодно

Где он это разрешает? И, даже если б и разрешал, это может позволить компилятору лишь проглотить конструкцию без ошибки, но никак не менять режим генерации кода.

как тут const поможет?

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

Разрешает стандарт это в пункте 7.2.1 (11):


If a program attempts to access (3.1) the stored value of an object through a glvalue whose type is not similar (7.3.6) to one of the following types the behavior is undefined
(11.1) — the dynamic type of the object,
(11.2) — a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
(11.3) — a char, unsigned char, or std::byte type.

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

Здесь стандарт не "разрешает", а определяет, что будет, если насильно обращаться к объектам через указатель/ссылку на чужой тип. Понятно, что гарантированно (и совместимо) работать будет только байтовый доступ. И поведение тут "undefined" только в рамках абстрактной программы в вакууме, а фактически оно platform-dependent, поскольку такое совершенно нормально для низкоуровневого кода. Никакого запрета здесь нет, есть лишь ограничение переносимости.

С какими компиляторами, и в каких режимах можно наблюдать, как компилятор "думает" и "выгружает", если в функцию передан указатель на char? Я такого не видел нигде и никогда.

Насчет const я говорил в том смысле, как мне удалось понять ту кривую фразу про "что-то извне посылки". Если она расшифровывается иначе - возможно, const там действительно не нужен.

Это именно что разрешение. Согласно стандарту, через указатель типа char* можно обращаться к любым объектам не вызывая UB — а следовательно, компилятор обязан учитывать тот случай, когда указатель data указывает на result. Именно это и "удлиняет" машинный код функции do_something из примера в статье.


С какими компиляторами, и в каких режимах можно наблюдать, как компилятор "думает" и "выгружает", если в функцию передан указатель на char? Я такого не видел нигде и никогда.

С любыми достаточно современными. GCC, Clang, MSVC. Тут на Хабре уже много раз приводили примеры, так что приводить их ещё раз как-то лень.


Насчет const я говорил в том смысле, как мне удалось понять ту кривую фразу про "что-то извне посылки". Если она расшифровывается иначе — возможно, const там действительно не нужен.

Вы не можете через const запретить указателю data указывать на result — а потому модификатор const тут совершенно бесполезен.


И поведение тут "undefined" только в рамках абстрактной программы в вакууме, а фактически оно platform-dependent

Неопределённое поведение и правда не определено

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

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

С любыми достаточно современными. GCC, Clang, MSVC

Вот я посмотрел, что делают GCC 11.2.0 с -Ofast, и VC 19.29 с /O2. Как и 10, 20 или 30 лет назад, логика кода в режиме рабочей (максимальной) оптимизации никак не зависит от типа указателя, поскольку возможность наложения подразумевается для всего. Но VC как сохраняет промежуточные значения result, так и загружает значения из data в правильном порядке, а вот GCC почему-то соблюдает это для char и int, но не соблюдает для char8_t, short и long.

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

Вы не можете через const запретить указателю data указывать на result

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

Возможность наложения как раз от типов зависит. В случае char8_t стандарт говорит о UB в случае наложения с int, что равносильно запрету наложения.

Если следовать стандарту, то любой тип, отличный от [unsigned] char, равносилен запрету наложения. Но компиляторы, слава богу, пока не делают добуквенное соблюдение стандарта своей главной целью, а ориентируются на практические задачи, поскольку компилятор - средство решения задач, а не проверки соблюдения стандарта. Именно поэтому они чаще всего оставляют без внимания обсуждаемую особенность.

В итоге, скорее всего, в компиляторах будет реализован специальный, явно задаваемый режим "соблюдать все требования стандарта до мелочей". Но использоваться он будет главным образом для тестирования совместимости. А практические задачи будут продолжать решаться с использованием более очевидных конструкций, вроде того же restrict, __declspec (noalias) и подобных.

Э-э-э, в каком мире вы живёте? Компиляторы давно уже в целях оптимизации "осваивают" разные UB, и strict aliasing rule было "освоено" одним из первых.

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

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


И да, разрешённое наложение char с любым другим типом — одна из проблем языка, из-за которой и пришлось вводить char8_t.

Сдуру можно много чего сломать. А если по уму, то обещанная "максимальная оптимизация, невзирая на возможность наложения" так и должна работать - невзирая, а для случаев, где наложение возможно, должна быть возможность явно это указать. Но почему-то сложилось так, что явно можно указать лишь отсутствие наложение, поэтому оптимизаторы вынуждены осторожничать.

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

Поэтому добавление chat8_t может принести пользу исключительно "программисту нового типа", который записывает конструкции языка, весьма смутно представляя, что происходит под капотом. C++ таки не для этого, и эта тенденция к выхолащиванию очень удручает.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий