Коды ошибок и отрицательные числа
От: cppguard  
Дата: 30.01.22 04:00
Оценка: 3 (1) -2
Никак не могу сопоставить в голове кое-что про коды ошибок. Со времён юникса тянется наследие в виде отрицательных значений при возврате кода ошибки целочисленным числом. Очевидно, что писать повсюду "err != -1" не очень-то приятно. Поэтому возникла мысль, что результат сохраняли в unsigned и вместо этого писали "err < INT_MAX", что читается легче и понятнее. НО! С тех же самых бородатых времён тянется наследие в виде уникальных платформ, про которые никто не слышал, правда, где underflow или overflow для int НЕ выполняется циклически, и таким образом приведение int к unsingned для отрицательных чисел считается UB (слышал, что хотят отменить).

А ведь как было бы приятно жить в мире доминирования неотрицательных чисел? Ошибки тогда просто уходили бы в INT_MAX, а используемый диапазон сокращался бы не вполовину, а всего на несколько элементов. Реально целые отрицательные используются для отрицательных смещений, разности двух величин и ... всё
Re: Коды ошибок и отрицательные числа
От: Nuzhny Россия https://github.com/Nuzhny007
Дата: 30.01.22 06:20
Оценка: +1
Здравствуйте, cppguard, Вы писали:

C>А ведь как было бы приятно жить в мире доминирования неотрицательных чисел? Ошибки тогда просто уходили бы в INT_MAX, а используемый диапазон сокращался бы не вполовину, а всего на несколько элементов. Реально целые отрицательные используются для отрицательных смещений, разности двух величин и ... всё


Отрицательные пиксельные координаты — повсюду.
https://elibrary.ru/author_counter.aspx?id=875549
Re: Коды ошибок и отрицательные числа
От: 5.März  
Дата: 30.01.22 06:47
Оценка: 57 (7) +2
Здравствуйте, cppguard, Вы писали:

C>А ведь как было бы приятно жить в мире доминирования неотрицательных чисел? Ошибки тогда просто уходили бы в INT_MAX, а используемый диапазон сокращался бы не вполовину, а всего на несколько элементов. Реально целые отрицательные используются для отрицательных смещений, разности двух величин и ... всё


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

Signed и unsigned следуют разной арифметике: signed представляет обычные числа которые следуют обычной арифметике в которой x-1 < x всегда true (именно из-за этого signed underflow/overflow навсегда объявлено UB), unsigned представляет машинные слова которые следуют modulo-2^N арифметике, где x-1 может быть как меньше так и больше x (well-defined)

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

Поэтому C++ Core Guidelines, Google Style Guide и многие другие не рекомендуют использовать unsigned типы для чисел (только для флагов и битовых операций); Страуструп признал "беззнаковость" size_t исторической ошибкой; в новые стандарты введен ssize() возвращающий размеры в правильном типе.

C++ Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-nonnegative

Google Style Guide: https://google.github.io/styleguide/cppguide.html#Integer_Types

B.Stroustrup “Subscripts and sizes should be signed”: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1428r0.pdf

Длинное видео из 2013г где комитет C++ несколько раз признает ошибочность использования unsigned: https://channel9.msdn.com/Events/GoingNative/2013/Interactive-Panel-Ask-Us-Anything (12:10, 42:40, 1:02:50)
Re: Коды ошибок и отрицательные числа
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 30.01.22 07:04
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Никак не могу сопоставить в голове кое-что про коды ошибок. Со времён юникса тянется наследие в виде отрицательных значений при возврате кода ошибки целочисленным числом. Очевидно, что писать повсюду "err != -1" не очень-то приятно.


Зато писать "err<0" вполне приятно.

C>Поэтому возникла мысль, что результат сохраняли в unsigned и вместо этого писали "err < INT_MAX", что читается легче и понятнее. НО! С тех же самых бородатых времён тянется наследие в виде уникальных платформ, про которые никто не слышал, правда, где underflow или overflow для int НЕ выполняется циклически,


