Re[24]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.04.24 02:11
Оценка:
Здравствуйте, CreatorCray, Вы писали:
CC>Теперь твоя очередь объяснять как ты из моего фейспалма вытащил утверждение "UB — ерунда"
Экстраполяцией.
CC>Что именно ты тут понимаешь под "интовая арифметика"?
сложения, вычитания, умножения.

CC>метапрограммирование



CC>Вот только не надо забывать что compile-time вычисления работают НЕ по правилам платформы, под которую выполняется компиляция.

Хм. И в каком месте стандарта это разрешено?
Насколько я понимаю, всё поведение компилятора за пределами UB достаточно жёстко ограничено. Он не имеет права менять семантику программы, в том числе, и в целях оптимизации.
То есть если у меня описана какая-то формула, которая при "вычислении в рантайме" даёт X, то компилятор может перестраивать эту формулу ровно в тех пределах, в которых она при исполнении всё ещё даст X.
В том числе и выполнять часть вычислений в compile-time. Если на целевой платформе вычисления выполняются по-другому, то компилятор обязан проэмулировать именно их.
Особенности тут могут быть только вокруг плавающей точки, где поведение определяется флагами компилятора.

CC>То, что ты в данном примере называешь UB — на самом деле очень даже defined behavior. Неожиданный — да, но не undefined.

Простите, какой именно эффект? Вывод oops вместо wow? Нет, это именно что undefined behavior. Именно поэтому он зависит от флагов компиляции.

S>>
S>>long avg(long a, long b, long c)
S>>{
S>>  return (a+b+c)/3; 
S>>}
S>>

CC>А что именно ты тут считаешь UB? Переполнение знакового типа имеет очень даже defined поведение для платформы, и если погромист не знает что творит — это недостаток квалификации.
Нет. Вы путаете implementation-defined и undefined behavior.
Если бы знаковое переполнение было implementation-defined, то ни с какими флагами компилятор бы не выдавал oops ни на какой платформе.
Однакож, он вполне успешно выдаёт oops на вполне известной платформе, нарушая ожидание.
Здесь, по стандарту, ровно два UB — в первом сложении и во втором.
Читаем стандарт:

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.

Эта клауза покрывает сложение с переполнением, вычитание с переполнением, умножение с переполнением, и деление на ноль.

CC>А у меня абсолютно везде беззнаковая, кроме тех редких мест где знак именно что нужен.

Ну, у вас — может быть. Поверю вам на слово.
Но в среднем такого нет. Язык настойчиво подталкивает программистов к знаковым операциям. Начиная прямо с auto a = 42. Какого типа будет a? То-то же.

CC>Потому что "оптимизатор" по какой то внутренней причине ниасилил свернуть формулу в символьном виде, сработал fallback path и он формулу посчитал внутри себя на таких же типах, на том же железе.

Совершенно верно. Вот GCC осиливает свёртку (потому что у них внутри есть SCEV), и его не получается сбить с толку введением временной переменной.
Но это ровно противоположно тому, что вы писали в предыдущем сообщении.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[25]: Carbon
От: CreatorCray  
Дата: 19.04.24 02:20
Оценка:
Здравствуйте, Sinclair, Вы писали:

CC>>Теперь твоя очередь объяснять как ты из моего фейспалма вытащил утверждение "UB — ерунда"

S>Экстраполяцией.




S>Хм. И в каком месте стандарта это разрешено?

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

S>Насколько я понимаю, всё поведение компилятора за пределами UB достаточно жёстко ограничено.

Для генерируемого кода, но не для AST трансформаций.

S>В том числе и выполнять часть вычислений в compile-time. Если на целевой платформе вычисления выполняются по-другому, то компилятор обязан проэмулировать именно их.

Вот тут лежит огроменный болт, ибо frontend пишется вообще без оглядки на платформу.

CC>>То, что ты в данном примере называешь UB — на самом деле очень даже defined behavior. Неожиданный — да, но не undefined.

S>Простите, какой именно эффект?
Compile time вычислений.

S>Если бы знаковое переполнение было implementation-defined, то ни с какими флагами компилятор бы не выдавал oops ни на какой платформе.

См выше про frontend
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[8]: Carbon
От: FR  
Дата: 19.04.24 06:09
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Да, если бы целое переполнение было IDB, то вопросов бы было гораздо меньше. Но нет, это именно UB, и, в отличие от IDB, компилятор имеет право доопределять UB произвольным образом в каждом месте использования.


Самое паршивое то, что де факто было IDB с новой версией стандарта может внезапно стать UB если вам не хватало UB в C, то вам принесли ещё от такого вообще никак ни застраховаться
Re[23]: Carbon
От: vdimas Россия  
Дата: 19.04.24 09:07
Оценка: +1
Здравствуйте, Sinclair, Вы писали:

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

S>Вот я вижу, что интовая арифметика используется примерно в каждой первой программе.

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

Знаковые целые нужны редко, в основном для оперирования сущностями, близкому по смыслу к "разница", "текущий остаток/баланс", или для традиционного возврата кодов или признаков ошибок.

У меня обсуждаемая проблема вечно вылазит в дотнете, бо системное АПИ расписано на знаковых, даже где это выглядит абсурдом.
Причина известна — не во всех языках есть беззнаковый тип, поэтому платформа затачивалась на слабое звено. ))

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


S>То есть если покрасить исходный код более-менее любого проекта в красный там, где "возможно UB", то его будет больше, чем не красного.


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


CC>>Если ударяться в странные конструкции и потом бороться со странностями compile time вычислений — то гемора будет побольше.

S>Что вы называете "странными конструкциями"? Мы кагбэ просто взяли сложение и сравнение — чо уж тут странного-то?

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


S>Compile-time вычисления сами по себе штука хорошая, поскольку позволяют сильно улучшить перформанс программ без потери общности. Те самые zero-cost abstractions, ради которых люди и выбирают C++ вместо менее упоротых языков.

CC>>Если придерживаться KISS и писать просто и строго по делу, не растекаясь шаблонами по шаблонам — таких проблем не будет.
S>Ооо, мне нравится ваш задор. Давайте попробуем починить UB в функции, написанной просто и строго по делу, безо всяких шаблонов:
S>
S>long avg(long a, long b, long c)
S>{
S>  return (a+b+c)/3; 
S>}
S>


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

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

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


S>А перенос вычислений в рантайм заставил сработать оптимизацию, которая воспользовалась UB в полном соответствии со стандартом языка.


