Почему компилятор C не может делать сравнения со знаком и без знака интуитивно понятным способом

Под «интуитивным» я подразумеваю

int a = -1;
unsigned int b = 3;

выражение (a < b) следует оценить до 1.

В Stackoverflow уже есть ряд вопросов, в которых спрашивается, почему в том или ином конкретном случае компилятор C жалуется на сравнение со знаком и без знака. Ответы сводятся к целочисленным правилам преобразования и так далее. Тем не менее, похоже, нет обоснование почему компилятор должен быть таким исключительно тупой при сравнении целых чисел без знака и без. Используя объявления выше, почему выражение как

(a < b)

не заменяется автоматически

(a < 0 || (unsigned int)a < b)

если нет единой машинной инструкции, чтобы сделать это правильно?

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

Я также не думаю, что могу купить идею, выраженную в комментариях ниже, что неявный преобразование целого числа без знака в сравнение ( (d - '0' < 10U) «идиома») дает программисту C дополнительные возможности по сравнению с явный бросать (((unsigned int)(d - '0') < 10U)). Но, конечно же, это открывает широкие возможности облажаться.

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

5

Решение

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

if (d-'0'<10U)  // false if d is not a digit

станет верным для пространства ASCII и многих других символов с вашей предложенной заменой.

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

Не нарушит ли это язык или существующий код, если мы добавим безопасные сравнения со знаком / без знака в C / C ++?

6

Другие решения

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

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

1

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

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

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

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

1

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

Проблема заключается в диапазоне целочисленных вариантов.

Хотя целое число со знаком может содержать значения от -2147483648 до 2147483648 (+ — один или два), целое число без знака может находиться в диапазоне от 0 до 4294967296.

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

Пример:

У вас есть номер -1 и номер 3 000 000 000. Какой из них больше? Понятно, что второе вы можете сказать … но для компьютера -1 на самом деле больше, потому что «как без знака» (что потребуется для правильной оценки большого), -1 представляется как максимальное число. (4294967296).

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

Вот почему компилятор выводит это предупреждение. Хотя фактическая ошибка встречается довольно редко, она все-таки МОЖЕТ произойти. И это именно то, о чем вас предупреждает компилятор … что при сравнении двух целых чисел с разными знаками может произойти нечто неожиданное.

0
По вопросам рекламы [email protected]