Причем тут уникальность каких платформ? Такое на любой платформе можно получить довольно легко


C>и таким образом приведение int к unsingned для отрицательных чисел считается UB (слышал, что хотят отменить).


Где ты такое вычитал?


C>А ведь как было бы приятно жить в мире доминирования неотрицательных чисел? Ошибки тогда просто уходили бы в INT_MAX, а используемый диапазон сокращался бы не вполовину, а всего на несколько элементов. Реально целые отрицательные используются для отрицательных смещений, разности двух величин и ... всё


Очень неудачное решение.
Можно возвращать не просто -1, а отрицательный код ошибки, вместо сохранения его в errno, например.
У меня есть самописные контейнеры, например, кольцевой буфер, там тип размера — параметр шаблона, и я могу использовать uint8_t/uint16_t для экономии памяти, а для инвалидного индекса есть npos.
Маньяк Робокряк колесит по городу
Re: Коды ошибок и отрицательные числа
От: Андрей Тарасевич Беларусь  
Дата: 30.01.22 07:10
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Никак не могу сопоставить в голове кое-что про коды ошибок. Со времён юникса тянется наследие в виде отрицательных значений при возврате кода ошибки целочисленным числом. Очевидно, что писать повсюду "err != -1" не очень-то приятно. Поэтому возникла мысль, что результат сохраняли в unsigned и вместо этого писали "err < INT_MAX", что читается легче и понятнее. НО! С тех же самых бородатых времён тянется наследие в виде уникальных платформ, про которые никто не слышал, правда, где underflow или overflow для int НЕ выполняется циклически, и таким образом приведение int к unsingned для отрицательных чисел считается UB (слышал, что хотят отменить).


Какая-то непонятная чепуха написана.

Во-первых, поведение целочисленных типов при приведении от signed к unsigned четко определено стандартом языка. Никаких неожиданностей здесь нет и никакого "наследия уникальных платформ" нет тоже. Если вам нравится сохранять результат в `unsigned` и проверять на `err < INT_MAX` — флаг вам в руки. Возможно тут есть какие-то подводные камни, но в общем и целом это будет гарантированно работать. Почему вы считаете, что это "лучше" проверки на отрицательность, я в упор не вижу, но если вам так больше нравится — вперед.

Во-вторых, возвращаясь к вопросу знакового переполнения ("для int"), о каких "уникальных платформах" вы ведете речь? Вы о чем вообще? Постарайтесь понять простую вещь: основным источником неопределенного поведения в С и С++ является не аппаратная платформа, а компилятор. Компилятор, компилятор и только компилятор. То есть те предположения о "невозможности" неопределенного поведения, которые он широко, охотно и активно использует в процессе оптимизации кода. Неопределенное поведение в С и С++ существует именно для этого: чтобы помочь компилятору в оптимизации кода. На всех С и С++ платформах, без единого исключения, знаковые типы НЕ заворачиваются при переполнении. Никакой "уникальности" или "бородатости" в этом нет. Что за чушь? Ни на одной современной С или С++ платформе `int` НЕ заворачивается. С/С++ платформ с заворачивающимся `int` не существует и не существовало нигде и никогда, кроме, возможно, вашего воображения. Ни "уникальных", ни "повсеместных", ни "бородатых", ни "бритых" — никаких.