Наверно, наоборот, compile-time вычисления произошли из предположения, что никакого UB не возникает, и тогда результат всегда false.

Кстате, мне Решарпер в C# иногда подсвечивает "тут у вас всегда true" (или всегда false), т.е. намекает на мёртвые ветки кода, хотя не факт.
Спасает то, что компилятор C# и JIT пока мест не делают суровых оптимизаций.
(можно сказать, что компилятор C# не делает никаких оптимизаций от слова вообще, кроме совсем скромных манипуляций локальными переменными)

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


CC>>Это архитектурный баг компиляторостроителей, который можно починить но никто не станет, ибо пуристы упрутся рогом.

S>Это интересная гипотеза, но она не подтверждается практикой.

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

А так-то, забесплатно можно расписать, к примеру, корректное сравнение знакового с беззнаковым:
#include <iostream>

enum int_t : int;
enum uint_t : unsigned int;

bool operator<(int_t a, uint_t b) {
    using uint = unsigned int;
    return int(a) < 0 || uint(a) < b;
}

int main()
{
    auto result = int_t(-42) < uint_t(42);
    std::cout << result << std::endl;
    return 0;
}

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

Еще популярный сценарий — индексы беззнаковые, а разница индексов знаковая, расписываются операции сложения беззнаковых индексов со знаковой разницей с контролем переполнения.
Отредактировано 19.04.2024 9:08 vdimas . Предыдущая версия .
Re[8]: Carbon
От: vdimas Россия  
Дата: 19.04.24 11:15
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>В данном случае ты даёшь оценку своим фантазиям, бо архитектуры/компиляторы явно определяют своё поведение при переполнении.

S>Ошибаетесь. Я же привёл пример. Каким образом "явно определённое поведение" приводит к разнице между -O0 и -O2 для приведённого фрагмента?

1. Твой пример содержит ошибки, например, для short, т.к. значением short(value)+1 будет значение типа int, т.е. еще до рассуждений об UB необходимо исправить ошибку:
template<typename signed_integral>
bool is_max(signed_integral value) {
    return signed_integral(value + 1) < value;
}



S>Что мы и наблюдаем в приведённом примере.


2. Мы наблюдаем невладение предметом, бо в C/C++ еще есть битовые поля, для которых это однозначное UB со всеми спецификациями платформы:
int main()
{
    struct S {
        int field1 : 8,
            field2 : 7,
            flag : 1;
    } s = { 127, 63, 1 };

    std::cout
        << (is_max(s.field1) ? "wow " : "oops ")
        << (is_max(s.field2) ? "wow " : "oops ")
        << (is_max(s.flag) ? "wow " : "oops ")
        << std::endl;

    using sbyte = signed char;
    
    std::cout
        << (is_max(static_cast<sbyte>(127)) ? "wow " : "oops ")
        << (is_max(static_cast<short>(32767)) ? "wow " : "oops ")
        << std::endl;

    return 0;
}



V>>Т.е. можно подобрать такое кодирование, что в твоём коде будет UB именно для этой платформы, например, для обратного кодирования есть два нуля для знаковых чисел — 0000 и 1111, где при сравнении первый ноль меньше второго, и твоя программа закономерно поломалась, как и предостерегал стандарт.

S> Нет. Не угадали.

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


S>Программа прекрасно ломается на совершенно любой платформе.


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

Фишка в том, что для промежуточных вычислений в железе часто используют регистры шириной в слово, т.е. UB возникает прямо в железе — ты можешь проверить это на последних gcc на x64, там будет тот же эффект в рантайм для int, что и в случае short(value)+1, т.е. даже в рантайме программа сломается (без оптимизации), потому что в ней ошибка — ты не можешь гарантировать ширину бит промежуточных вычислений, т.е. не можешь гарантировать, что переполнение действительно произойдет.
Отредактировано 19.04.2024 11:29 vdimas . Предыдущая версия . Еще …
Отредактировано 19.04.2024 11:24 vdimas . Предыдущая версия .
Отредактировано 19.04.2024 11:20 vdimas . Предыдущая версия .
Re[26]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.04.24 11:25
Оценка:
Здравствуйте, CreatorCray, Вы писали:
CC>Compile time вычислений.
Повторюсь: наблюдаемый эффект никак не связан с compile-time вычислениями.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[9]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.04.24 11:47
Оценка: 3 (1)
Здравствуйте, vdimas, Вы писали:

V>Здравствуйте, Sinclair, Вы писали:


V>>>В данном случае ты даёшь оценку своим фантазиям, бо архитектуры/компиляторы явно определяют своё поведение при переполнении.

S>>Ошибаетесь. Я же привёл пример. Каким образом "явно определённое поведение" приводит к разнице между -O0 и -O2 для приведённого фрагмента?

V>1. Твой пример содержит ошибки, например, для short, т.к. значением short(value)+1 будет значение типа int, т.е. еще до рассуждений об UB необходимо исправить ошибку:

V>
V>template<typename signed_integral>
V>bool is_max(signed_integral value) {
V>    return signed_integral(value + 1) < value;
V>}
V>

Это не отвечает на мой вопрос, т.к. программа выдаёт oops для int. И после вашего исправления она продолжит выдавать oops и для int, и для short.


V>2. Мы наблюдаем невладение предметом

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

V>Но что в любом обсуждении ты теперь на рефлексах считаешь собеседников по-умолчанию идиотами, и даже не мелькает мысли проверить себя — это изрядно утомляет, конечно...

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

V>Программа ломается, потому что в ней ошибки. ))

Указанная вами ошибка в ней действительно есть — integral promotions не учитываются. Но ломается она вовсе не поэтому.
Убедиться в этом очень легко, применив предложенный вами фикс:
https://godbolt.org/z/1EGzMnWs5

V>Ты написал некий обощённый код из некоторого обощённого предположения, но это предположение для многих ситуаций ложно.

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

V>Фишка в том, что для промежуточных вычислений в железе часто используют регистры шириной в слово, т.е. UB возникает прямо в железе — ты можешь проверить это на последних gcc на x64, там будет тот же эффект в рантайм для int, что и в случае short(value)+1, т.е. даже в рантайме программа сломается (без оптимизации), потому что в ней ошибка — ты не можешь гарантировать ширину бит промежуточных вычислений.

Я правильно понимаю, что вы ожидаете неверного поведения от моей программы на "последних gcc на x64 без оптимизаций" потому, что промежуточные вычисления он будет делать в 64 битах?
Это легко проверить — достаточно
а) попробовать починить программу предложенным вами образом
б) попробовать запретить оптимизации в исходной программе
Если вы правы, то а) даст wow, а b) — oops.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: Carbon
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 19.04.24 12:01
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

