LINUX.ORG.RU

Красивые способы корректного сравнения знаковых и беззнаковых целых

 , ,


5

7

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

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

unsigned int a = 1;
int b = -1;
int res = (b < a);

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

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

Мой основной способ решения этой проблемы через расширение разрядности, так как я в первую очередь имею дело с unsigned char, но смесь size_t c ssize_t или что-то подобное также нередко доставляет неудобства.

Опишите кто и как выкручивается в сложившейся ситуации.

[UPDATE] ассемблерные листинги к классическим алгоритмам сравнения

For example x86 gcc 7.1 will for C++ source:

bool compare(int x, unsigned int y) {
    return (x < y); // "wrong" (will emit warning)
}

bool compare2(int x, unsigned int y) {
    return (x < 0 || static_cast<unsigned int>(x) < y);
}

bool compare3(int x, unsigned int y) {
    return static_cast<long long>(x) < static_cast<long long>(y);
}

Produce this assembly (godbolt live demo):

compare(int, unsigned int):
        cmp     edi, esi
        setb    al
        ret

compare2(int, unsigned int):
        mov     edx, edi
        shr     edx, 31
        cmp     edi, esi
        setb    al
        or      eax, edx
        ret

compare3(int, unsigned int):
        movsx   rdi, edi
        mov     esi, esi
        cmp     rdi, rsi
        setl    al
        ret

Взято вот здесь:

https://stackoverflow.com/a/44070807/73747

★★★★★

Последнее исправление: cvv (всего исправлений: 10)
Ответ на: комментарий от Iron_Bug

я уверена, какие именно там данные и почему это безопасно

Год назад одну утилитку, написанную под 32-битную систему запустили на 64-битной, а потом весь вечер вычищали косячки. Там первородный разработчик тоже был уверен, что его программку будут запускать только на i386.

cherry_boy
()
Ответ на: комментарий от i-rinat

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

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

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

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

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

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

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

так это пример школярского косяка. я такие косяки уже лет 20 назад не делала.

Ну что ж, счётчик тикал 20 лет, и вчера таки сбросился. Теперь пиши: «я такие косяки уже один день не делала».

i-rinat ★★★★★
()
Ответ на: комментарий от cherry_boy

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от i-rinat

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

Iron_Bug ★★★★★
()

У дамы очередной приступ ЧСВ. Случается регулярно раз в месяц, длится несколько дней. Подозреваю цикл.

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

Короче, используй знаковые типы.

filosofia
()
Последнее исправление: filosofia (всего исправлений: 1)

Ну так Си же с крестами – говно.

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

Qt — местечковая библиотека, вещь в себе. Они могут позволить себе полностью ломать API между версиями.

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

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

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

byko3y ★★★★
()

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

unsigned int a = 1;

int b = -1;

int res = (b < a);

Я не считаю себя мегаспецом по Си, но таки тут res = 1 должен быть. Что тут не практичного?

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

С точки зрения опа непрактично то, что там res = 0 получится. Но вообще непрактичны оба варианта, так как в любом случае будешь наступать на неявные грабли. Лучше бы программа не компилировалась вообще, чем вот это все.

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

Лучше бы программа не компилировалась вообще, чем вот это все.

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

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

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

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

Сам язык должен запрещать сравнение чисел разной знаковости.

В теории, да. Но на практике, в неидеальном мире, решение с -Wsign-compare – это наш лучший друг в этой ситуации.

Я-то могу, но только с -Werror и -Wsign-compare, а они в стандартную поставку не входят.

На каких платформах компиляторы не могут выдавать эти или подобные предупреждения?

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

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

Если бы люди использовали только те флаги компилятора, которые включены по умолчанию, то все программы компилировались бы с -O0 и назывались a.out.

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

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

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

Ох уж эти -Wextra

вот эта точно работает: -Wall-zub-daju-blya-budu

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

Лучше бы программа не компилировалась вообще, чем вот это все

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

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

Лучше объявить, что это UB, и оптимизировать так, что любое сравнение всегда возвращало 1(true). Или пойти еще дальше, возвращать то значение, что позволит оптимизировать остальной код, использующий это сравнение. Clang может такое учудить вполне на законных основаниях, написав в очередном changelog’е, что его implementaton ничем не отличается от undefined.

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

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

Уже сделали: https://imgur.com/a/qh4P8EC

Если переполнение в компайлтайме, то ошибка компиляции: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0e4487ce9cedc12b1c8d3fbb83da3bef

Если переполнение в рантайме, то паника: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0221066d48baae23f39a25cdb5e1b892

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

В расте так и сделали, см. комментарий выше. Дебажить куда проще, в релизе проверки можно отключить. А еще есть checked_add, возвращающий либо Some(result), либо None. Просто жаль, что такие простые вещи в сишку не завезут уже никогда. Жизнь была бы сильно проще.

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

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

неблагодарное то было дело, поэтому я уступил ее молодежи :-)

cvv ★★★★★
() автор топика

перед тем как ответить по теме хочу накидать говна тебе лопатой за вот это:

c, integer, сравнение

Стандарты языка С

For example x86 gcc 7.1 will for C++ source

Тред по C но примерчик на C++?
Переписать листинг на C у тебя заняло бы одну минуту зато не вызывал бы рвотный рефлекс.

Итак, к теме.

Сколько раз были взорваны пуканы (в том числе мой) когда приходилось иметь дело с значениями разных типов. А всё из-за того что люди изначально не хотели выбрать нормальные типы для данных которые будут там сохраняться.

Как решить проблему сравнения?

В зависимости от того что приходит в знаковых целых. Если там гарантированно >= 0, то тупо тайпкастишь. Довольно удобно получать знаковое целое от POSIX-функций - негативное значение свидетельствует об ошибке и ты в любом случае его проверяешь, а после проверки спокойно тайпкастишь.

Собсна, больше нормальных вариантов нет. По крайней мере так чтобы под каком-то недоконТРОЛЛере с ARM ядром юзая ARM тулчейн не случалось фигни. А она бывает на ровном месте.

----

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

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

Дык «царь» же хде-то бродит… Он как бы почти князь (может даже между богом и князем).

HIS
()
Ответ на: комментарий от i-rinat

Анализатор PVS Studio выдаёт здесь, во первых: V547 Expression ‘b < a’ is always false. Это и так понятно и не интересно. И собственно предупреждение: V605 Consider verifying the expression: b < a. An unsigned value is compared to the number -1. В общем, ловится.

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

Можно сказать так. Новичкам анализатор не нужен, так как им не до него. И без того полно непонимания, недостатка знания и т.д. Не нужен и профессионалам, так как они не делают ошибок (что неправда, ну да ладно).

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

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

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

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

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

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

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

Хорошо, что разработкой руководят менеджеры

Аргументации в данном случае надо поболее:

  • Разработку должны вести технари (тогда она продуктивна)
  • Руководить должны организаторы-манагеры (тогда она продуктивна)
  • Смешивать мягкое с тёплым не надо (получается говно).
anonymous
()
Ответ на: комментарий от anonymous

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

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

В общем, ловится.

Нет, в этой нитке речь была не о сравнении signed и unsigned.

i-rinat ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.