В-третьих, почему вы вообще ведете речь о "underflow или overflow для int"? Приведения типов (между signed и unsigned) никогда не вызывали "переполнений" в С и С++. Как я сказал выше, приведение от signed к unsigned определено стандартом. Приведение от unsigned к signed всегда было implementation defined, но сейчас стандарты С и С++ четко определяют и его тоже. При целочисленном приведении нет никакого "underflow или overflow для int" даже отдаленно. "Overflow для int" существует только в процессе выполнения арифметических операций над int, но не в процессе выполнения приведений. Так что к чему вы вообще приплели тут "underflow или overflow для int" — в упор не ясно.
Best regards,
Андрей Тарасевич
http://files.rsdn.org/2174/val.gif
Отредактировано 30.01.2022 7:17 Андрей Тарасевич . Предыдущая версия . Еще …
Отредактировано 30.01.2022 7:16 Андрей Тарасевич . Предыдущая версия .
Отредактировано 30.01.2022 7:14 Андрей Тарасевич . Предыдущая версия .
Re: Коды ошибок и отрицательные числа
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 30.01.22 07:22
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Никак не могу сопоставить в голове кое-что про коды ошибок. Со времён юникса тянется наследие в виде отрицательных значений при возврате кода ошибки целочисленным числом. Очевидно, что писать повсюду "err != -1" не очень-то приятно. Поэтому возникла мысль, что результат сохраняли в unsigned и вместо этого писали "err < INT_MAX", что читается легче и понятнее.


Нет, не писали. Нет, не понятнее.
И это только в ядерном API возврат -1 для всех ошибок. Есть такие, в которых -1, -2, ...
Поэтому "< 0" универсальнее.

C> НО! С тех же самых бородатых времён тянется наследие в виде уникальных платформ, про которые никто не слышал, правда, где underflow или overflow для int НЕ выполняется циклически, и таким образом приведение int к unsingned для отрицательных чисел считается UB (слышал, что хотят отменить).


C++20 — уже отменили. C — на очереди.
Разница между ==1 и <0 при единственном -1 тут не появляется. А вот подозрительные конверсии — не в тему.

C>А ведь как было бы приятно жить в мире доминирования неотрицательных чисел? Ошибки тогда просто уходили бы в INT_MAX, а используемый диапазон сокращался бы не вполовину, а всего на несколько элементов. Реально целые отрицательные используются для отрицательных смещений, разности двух величин и ... всё


И это будет решение частного случая, а не общего подхода, причём предельно кривым методом.
Вы не хотите взглянуть чуть-чуть шире. На самом деле у этих функций не какие-то -1 или прочее, это уже результат укладки в доступные (для дорогого хардвера 1970-х) возможности. Все эти функции, по сути, возвращают то, что в C++ называется variant, в Rust — enum:

enum errno { коды; };
struct normal_rvalue { int v; };
using syscall_retval = std::variant<normal_rvalue, errno>;


enum syscall_retval {
  normal(int),
  error(errno),
}


Это и отражается соответственно в ядерном ABI. Например, FreeBSD на x86 использует такое: если при выходе из сисколла CF=0, то в EAX/RAX нормальное значение, оно и передаётся. Если CF=1, то RAX пишется в errno текущей нити и заменяется на -1.

Ряд более новых функций, типа семейства pthread_*, возвращают 0 нормально и код errno в случае ошибки, там такого костыля, как выше, нет.

А вот если бы такой variant был в явном виде с самого начала — вы бы это и проверяли по типу (псевдокод):