S>Это легко проверить — достаточно

S>а) попробовать починить программу предложенным вами образом
S>б) попробовать запретить оптимизации в исходной программе
S>Если вы правы, то а) даст wow, а b) — oops.

Ловкач!
Re[24]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.04.24 12:08
Оценка: +1 -1 :)
Здравствуйте, vdimas, Вы писали:

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

И вы, конечно же, сможете это подтвердить, дав ссылку на более-менее любой публичный C++ — проект.
V>Знаковые целые нужны редко, в основном для оперирования сущностями, близкому по смыслу к "разница", "текущий остаток/баланс", или для традиционного возврата кодов или признаков ошибок.
Угу. Вся индустрия идёт не в ногу, только vdimas идёт в ногу.

V>У меня обсуждаемая проблема вечно вылазит в дотнете, бо системное АПИ расписано на знаковых, даже где это выглядит абсурдом.

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

V>Причина известна — не во всех языках есть беззнаковый тип, поэтому платформа затачивалась на слабое звено. ))

Опять смелая догадка.

V>Переполнения принято производить с беззнаковыми, а со знаковыми принято оперировать так, чтобы переполнения не возникало.

Не, просто 99% С++ программистов вообще не в курсе, что знаковое переполнение — это UB.
Большинство пребывают примерно в таком же заблуждении, как и вы — что это Implementation-defined.

V>Что код примера не обязательно выполняется в реальном железе, а порой только на этапе компиляции.

Я правильно понимаю вашу мысль, что причина ошибки тут — в том, что код примера выполнился на этапе компиляции, при этом там было какое-то такое железо, в котором (MAX_INT+1) оказался больше MAX_INT?
И этот неверный результат попал в конечную программу, которая должна исполняться на каком-то другом железе, где это не так?
Или я вас неверно понял?

V>Хороший пример, кстате.

V>Что в дотнете, что в плюсах такими вещами упражняюсь путём приведения к более широкому типу, поэтому int64 пролетает.
Ну, так какой ответ-то на этот пример?

V>Наверно, наоборот, compile-time вычисления произошли из предположения, что никакого UB не возникает, и тогда результат всегда false.

Давайте распишем это утверждение. "compile-time вычисления произошли из предположения, что никакого UB не возникает" — то есть что будет какое-то implementation-defined behavior.
И какое же поведение имел в виду компилятор?

V>Кстате, мне Решарпер в C# иногда подсвечивает "тут у вас всегда true" (или всегда false), т.е. намекает на мёртвые ветки кода, хотя не факт.

V>Спасает то, что компилятор C# и JIT пока мест не делают суровых оптимизаций.
Дело не в суровости оптимизаций. Применимые в данном примере оптимизации они таки делают.
V>(можно сказать, что компилятор C# не делает никаких оптимизаций от слова вообще, кроме совсем скромных манипуляций локальными переменными)
V>Когда в дотнете, наконец, случится обещанная тобой еще 20 лет назад суровая оптимизация — напорешься на аналогичные эффекты. ))
На аналогичные — нет. Ваши утверждения основаны на неверном понимании как С++, так и дотнета.
Давайте посмотрим, что делает дотнет в описанном примере:
https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKGIGYACY8pJ0hgYQYG8aH+mjZq2AQIAGwYBJXAFlsCABQYAFgEtcDNQDsMDBAEoGAXgB8DRQgDU5IwB59Abj4D6TFlt0NZio72oCgUwA7J4YAHTyCABq2OIArjDhMlG+DAD8DOShIAwopM4BAgC+LvxuwmEMAHKKOnqGPGVBxKEIyXIKaZnZDLn5hYGl1MVAA===
Внимательно смотрим в код метода M и видим, что
а) оптимизатор заинлайнил код IsMax, как и следовало ожидать
б) оптимизатор выполнил compile-time вычисления, как и следовало ожидать
в) оптимизатор при этом вернул корректный результат. Опять-таки, как и следовало ожидать. И не потому, что он "не догадался" что-то там соптимизировать в неверную сторону, а потому, что в дотнете арифметика устроена не так, как в С++, в частности по отношению к знаковому переполнению.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[11]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.04.24 12:10
Оценка: :)
Здравствуйте, Pauel, Вы писали:
P>Ловкач!
А то.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: Carbon
От: vdimas Россия  
Дата: 19.04.24 14:31
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

V>>1. Твой пример содержит ошибки, например, для short, т.к. значением short(value)+1 будет значение типа int, т.е. еще до рассуждений об UB необходимо исправить ошибку:

V>>
V>>template<typename signed_integral>
V>>bool is_max(signed_integral value) {
V>>    return signed_integral(value + 1) < value;
V>>}
V>>

S>Это не отвечает на мой вопрос, т.к. программа выдаёт oops для int. И после вашего исправления она продолжит выдавать oops и для int, и для short.

Не-а, перестаёт выдавать ошибку для многих компиляторов, или меняет поведение на правильное для платформы x86 для некоторых компиляторов, даже если в них всё еще не работает в x64.


V>>2. Мы наблюдаем невладение предметом

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

Я не усложнял твой код, я показывал примеры, где твой код заведомо не работает даже в дебаге.


V>>Но что в любом обсуждении ты теперь на рефлексах считаешь собеседников по-умолчанию идиотами, и даже не мелькает мысли проверить себя — это изрядно утомляет, конечно...

S>Я не считаю собеседников идиотами. Но иногда бывает так, что собеседник выставляет идиотом сам себя.

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

А как же "умный человек всегда сомневается" и вот это всё?


S>Вот что вам помешало перед тем, как фонтанировать заблуждениями, просто пойти по ссылкам на gotbolt, которые приведены ниже в обсуждении?


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

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


V>>Программа ломается, потому что в ней ошибки. ))

S>Указанная вами ошибка в ней действительно есть — integral promotions не учитываются. Но ломается она вовсе не поэтому.
S>Убедиться в этом очень легко, применив предложенный вами фикс:
S>https://godbolt.org/z/1EGzMnWs5

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


V>>Ты написал некий обощённый код из некоторого обощённого предположения, но это предположение для многих ситуаций ложно.

S> Вопрос не в том, в каких ситуациях это предположение ложно, а том, почему оно ложно в конкретной рассмотренной ситуации.

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

Кстате, в C# это точно так же допустимо по стандарту.
Поэтому, готовься ловить аналогичные прилёты и в этом языке, если/когда ему прикрутят человеческий оптимизатор.

Я выше не просто так обратил внимание на твою стрёмную позицию в обсуждении как таковую.
Из-за непонимания причины происходящего ты набросился на компиляторы С++, что они, мол, умыли руки из-за UB.

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

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


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


Обобщенный код должен вести себя правильно для различных типов, иначе нафига он такой обобщённый нужен? ))


V>>Фишка в том, что для промежуточных вычислений в железе часто используют регистры шириной в слово, т.е. UB возникает прямо в железе — ты можешь проверить это на последних gcc на x64, там будет тот же эффект в рантайм для int, что и в случае short(value)+1, т.е. даже в рантайме программа сломается (без оптимизации), потому что в ней ошибка — ты не можешь гарантировать ширину бит промежуточных вычислений.

S>Я правильно понимаю, что вы ожидаете неверного поведения от моей программы на "последних gcc на x64 без оптимизаций" потому, что промежуточные вычисления он будет делать в 64 битах?
S>Это легко проверить — достаточно
S>а) попробовать починить программу предложенным вами образом
S>б) попробовать запретить оптимизации в исходной программе
S>Если вы правы, то а) даст wow, а b) — oops.

Первый вариант:
template<typename signed_integral>
bool is_max(signed_integral value)
{
    volatile signed_integral tmp = value + 1;
    return tmp < value;
}

https://godbolt.org/z/fTs1Gz663
Этот вариант использует operator<, где ошибка исходного варианта крылась в неуловимости старшего бита.

Чтобы поймать этот бит, нам пришлось явным образом указать на необходимость сохранения промежуточного результата в переменную (пусть даже локальную, т.е. потенциально регистровую). Сам факт сохранения переменной нас не интересует, нас интересует обязательность операции truncation до нужного кол-ва бит, дабы неуловимый разряд стал уловимым. В случае оптимизациии и выкидывания вызова is_max из рантайма, это тоже работает, т.к. в процессе compile-time вычислений выполняется такое же обрезание промежуточного значения, что и требовалось.

Но это всё-равно кривое решение — погоня за неуловимыми битами, детсад...
Чуть правильней будет не гоняться за неуловимым битом, а проверить его напрямую.
template<typename signed_integral>
bool is_max(signed_integral value)
{
    using u_t = make_unsigned_t<signed_integral>;

    u_t mask = static_cast<u_t>(0x8000000000000000ul >> (8 - sizeof(value))*8);
    u_t u_v = static_cast<u_t>(value);

    return ((u_v + 1) & mask) != 0;
}

https://godbolt.org/z/b5f8zxMnv
Здесь для избегания UB все битовые операции ведутся в беззнаковых целых.
Ради интереса замени тут:
    using u_t = make_signed_t<signed_integral>;

И погоняй на различных компиляторах.

Но это по-прежнему кривое решение, потому что достаточно сравнить число сразу с константой, без операции сложения:
template<typename signed_integral>
bool is_max(signed_integral value)
{
    return value == 0x7FFFFFFFFFFFFFFFull >> (8 - sizeof(value))*8;
}

https://godbolt.org/z/a7ajxzdsP

В целом обсуждение доставляет лулзы именно этим — эффектом неожиданности. ))
Даётся очень странное решение простой задачи, потом "следите за руками", потом "а вот тут фокус удался!" и прочая чепуха.

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

А если ты так пишешь, в духе исходного примера, то однажды будешь ловить прилёты и на любимом C#, когда он чуть подрастёт, бо гигиену надо соблюдать с детства, как грится! ))
Re[11]: Carbon
От: vdimas Россия  
Дата: 19.04.24 16:30
Оценка:
Здравствуйте, Pauel, Вы писали:

S>>Это легко проверить — достаточно

S>>а) попробовать починить программу предложенным вами образом
S>>б) попробовать запретить оптимизации в исходной программе
S>>Если вы правы, то а) даст wow, а b) — oops.
P>Ловкач!

Ага, опять ловко в лужу приземлился ))
Ему пришлось дважды разжёвывать, как тебе когда-то.
https://www.rsdn.org/forum/flame.comp/8734528.1

Еще обязательно кодом, и обязательно пояснять происходящее.
С такими навыками надо скромно спрашивать и долго обдумывать каждый свой следующий вопрос.
Но куда там...
Отредактировано 19.04.2024 16:34 vdimas . Предыдущая версия .
Re[11]: Carbon
От: vdimas Россия  
Дата: 19.04.24 17:05
Оценка:
Здравствуйте, vdimas, Вы писали:

Кстате, я поторопился (отвечал в рабочее время) и тоже допустил ошибку в одном из вариантов.
Обычная честная ошибка, которая легко отлавливается. ))
Re[11]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.04.24 17:39
Оценка: +1
Здравствуйте, vdimas, Вы писали:

V>Не-а, перестаёт выдавать ошибку для многих компиляторов, или меняет поведение на правильное для платформы x86 для некоторых компиляторов, даже если в них всё еще не работает в x64.


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

V>Я не усложнял твой код, я показывал примеры, где твой код заведомо не работает даже в дебаге.


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

V>В данном случае этот твой недостаток выходит на уровень упоротости, бо правильный ответ тебе был уже дан, но ты не проверил и продолжаешь падать.

Правильный ответ дал коллега CreatorCray. А вы продолжаете гнать чушь.

V>Я прошёлся позже, почитал и словил много лулзов, надо сказать.

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

V>Ладно ты не плюсовик, тебе простительно не держать в голове кучу низкоуровневых мелочей...

Вот именно. А вот вы, раз претендуете на профессионализм в плюсах, так плавать в этих нюансах вроде бы не должны.

V>Описание второй ошибки было дано в том же посте, так зачем ты разводишь лишные пинг-понги? ))

Не было у вас там никакого описания "второй ошибки". Был некий пример с битовыми полями, в котором вы намекаете на то, что не понимаете правил integral promotions.

V>Вынуждаешь повторяться — из-за допустимости промежуточных вычислений, выполняемых в более широких типах.

V>И это прямо по стандарту.
Ну, то есть вам было недостаточно один раз сказать глупость, вы хотите на ней настаивать.
Ок, давайте разберём подробно.
Вы намекаете на то, что компилятор, увидев вот такое выражение: (value+1)<value, где value имеет тип int, возможно исполняет его как (long(value)+1)<long(value).
Ну так вот, это — неверно.
Смотрим в справочник:

if the integer conversion rank of T is lower than the rank of int:
val can be converted to a prvalue of type int if int can represent all the values of T;
otherwise, val can be converted to a prvalue of type unsigned int.

То есть типы, более короткие чем int, допустимо расширять до int или unsigned int. Точка. Стандарт не разрешает промотить int до long.
Все более широкие promotions допустимы только для случаев char8_t, char16_t, char32_t, и для битовых полей (что, очевидно, не наш случай, но об этом позже).

Ваше замечание про short справедливо ровно потому, что short короче int. И по стандарту

arithmetic operators do not accept types smaller than int as arguments, and integral promotions are automatically applied after lvalue-to-rvalue conversion, if applicable.

Поэтому sizeof(value+1) > sizeof(value) в случае, когда тип value — short.
А вот для int sizeof(value+1) == sizeof(value) в любом компиляторе, соответствующем стандарту.

V>Кстате, в C# это точно так же допустимо по стандарту.

Нет, недопустимо. Там работают очень похожие правила integral promotions, и даже более строгие. В частности, С++ вот такой код прожёвывает без единого warning:
short x = 42;
x = x + 1;

А на С# это даёт ошибку компиляции.
V>Поэтому, готовься ловить аналогичные прилёты и в этом языке, если/когда ему прикрутят человеческий оптимизатор.
Спасибо, посмешили.
V>Я выше не просто так обратил внимание на твою стрёмную позицию в обсуждении как таковую.
Стрёмная позиция, коллега, тут только у вас. Потому что воинствующее невежество заведомо стремнее простого невежества.

V>Из-за непонимания причины происходящего ты набросился на компиляторы С++, что они, мол, умыли руки из-за UB.



V>Хотя суть происходящего не в С++, та же фигня возможна в любом языке, в котором будут происходит хоть какие-то заметные оптимизации.

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

V>Ключевое в обсуждаемом примере то, что UB в нём — это не побочный эффект "ужасного С++", это насущная необходимость, это даже непосредственная цель!


V>В противном случае пришлось бы обязательно обрезать результат каждого промежуточного вычисления.
Результат каждого промежуточного вычисления и так обязательно обрезается. Бит переполнения остаётся только в регистре флагов, и игнорируется в последующей арифметике.
В дотнете, как вы, должно быть, знаете, никакого UB для integral arithmetics нету, и ничего — никто не умер.

V>Обобщенный код должен вести себя правильно для различных типов, иначе нафига он такой обобщённый нужен? ))

Ну вот у вас пока не получилось написать правильный обобщённый код.

S>>Я правильно понимаю, что вы ожидаете неверного поведения от моей программы на "последних gcc на x64 без оптимизаций" потому, что промежуточные вычисления он будет делать в 64 битах?

S>>Это легко проверить — достаточно
S>>а) попробовать починить программу предложенным вами образом
S>>б) попробовать запретить оптимизации в исходной программе
S>>Если вы правы, то а) даст wow, а b) — oops.

V>Первый вариант:

V>
V>template<typename signed_integral>
V>bool is_max(signed_integral value)
V>{
V>    volatile signed_integral tmp = value + 1;
V>    return tmp < value;
V>}
V>

V>https://godbolt.org/z/fTs1Gz663
V>Этот вариант использует operator<, где ошибка исходного варианта крылась в неуловимости старшего бита.
) нет там никакой неуловимости.

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

И вот опять, вы сами себя перехитрили. volatile явным образом запрещает "регистровость" переменной, вместе с ещё рядом оптимизаций.
Собственно это и показывает, что исходное утверждение про "расширение битности" — полная чушь и невежественный бред.
Для того, чтобы запретить компилятору расширять битность выражения, достаточно было исходного фикса с принудительным приведением (value+1) к signed_integral.
В частности, в коде для short будет использоваться регистр AX, а не EAX.
И в обратную сторону это тоже работает — можно обойтись вообще без "сохранений в переменную", одними чтениями:
template<typename signed_integral>
bool is_max(volatile signed_integral value)
{
    return signed_integral(value + 1) < value;
}

Как видите, никаких переменных и сохранений не надо. Достаточно запретить компилятору полагаться на то, что слева и справа от сравнения стоит одно и то же значение — теперь он вынужден честно его вычислять, и это сразу даёт правильный результат. А без volatile мы налетаем на то, что gcc, к примеру, даже в -O0 включает порядка полусотни оптимизаций, из-за чего программа по-прежнему работает неверно.

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

Опять чушь. Для int никакого truncation не выполняется.
Ну, я понимаю, вы человек упёртый. Давайте, расскажите мне, в какой битности будут выполняться промежуточные вычисления is_max для long. Там тоже будет какой-то "расширенный тип для проомежуточных результатов", который нужно обрезать руками?

V>Но это всё-равно кривое решение — погоня за неуловимыми битами, детсад...

Нет никакой погони за "неуловимыми битами".
V>Чуть правильней будет не гоняться за неуловимым битом, а проверить его напрямую.
V>
V>template<typename signed_integral>
V>bool is_max(signed_integral value)
V>{
V>    using u_t = make_unsigned_t<signed_integral>;

V>    u_t mask = static_cast<u_t>(0x8000000000000000ul >> (8 - sizeof(value))*8);
V>    u_t u_v = static_cast<u_t>(value);

V>    return ((u_v + 1) & mask) != 0;
V>}
V>

Омг.
V>https://godbolt.org/z/b5f8zxMnv
V>Здесь для избегания UB все битовые операции ведутся в беззнаковых целых.
Ну так всё верно — в итоге вернулись к тому, с чего начинали. Все эти приседания вокруг сдвижки бит и прочая наркомания никакой пользы не несут. Только мешают компилятору работать — вы посмотрите, какой код генерирует выбранный вами кланг. Ключ к успеху именно в уходе от UB. Никаких "расширений" и "обрезаний". Поэтому ответ CreatorCray и является самым правильным.


V>Ради интереса замени тут:

Я с самого начала знал, что переход к беззнаковой арифметике спасёт отцов русской демократии.
А всё потому, что корень проблем не состоит ни в каких "неуловимых битах". В беззнаковых типах бит ровно столько же, сколько в знаковых.

V>Но это по-прежнему кривое решение, потому что достаточно сравнить число сразу с константой, без операции сложения:

V>
V>template<typename signed_integral>
V>bool is_max(signed_integral value)
V>{
V>    return value == 0x7FFFFFFFFFFFFFFFull >> (8 - sizeof(value))*8;
V>}
V>