match open("/dev/null", O_RDONLY) {
  case normal_rvalue(int newfd): ...
  case error(int err): ...


c> А ведь как было бы приятно жить в мире доминирования неотрицательных чисел?


Рядом хорошо расписали, чем это плохо.
Re[2]: Коды ошибок и отрицательные числа
От: Андрей Тарасевич Беларусь  
Дата: 30.01.22 07:44
Оценка:
Здравствуйте, netch80, Вы писали:

N>C++20 — уже отменили. C — на очереди.


UB при signed overflow никто в С++20 не отменял. То, что signed overflow на самом деле не имеет никакого отношения к теме, поднятой автора вопроса — дело другое, но тем не менее....
Best regards,
Андрей Тарасевич
http://files.rsdn.org/2174/val.gif
Re[3]: Коды ошибок и отрицательные числа
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 30.01.22 07:53
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

АТ>Здравствуйте, netch80, Вы писали:


N>>C++20 — уже отменили. C — на очереди.


АТ>UB при signed overflow никто в С++20 не отменял. То, что signed overflow на самом деле не имеет никакого отношения к теме, поднятой автора вопроса — дело другое, но тем не менее....


Спасибо за уточнение. Я "отменили" написал к словам комментатора

C>> НО! С тех же самых бородатых времён тянется наследие в виде уникальных платформ, про которые никто не слышал, правда, где underflow или overflow для int НЕ выполняется циклически


но получилось недостаточно явно, к чему это было.
Re[2]: Коды ошибок и отрицательные числа
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 30.01.22 08:42
Оценка: 1 (1)
Здравствуйте, 5.März, Вы писали:

5M>Выбор int как типа по умолчанию не было ошибкой, все что семантически представляет собой целое число (неважно, может быть отрицательным или нет) должно быть по умолчанию знаковым типом. Реальная разница signed и unsigned не в отрицательности, их просто назвали неудачными именами. Из-за неудачного названия программисты теперь суют unsigned во все, что не может быть отрицательным.


5M>Signed и unsigned следуют разной арифметике: signed представляет обычные числа которые следуют обычной арифметике в которой x-1 < x всегда true (именно из-за этого signed underflow/overflow навсегда объявлено UB), unsigned представляет машинные слова которые следуют modulo-2^N арифметике, где x-1 может быть как меньше так и больше x (well-defined)


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

Первые компиляторы C выполняли операции строго 1:1 и ничего почти не оптимизировали. Всякие сокращения вычисления констант, конечно, уже были, но современного уровня серьёзных переделок не было. Операции выполнялись так, как сейчас получается в GCC/Clang с -fwrapv: переполнение игнорировалось. Но: из-за одновременного наличия машин как минимум с дополнительным кодом (>99%) и обратным (отдельные модели), стандарт не смог записать это (скорее, к сожалению) для знаковых операций. Причём, такого жёсткого разделения между undefined, unspecified и implementation-defined behavior, как сейчас, тогда не было: термины вводились, но достаточно наобум. И высокой опасности эффектов от UdB не было, операциями с ними пользовались напропалую.

А уже в 1990-х, когда начали вводить высокий уровень оптимизаций, фактически как злые хакеры, "переиспользовали" лазейку в стандарте с UdB для signed для оптимизаций. Но это было на ~20 лет позже.

Всё описанное известно по документам и воспоминаниям. Ссылок в явном виде я не собирал, но если попадутся и вспомню — закину сюда. Если горит, можно, например, попросить khim@habr (с ходу нашлось такое очень близкое, но он ещё высказывался), или John Regehr, у него в блоге есть современные последствия, но и предпосылки наверняка исследовал.

Для современного состояния, конечно, ваши слова актуальны. К сожалению. Я бы таки предпочёл, чтобы вначале зафиксировались на усечении для всех случаев: это бы ускорило введение нормальных средств, в которых реакция на нарушение области значения (обычно называемое переполнением) задаётся конкретным символом операции или контекстом (лучше всего в среднем). Это бы ускорило тут прогресс лет на 20-25.

Для сравнения: в Ada разделены диапазонные целочисленные типы (вместе с базовыми стандартными знаковыми и беззнаковыми) и модульные. У диапазонных по умолчанию проверяется результат при присвоении (и при многих арифметических операциях), генерируется исключение при выходе за диапазон; это заметно дороже, но надёжно. Для модульного типа (в стиле type byte = modulo 2**8) все операции по модулю, и там по типу видно, что будет усечение. Для идеального языка я ожидаю что-то среднее: по умолчанию проверка с исключениями, но при явном указании программистом — правила упрощаются вплоть до того, как сейчас в C со знаковыми — считается, что программист всё предусмотрел и переполнения (и прочих нарушений) не будет.

5M>В 99.99% случаев когда ты размышляешь об операциях с числами, ты и читатели твоего кода подразумевают обычную, а не модульную арифметику, так что и тип нужно использовать предназначенный для чисел, а не для машинных слов.


5M>Поэтому C++ Core Guidelines, Google Style Guide и многие другие не рекомендуют использовать unsigned типы для чисел (только для флагов и битовых операций);


Они именно так мотивируют? можно цитату? (с ходу не нашёл)
А то я бы предположил скорее сложность задания реакции на переход 0 -> -1 (как в примерах из этого
Автор: netch80
Дата: 10.05.20
обсуждения). Тем более что вот прямая цитата:

ES.102: Use signed types for arithmetic
Reason

Because most arithmetic is assumed to be signed; x — y yields a negative number when y > x except in the rare cases where you really want modulo arithmetic.


По-моему, вам пора прекратить мучить сову.

5M> Страуструп признал "беззнаковость" size_t исторической ошибкой; в новые стандарты введен ssize() возвращающий размеры в правильном типе.


"Исторической ошибкой" size() является, конечно, в некоторой степени, но это факт не unsigned самого по себе — потому что размер не может быть отрицательным, а, значит, домен правильный. Страуструп как раз объясняет причины, а вы как-то очень странно читаете:

1. Там, где размер участвует в арифметике — почти всегда — естественно могут появляться отрицательные значения. Да, он это говорит.

Но: проблемные последствия этого являются следствием устоявшейся кривости стандарта, который не предусматривает иного действия для беззнаковых, кроме как по модулю. Если бы s-10, где s как size() какого-то контейнера равно 4, вызывало бы исключение — проблемы бы не было (или было бы сильно меньше). Считаете невозможным такой результат? — ok, не конвертировать к знаковым, пусть выбивает. Возможно, отрабатывать? — явно конвертировать.

2. Нужно явно конвертировать к знаковым для таких операций, из-за п.1. Но сейчас в C и C++ по правилам, если разная знаковость у операндов, они оба приводятся к беззнаковому.

Жёсткий надёжный язык тут бы скорее отказался компилировать — приведите к одной знаковости или ступайте себе лесом. Чуть более расслабленный пересчитал бы множество значений результата и проинтерпретировал по нему — и это тоже имеет смысл. Но автоконверсия C/C++ это таки точно наследие "бородатых годов".

3. (Страуструп не писал вроде, но я добавлю: ) Из-за (естественной) знаковости ptrdiff_t никакой контейнер байтов не может занимать более половины памяти (а с учётом кодирования в дополнительном коде — и ровно половина тоже недопустима).
Поэтому или разделять разные случаи (для байтовых одни правила, для прочих другие), или можно постановить, что size() любого контейнера не может превышать SSIZE_MAX.
Может, где-то есть платформа, где это даст неожиданные проблемы (например, 16-битный мир с одним массивом в адресах от 0x1000 до 0xEFFF), но их возможные проблемы проигнорировали, тем более что там и так непонятно, что делать — ptrdiff_t должен для корректной работы быть минимум 17-битным... на такой платформе и про STL сложно говорить всерьёз.

5M>C++ Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-nonnegative

5M>Google Style Guide: https://google.github.io/styleguide/cppguide.html#Integer_Types
5M>B.Stroustrup “Subscripts and sizes should be signed”: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1428r0.pdf
5M>Длинное видео из 2013г где комитет C++ несколько раз признает ошибочность использования unsigned: https://channel9.msdn.com/Events/GoingNative/2013/Interactive-Panel-Ask-Us-Anything (12:10, 42:40, 1:02:50)

Последняя ссылка битая, видео, наверно, удалено.
По поводу остальных ссылок, удивительно, как вы при наличии прямых указаний авторов умудряетесь переинтерпретировать вплоть до полной противоположности.
Отредактировано 30.01.2022 8:53 netch80 . Предыдущая версия . Еще …
Отредактировано 30.01.2022 8:53 netch80 . Предыдущая версия .
Re: Коды ошибок и отрицательные числа
От: kov_serg Россия  
Дата: 30.01.22 08:42
Оценка: +1
Здравствуйте, cppguard, Вы писали:

C>Никак не могу сопоставить в голове кое-что про коды ошибок. Со времён юникса тянется наследие в виде отрицательных значений при возврате кода ошибки целочисленным числом. Очевидно, что писать повсюду "err != -1" не очень-то приятно. Поэтому возникла мысль, что результат сохраняли в unsigned и вместо этого писали "err < INT_MAX", что читается легче и понятнее. НО! С тех же самых бородатых времён тянется наследие в виде уникальных платформ, про которые никто не слышал, правда, где underflow или overflow для int НЕ выполняется циклически, и таким образом приведение int к unsingned для отрицательных чисел считается UB (слышал, что хотят отменить).


C>А ведь как было бы приятно жить в мире доминирования неотрицательных чисел? Ошибки тогда просто уходили бы в INT_MAX, а используемый диапазон сокращался бы не вполовину, а всего на несколько элементов. Реально целые отрицательные используются для отрицательных смещений, разности двух величин и ... всё


Всё очень просто 0-нет ошибки, не 0 — есть ошибка и всё.
Re[2]: Коды ошибок и отрицательные числа
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 30.01.22 09:38
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

AT>На всех С и С++ платформах, без единого исключения, знаковые типы НЕ заворачиваются при переполнении. Никакой "уникальности" или "бородатости" в этом нет. Что за чушь? Ни на одной современной С или С++ платформе `int` НЕ заворачивается. С/С++ платформ с заворачивающимся `int` не существует и не существовало нигде и никогда, кроме, возможно, вашего воображения. Ни "уникальных", ни "повсеместных", ни "бородатых", ни "бритых" — никаких.


`gcc -fwrapv`, `clang -fwrapv` это столь же законные и легкодоступные платформы, как и просто `gcc` или `clang`, почти повсеместные, и они именно "с заворачивающимся `int`". Зачем вы говорите про их несуществование, да ещё и с таким апломбом, мне непонятно.
И оно реально используется: CPython2 компилируется именно с -fwrapv (при том, что с -O3). (В 3-м это устранили — видно, вычистили проблемные места.)

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

AT> Приведение от unsigned к signed всегда было implementation defined, но сейчас стандарты С и С++ четко определяют и его тоже.


И где такое сказано для C?

C17 (N2176) и последний на сейчас драфт (N2731), 6.3.1.3(3):

Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.


AT>При целочисленном приведении нет никакого "underflow или overflow для int" даже отдаленно.


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

То, что большинство компиляторов реализуют его как bit cast, это таки I-D. Сами ж хотели все платформы, а не только основные.

АТ>Какая-то непонятная чепуха написана.


Re: Коды ошибок и отрицательные числа
От: AeroSun  
Дата: 30.01.22 09:41
Оценка: -1
Подведу итог:
1) в стартовом посте написан сумбур
2) никто ничего не сохранял в unsigned
3) всегда сравнение было с нулём, причем 0 — это всё ок, -1 — это невалидное значение, 1+ — это коды ошибок
4) уже давно вместо этого используют typed enum — чего и вам советую
Re[2]: Коды ошибок и отрицательные числа
От: cppguard  
Дата: 30.01.22 09:47
Оценка:
Здравствуйте, Nuzhny, Вы писали:

N>Отрицательные пиксельные координаты — повсюду.


Экранные? Это где, например? Графику мы не берём, там сразу floating-point.
Re: Коды ошибок и отрицательные числа
От: T4r4sB Россия  
Дата: 30.01.22 09:51
Оценка:
Здравствуйте, cppguard, Вы писали:

C>А ведь как было бы приятно жить в мире доминирования неотрицательных чисел?


Я уже несколько лет адово бомблю про идиотим с беззнаковым размером и индексом у векторов.
Заведи же блин именованную константу для этой своей -1.
Re[2]: Коды ошибок и отрицательные числа
От: T4r4sB Россия  
Дата: 30.01.22 09:53
Оценка:
Здравствуйте, 5.März, Вы писали:

5M>Signed и unsigned следуют разной арифметике: signed представляет обычные числа которые следуют обычной арифметике в которой x-1 < x всегда true (именно из-за этого signed underflow/overflow навсегда объявлено UB)


И это большая проблема С/С++ — нет стандартного способа проверить, что у тебя всё ок с арифметикой, потому что компилятор тупо вырезает проверки вида assert(a+b>a). Если бы в стандарте был описан отладочный режим, при котором переполнение знаковых это остановка программы и вызов отладчика / завершение программы, то было бы всё не так плохо, но такой режим есть только в виде личной инициативы отдельных компиляторов.
Re[2]: Коды ошибок и отрицательные числа
От: cppguard  
Дата: 30.01.22 09:53
Оценка: +1
Здравствуйте, Андрей Тарасевич, Вы писали:

АТ>Во-первых, поведение целочисленных типов при приведении от signed к unsigned четко определено стандартом языка. Никаких неожиданностей здесь нет и никакого "наследия уникальных платформ" нет тоже. Если вам нравится сохранять результат в `unsigned` и проверять на `err < INT_MAX` — флаг вам в руки. Возможно тут есть какие-то подводные камни, но в общем и целом это будет гарантированно работать. Почему вы считаете, что это "лучше" проверки на отрицательность, я в упор не вижу, но если вам так больше нравится — вперед.


Потому что такой код проще формально доказывать. А конструкция "err ! =-1" подразумевает, что err может быть -2, например, хотя описание функции чётко говорит "положительное или -1". Это один момент. Второй момент — когда читаешь такой код, то сразу начинаешь думать "так, у нас может быть -1, а -2 может быть? А что это вообще за функция? Это POSIX? Возвращает какой-то handle типа файла или сокета или PID или что-то другое?". Кода возврата программ, кстати, почему-то неотрицательные.

АТ>Во-вторых, возвращаясь к вопросу знакового переполнения ("для int"), о каких "уникальных платформах" вы ведете речь? Вы о чем вообще? Постарайтесь понять простую вещь: основным источником неопределенного поведения в С и С++ является не аппаратная платформа, а компилятор. Компилятор, компилятор и только компилятор. То есть те предположения о "невозможности" неопределенного поведения, которые он широко, охотно и активно использует в процессе оптимизации кода. Неопределенное поведение в С и С++ существует именно для этого: чтобы помочь компилятору в оптимизации кода. На всех С и С++ платформах, без единого исключения, знаковые типы НЕ заворачиваются при переполнении. Никакой "уникальности" или "бородатости" в этом нет. Что за чушь? Ни на одной современной С или С++ платформе `int` НЕ заворачивается. С/С++ платформ с заворачивающимся `int` не существует и не существовало нигде и никогда, кроме, возможно, вашего воображения. Ни "уникальных", ни "повсеместных", ни "бородатых", ни "бритых" — никаких.


АТ>В-третьих, почему вы вообще ведете речь о "underflow или overflow для int"? Приведения типов (между signed и unsigned) никогда не вызывали "переполнений" в С и С++. Как я сказал выше, приведение от signed к unsigned определено стандартом. Приведение от unsigned к signed всегда было implementation defined, но сейчас стандарты С и С++ четко определяют и его тоже. При целочисленном приведении нет никакого "underflow или overflow для int" даже отдаленно. "Overflow для int" существует только в процессе выполнения арифметических операций над int, но не в процессе выполнения приведений. Так что к чему вы вообще приплели тут "underflow или overflow для int" — в упор не ясно.


Да, тут я дал маху и перепутал приведение типов и переполнение при арифметических операциях. В ОП, конечно же, речь шла о приведении типов.
Re[2]: Коды ошибок и отрицательные числа
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 30.01.22 09:58
Оценка:
Здравствуйте, AeroSun, Вы писали:

AS>Подведу итог:


Много берёте на себя, и в принципиальных моментах неверно.

AS>3) всегда сравнение было с нулём, причем 0 — это всё ок, -1 — это невалидное значение, 1+ — это коды ошибок


НЕТ.

Если говорить в контексте типового Unix API, то есть минимум 4 разных случая:

1. Нет ошибки — 0, ошибка — -1 и в errno сохранён код ошибки (>0).
Выполняется для функций, у которых только признак успех/неудача (как close(), ioctl(), setuid()...), или результат идёт другим каналом (указатель в аргументах, например: pipe() — массив на 2 дескриптора).