V>https://godbolt.org/z/a7ajxzdsP
Нда. На всякого мудреца довольно простоты.
Заметим, для начала, что решение от СreatorCray совершенно случайно работает и для беззнаковых типов тоже. А ваше — нет. Ну, понятно, вы купились на имя типа "signed_integral". Увы — это всего лишь имя типа. В вашем шаблонном check параметр даже текстом не намекает пользователю, что беззнаковые типы вы обрабатывать не умеете .

Как я и писал в самом начале этого треда, С++ больнее всего бьёт именно по таким программистам, как вы — тем, кто думает, что "разобрался в тонкостях".

V>В целом обсуждение доставляет лулзы именно этим — эффектом неожиданности. ))

V>Даётся очень странное решение простой задачи, потом "следите за руками", потом "а вот тут фокус удался!" и прочая чепуха.
Ну, вы сумели переплюнуть всех в жанре "очень странное решение простой задачи".
V>Да не сталкиваемся мы с этой хернёй в работе, потому что не пишем так.
V>Не решаем так задачи.
Да я вижу, как вы решаете. Прекрасная антиреклама.
template<typename T> 
bool is_max(T value)
{
  return std::numeric_limits<T>::max() == value;
}

V>А если ты так пишешь, в духе исходного примера, то однажды будешь ловить прилёты и на любимом C#, когда он чуть подрастёт, бо гигиену надо соблюдать с детства, как грится! ))
Вот вроде бы забрезжил свет понимания — но нет, снова бред.
Процитирую единственную осмысленную вещь, которую вы сумели написать в процессе этой беседы:

Здесь для избегания UB все битовые операции ведутся в беззнаковых целых

То есть единственный надёжный способ борьбы с UB в С++ — избегать undefined behavior.
А как только мы убираем undefined behavior, внезапно самый оптимизирующий из C++ компиляторов начинает вести себя прилично, и никакой разницы между -O0 и -03 не происходит. Так что дело — вовсе не в мифических "взрослых оптимизациях", а только в UB.
Так вот, в дотнете UB возможен только в unsafe context. Всё. В безопасном контексте стандарт явно запрещает undefined behavior. Есть пара моментов, где стандарт допускает implementation-defined behavior, но это опять-таки детерминированное поведение. И ни компилятор, ни джит не имеют права "доопределять" его по вкусу таким способом, что на одной платформе оно противоречит само себе: https://godbolt.org/z/GxvThzs1a
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 19.04.2024 18:06 Sinclair . Предыдущая версия . Еще …
Отредактировано 19.04.2024 18:05 Sinclair . Предыдущая версия .
Re[25]: Carbon
От: vdimas Россия  
Дата: 19.04.24 17:52
Оценка: +1 :)
Здравствуйте, Sinclair, Вы писали:

V>>Знаковые целые нужны редко, в основном для оперирования сущностями, близкому по смыслу к "разница", "текущий остаток/баланс", или для традиционного возврата кодов или признаков ошибок.

S>Угу. Вся индустрия идёт не в ногу, только vdimas идёт в ногу.

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

Например, смещение от некоего адреса — это и есть "разница".
И в языке это явно поддерживается для адресной арифметики, т.к. адреса можно складывать с положительными и отрицательными числами.
float data[42 + 42];
float * cursor = data + 42;
float value = cursor[-42];

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

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

Problems with unsigned
Mixing signed and unsigned numbers is a common source of confusion and bugs.


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

Я ж показал рядом, как можно корректно расписать взаимодействие знаковых и беззнаковых чисел:
https://www.rsdn.org/forum/flame.comp/8734308.1

Тебе заранее на всё ответили. ))

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

Даже взять вот это:

Checking signed 0<=x && x<max (often evaluated as (0<=x) & (x<max) to avoid branching) is not slower than unsigned x<max on a modern computer.

Это неверно для некоторых архитектур, для которых активно используют именно С/С++, т.к. для некоторых архитектур простое битовое сложение флагов искалечит первоначальное условие, и получится 0<x && x<max, что есть ошибка. Плюс компилятору потребуется сгенерировать код запоминания регистра флагов. Поэтому, для некоторых популярных архитектур там будет честный бранчинг, т.к. на уровне исходников всё-равно положено писать 0<=x && x<max.


V>>У меня обсуждаемая проблема вечно вылазит в дотнете, бо системное АПИ расписано на знаковых, даже где это выглядит абсурдом.

S>Обсуждаемая проблема в дотнете вылезти не может, т.к. в нём знаковое переполнение не является UB.

Это до тех пор, пока нет приличного оптимизатора.
А когда появится, то будет что-то типа такого поведения под x64:
    public static void Main(string[] args)
    {
        static bool IsMax(int i) {
            return (i + 1u) < i;
        }
        
        Console.WriteLine(IsMax(0x7FFFFFFF));
    }

бгг...


V>>Причина известна — не во всех языках есть беззнаковый тип, поэтому платформа затачивалась на слабое звено. ))

S>Опять смелая догадка.

Это официальная причина от непосредственных разработчиков дотнета.


V>>Переполнения принято производить с беззнаковыми, а со знаковыми принято оперировать так, чтобы переполнения не возникало.

S>Не, просто 99% С++ программистов вообще не в курсе, что знаковое переполнение — это UB.

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

А теперь правильный ответ:
Знаковое переполнения в 99.9% случаев указывает на потерю результата, т.е. на ошибку программы.

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

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

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


S>Большинство пребывают примерно в таком же заблуждении, как и вы — что это Implementation-defined.


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

Ты бы хоть голову включил бы разок — стандарты C++ на сегодня принимаются наиболее тщательно и с самым широким обсуждением.
В обсуждении каждой мелочи (пусть чаще в режиме read-only, т.е. "контроль происходящего глазами") участвуют даже не десятки тысяч людей по всему миру, а сотни тыщ.
Любая непродуманная дичь вызвала бы бесконечную по объёму реакцию.

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

Зато сейчас, когда Core-версия разрабатывается прилюдно, с косяками в самом языке стало полегче.

Например, последний косяк C# был в уродской декомпозии типов для целей паттерн-матчинга.
Этот ужас был разработан еще для старого фреймворка за закрытыми дверями, ничего удивительного.
Сравни теперь с той элегантностью, которую разработали уже с привлечением сообщества для тех же целей уже в Core, ы?
Отредактировано 19.04.2024 18:04 vdimas . Предыдущая версия . Еще …
Отредактировано 19.04.2024 17:56 vdimas . Предыдущая версия .
Отредактировано 19.04.2024 17:53 vdimas . Предыдущая версия .
Re[26]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.04.24 17:56
Оценка:
Здравствуйте, CreatorCray, Вы писали:

S>>Хм. И в каком месте стандарта это разрешено?

CC>Ни в каком месте стандарта не описано как оно вообще должно работать.
Эмм, "как оно вообще должно работать" написано в стандарте очень и очень подробно.
Вычисления в компайл-тайме — это только один из видов оптимизаций, которые может делать компилятор.
И эти оптимизации не могут нарушать стандарт. Поэтому, если в стандарте написано "компилятор имеет право самопроизвольно увеличивать разрядность prvalue до int", то компилятор имеет право это делать, хоть в рантайме, хоть в компайл тайме. А если ему чего-то делать нельзя — например, увеличивать разрядность int prvalue, то он не может этого делать ни в рантайме, ни в компайл-тайме.
Оптимизатору запрещено изменять семантику программы. Единственное место, где ему можно резвиться на эту тему — это как раз UB.
И как раз потому, что семантика UB операций не определена никак. Поэтому программа, которая выводит "oops", с т.з. стандарта ровно настолько же корректна, как и та, которая выводит "wow".

CC>Потому и пишут эти "оптимизаторы" абстрактную символьную логику, совершенно не привязанную к backend.


Семантика абстрактной символьной логики жёстко задана. В частности, в LLVM есть специальные инструкции для моделирования UB, и специальные конструкции для того, чтобы описывать недетерминированные программы.
Добавлено всё это ровно для того, чтобы можно было получать говноэффекты вроде обсуждаемого в данном треде для С++ программ. Ни одному другому известному мне языку это даром не надо; и в LLVM, естественно, есть инструкции знаковой арифметики, поведение которых well-defined.

CC>Для генерируемого кода, но не для AST трансформаций.

Это одно и то же.

CC>Вот тут лежит огроменный болт, ибо frontend пишется вообще без оглядки на платформу.

Эмм, есть примерно два класса компиляторов. Один — это платформенно-специфичные, вроде того же intel classic. У них нет никакой "без оглядки на платформу", потому что платформа им хорошо известна.
И другой класс — это LLVM-фронты, вроде кланга, современного gcc, и современного icx. Они никаких "символьных вычислений" не делают, т.к. это не их проблема. Все оптимизации, которые они выполняют — платформенно-независимы.
А все вот эти символьные преобразования, которые вас так пугают, делаются внутри оптимизатора LLVM. И там никогда-никогда не бывает так, что мы считаем среднее двух интов и вдруг мы решили его вычислить через лонг, и в компайл-тайме переполнения не произошло. А в другом месте мы его вычисление отложили до рантайма, и получилось переполнение.

CC>>>То, что ты в данном примере называешь UB — на самом деле очень даже defined behavior. Неожиданный — да, но не undefined.

S>>Простите, какой именно эффект?
CC>Compile time вычислений.
Нет, этот эффект именно что undefined.

S>>Если бы знаковое переполнение было implementation-defined, то ни с какими флагами компилятор бы не выдавал oops ни на какой платформе.

CC>См выше про frontend
Это нерелевантные рассуждения. Фронтенд по факту выполняется на той же платформе, что и результат.
Посмотрите на беззнаковые целые — там ни при каких флагах никаких косяков не возникает. Почему-то фронтенд не ошибается при compile-time вычислениях беззнаковых. Чем, собственно, вы и воспользовались в вашем решении.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[26]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.04.24 18:18
Оценка: +1 -1 :)
Здравствуйте, vdimas, Вы писали:
V>Мы уже обсуждали это не раз, и ты как раз отвечаешь на суть того, с чем все (или почти все) согласились — "разница" должна быть знаковой.
Угу. Гляжу в книгу, вижу фигу.

V>Я ж показал рядом, как можно корректно расписать взаимодействие знаковых и беззнаковых чисел:

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

V>Тебе заранее на всё ответили. ))



V>Это до тех пор, пока нет приличного оптимизатора.

Нет, это напрямую запрещено стандартом.

V>А когда появится, то будет что-то типа такого поведения под x64:

V>
V>    public static void Main(string[] args)
V>    {
V>        static bool IsMax(int i) {
V>            return (i + 1u) < i;
V>        }
        
V>        Console.WriteLine(IsMax(0x7FFFFFFF));
V>    }
V>

V>бгг...
Никогда такого поведения не будет. И приведённое вами поведение чётко описано в стандарте. Поэтому вот этот код даст false хоть в первом дотнете, хоть в девятом, и вне зависимости от дебаг/релиз режима. И в любой следующей версии дотнета продолжит выдавать false.
А код, где к i прибавляется знаковая единица, продолжит выдавать true. Ваши бугагашечки — просто роспись в некомпетентности, увы.


V>Это официальная причина от непосредственных разработчиков дотнета.

И с кем из непосредственных разработчиков дотнета вам довелось пообщаться?

V>Извини, но выдать такое мог только человек с не очень большим опытом в написании программ.



V>Поэтому, важность детерминированного поведения в случае знакового переполнения примерно нулевая.

V>Да всем насрать, собсно.


V>Я даже не могу придумать реальной задачи, которую стоит решать таким образом, потому что в таких задачах всегда удобней беззнаковое, т.е. просто набор бит.



V>И ты тоже не придумаешь такой задачи, просто разводишь своё обычное пустопорожнее ля-ля-ля.

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

V>Любая непродуманная дичь вызвала бы бесконечную по объёму реакцию.

Ну так она и вызывает. Просто вы в своём тепличном мирке ничего этого не видите.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[25]: Carbon
От: vdimas Россия  
Дата: 19.04.24 18:19
Оценка: +1
Здравствуйте, Sinclair, Вы писали:

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

S>И вы, конечно же, сможете это подтвердить, дав ссылку на более-менее любой публичный C++ — проект.

Исходники Линухов подойдут?
https://github.com/search?q=repo%3Atorvalds%2Flinux+unsigned&amp;type=code&amp;p=1

А WinAPI подойдёт?
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
Re[27]: Carbon
От: vdimas Россия  
Дата: 19.04.24 18:23
Оценка:
Здравствуйте, Sinclair, Вы писали:

CC>>Вот тут лежит огроменный болт, ибо frontend пишется вообще без оглядки на платформу.

S>Эмм, есть примерно два класса компиляторов. Один — это платформенно-специфичные, вроде того же intel classic. У них нет никакой "без оглядки на платформу", потому что платформа им хорошо известна.
S>И другой класс — это LLVM-фронты, вроде кланга, современного gcc, и современного icx. Они никаких "символьных вычислений" не делают, т.к. это не их проблема. Все оптимизации, которые они выполняют — платформенно-независимы.
S>А все вот эти символьные преобразования, которые вас так пугают, делаются внутри оптимизатора LLVM. И там никогда-никогда не бывает так, что мы считаем среднее двух интов и вдруг мы решили его вычислить через лонг, и в компайл-тайме переполнения не произошло. А в другом месте мы его вычисление отложили до рантайма, и получилось переполнение.

Тут коллега не прав.
Аналогичных эффектов можно добиться и без того, чтобы оптимизатор подставил константу в is_max и выдал готовый ответ вместо исполнения тела ф-ии.
Я прошёлся по куче компиляторов — даже когда is_max не уничтожается (подавал не константу компиляции) — эффект был тот же на -O2, всё те же oops для первоначального варианта. ))
Re[27]: Carbon
От: vdimas Россия  
Дата: 19.04.24 19:09
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Мы уже обсуждали это не раз, и ты как раз отвечаешь на суть того, с чем все (или почти все) согласились — "разница" должна быть знаковой.

S>Угу. Гляжу в книгу, вижу фигу.

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

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

Почему конкретнос в span<> использутся знаковые индексы — потому что это смещения, для которых как раз знаковые и пригодны, о чём я сразу же и сказал, а ты побежал спорить от "недостаточной компетенции" (С) Синклер.


V>>Я ж показал рядом, как можно корректно расписать взаимодействие знаковых и беззнаковых чисел:

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

Библиотечный подход еще никто не отменял.


V>>Это до тех пор, пока нет приличного оптимизатора.

S>Нет, это напрямую запрещено стандартом.

Да ничего там не запрещено:

In an unchecked context, overflows are ignored and any high-order bits that do not fit in the destination type are discarded.


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

"destination type" для переменной в памяти или возвращаемого значения метода это одно, а для промежуточного значения при вычислении формулы унутре регистров может быть другим.

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

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


V>>А когда появится, то будет что-то типа такого поведения под x64:

V>>
V>>    public static void Main(string[] args)
V>>    {
V>>        static bool IsMax(int i) {
V>>            return (i + 1u) < i;
V>>        }
        
V>>        Console.WriteLine(IsMax(0x7FFFFFFF));
V>>    }
V>>

V>>бгг...
S>Никогда такого поведения не будет.

Это не тебе решать.


S>И приведённое вами поведение чётко описано в стандарте.


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


S>Поэтому вот этот код даст false хоть в первом дотнете, хоть в девятом, и вне зависимости от дебаг/релиз режима.


Хосподя, вот же ж от тебя чудеса тугости порой.
Это была просто демонстрация того, что может однажды начать происходить в таком коде при агрессивной оптимизации:
    public static void Main(string[] args)
    {
        static bool IsMax(int i) {
            return (i + 1) < i;
        }
        
        Console.WriteLine(IsMax(0x7FFFFFFF));
    }

Я воспользовался для демонстрации явно описанными правилами продвижения типов в арифметике языка C#, конечно, поэтому 1u.

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


S>И в любой следующей версии дотнета продолжит выдавать false.

S>А код, где к i прибавляется знаковая единица, продолжит выдавать true.

Это не тебе решать.

Ведь настоящее более-менее работающее без приседаний AOT впервые вышло лишь недавно — в 8-м дотнете.
С почином, как грится.

Лично я запасся попкорном и наблюдаю — будут его оптимизировать или оставят детской игрушкой, которой дотнет и был все эти 20+ лет?
Ставлю на то, что под давлением сообщества и постепенным отмиранием авторитета людей твоего пошиба, дотнет, таки, начнут по-людски оптимизировать.
А значит, гарантий для unchecked-арифметики не будет.

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

Стоило избавиться от сорняков, от зашедшей за границы приличия ангажированности — и сразу дело пошло.
Даже достаточно посмотреть на ускорения в 3-5 раз даже банальных преобразований ASCII<>UNICODE.
Кто мешал это сделать в течении предыдущих примерно 20-ти лет?
Некогда работать было?
Надо было вести блоги и просвещать "невежд"?

Посмотри на современное IO, на ref-структуры, ref-поля, readonly-методы (полные аналоги const-методов из С++), на in-аргументы методов (полные аналоги const T& args из плюсов), на where T is unmanaged, Span, ValueTask и т.д.

И это только за последние 3+ года, блин.
А что делали 20 лет до этого?

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


S>Ваши бугагашечки — просто роспись в некомпетентности, увы.


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

Ты еще буквально сообщение назад гоношился, хотя уже сидел в луже:
https://www.rsdn.org/forum/flame.comp/8734528.1

Парадокс Блаба он такой, сцуко, подлый где-то... ))


V>>Это официальная причина от непосредственных разработчиков дотнета.

S>И с кем из непосредственных разработчиков дотнета вам довелось пообщаться?

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

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


V>>Я даже не могу придумать реальной задачи, которую стоит решать таким образом, потому что в таких задачах всегда удобней беззнаковое, т.е. просто набор бит.

S>

Так будет задача или нет?


V>>И ты тоже не придумаешь такой задачи, просто разводишь своё обычное пустопорожнее ля-ля-ля.

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

И чем там поможет знаковое в сравнении с беззнаковым?
И чем там поможет дотнет в режиме unchecked?
Там тупая потеря результата, если не удаётся расширить промежуточное значение (я же тебе же об этом же и отвечал ).

И тем забавнее твой смайл здесь:

V>>А теперь правильный ответ:
V>>Знаковое переполнения в 99.9% случаев указывает на потерю результата, т.е. на ошибку программы.

V>>Поэтому, важность детерминированного поведения в случае знакового переполнения примерно нулевая.
V>>Да всем насрать, собсно.
S>

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

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

- Ты куда? В баню?
— Да нет, в баню!


Комедиант, блин.


V>>Любая непродуманная дичь вызвала бы бесконечную по объёму реакцию.

S>Ну так она и вызывает. Просто вы в своём тепличном мирке ничего этого не видите.

Дичь была от вас, "недостаточно компетентных" (С) Синклер.

Но при чём тут остальные, которые стараются избегать переполнений при вычислениях?
Отредактировано 19.04.2024 19:33 vdimas . Предыдущая версия . Еще …
Отредактировано 19.04.2024 19:31 vdimas . Предыдущая версия .
Отредактировано 19.04.2024 19:11 vdimas . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.