2. Нет ошибки — >=0, ошибка — -1 и в errno сохранён код ошибки (>0). Это open(), read(), write(), fcntl() и десятки других.

3. Нет ошибки — 0, ошибка — код ошибки больше 0, обычно из errno. Результат всегда идёт другим каналом (по указателю в аргументах). Это вся группа pthread_*, это getaddrinfo(), getnameinfo() и некоторые другие.

(Кстати, если ошибки нет, варианты 1-3 могут менять errno, а могут и не менять.)

4. Индикации ошибки в возвращаемом значении нет вообще (или слишком много вариантов). Для проверки на ошибку надо смотреть на errno (предварительно обнулив его) и на другие признаки.
Пример: strtol(): если значение вышло за пределы — она пишет ERANGE в errno. Если после числа в строке мусор — надо смотреть на *endptr != 0. Если на входе пустая строка — ответ 0, errno == 0, но факт пустой строки определяется по endptr == beginptr.

А вот варианта, что вы описали, именно в этой комбинации, просто не существует.

AS>1) в стартовом посте написан сумбур


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

AS>2) никто ничего не сохранял в unsigned


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

AS>4) уже давно вместо этого используют typed enum — чего и вам советую


Перевести на него существующее API пока как-то невозможно.
Отредактировано 30.01.2022 10:06 netch80 . Предыдущая версия .
Re[3]: Коды ошибок и отрицательные числа
От: T4r4sB Россия  
Дата: 30.01.22 09:58
Оценка: :)
Здравствуйте, netch80, Вы писали:

N>1. Там, где размер участвует в арифметике — почти всегда — естественно могут появляться отрицательные значения. Да, он это говорит.


N>Но: проблемные последствия этого являются следствием устоявшейся кривости стандарта, который не предусматривает иного действия для беззнаковых, кроме как по модулю. Если бы s-10, где s как size() какого-то контейнера равно 4, вызывало бы исключение — проблемы бы не было (или было бы сильно меньше). Считаете невозможным такой результат? — ok, не конвертировать к знаковым, пусть выбивает. Возможно, отрабатывать? — явно конвертировать.


Я пишу на Расте и проблема никуда не делась.
Обычный заголовок

for i in 0 .. v.len()-1

Является валидным полностью рабочим кодом для знакового индекса. Но в Расте этот заголовок падает на пустом векторе. В С++ я бы получил ассерт при обращении по инвалидному индексу.

Внимание вопрос: а какая мне нахрен разница, что программа упала не с дурацким старомодным отсталым ассертом, а с красивой модной прогрессивной молодёжной паникой?

Так что нет, индексы и размеры должны быть знаковыми и только знаковыми.
Re[2]: Коды ошибок и отрицательные числа
От: cppguard  
Дата: 30.01.22 09:59
Оценка:
Здравствуйте, AeroSun, Вы писали:


AS>Подведу итог:

AS>1) в стартовом посте написан сумбур
Лишь толко часть, где я спутал переполнение и приведение.

AS>2) никто ничего не сохранял в unsigned

Да, об этом-то и речь. Почему?

AS>3) всегда сравнение было с нулём, причем 0 — это всё ок, -1 — это невалидное значение, 1+ — это коды ошибок

Если код, например, для автопилота, то нужно будет обработать -2, даже если оно там не планируется. Или доказать, что -2 не появится ни при каких условиях, что сделать труднее. Symbolic execution становится сложнее.

AS>4) уже давно вместо этого используют typed enum — чего и вам советую

Я вообще не пишу на С++
Re[3]: Коды ошибок и отрицательные числа
От: T4r4sB Россия  
Дата: 30.01.22 10:07
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Экранные? Это где, например? Графику мы не берём, там сразу floating-point.


Да хотя бы положение гуи-элемента на экране.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.