Ответ 1
То, что вы встретили, - это своеобразное поведение, возникающее в C при обработке выражений, содержащих как подписанные, так и неподписанные величины.
Когда выполняется операция, когда один операнд подписан, а другой - без знака, C будет неявно преобразовывать подписанный аргумент в unsigned и выполнять операции, предполагая, что числа неотрицательны. Это соглашение часто приводит к неинтуитивному поведению для реляционных операторов, таких как <
и >
.
Что касается вашей вспомогательной функции, обратите внимание, что, поскольку strlen
возвращает тип size_t
(неподписанная величина), разность и сравнение вычисляются с использованием беззнаковой арифметики. Если s1
короче s2
, разница strlen(s1) - strlen(s2)
должна быть отрицательной, но вместо этого становится большим, без знака числом, которое больше, чем 0
. Таким образом,
return strlen(s1) - strlen(s2) > 0;
возвращает 1
, даже если s1
короче s2
. Чтобы исправить вашу функцию, используйте этот код:
return strlen(s1) > strlen(s2);
Добро пожаловать в чудесный мир C!:)
Дополнительные примеры
Поскольку этот вопрос недавно получил большое внимание, я хотел бы привести несколько (простых) примеров, чтобы убедиться, что я получаю эту идею. Я предполагаю, что мы работаем с 32-разрядной машиной, используя два представления дополнения.
Важное понятие, которое следует понимать при работе с неподписанными/подписанными переменными в C, состоит в том, что если в одном выражении имеется смешанное количество неподписанных и подписанных величин, подписанные значения неявно передаются в unsigned.
Пример # 1:
Рассмотрим следующее выражение:
-1 < 0U
Так как второй операнд без знака, первый из них неявно приводится к unsigned, и, следовательно, выражение эквивалентно сравнению,
4294967295U < 0U
который, разумеется, неверен. Вероятно, это не то поведение, которое вы ожидали.
Пример # 2:
Рассмотрим следующий код, который пытается суммировать элементы массива a
, где число элементов задается параметром length
:
int sum_array_elements(int a[], unsigned length) {
int i;
int result = 0;
for (i = 0; i <= length-1; i++)
result += a[i];
return result;
}
Эта функция предназначена для демонстрации того, как легко могут возникать ошибки из-за неявного кастинга из подписанного без знака. Кажется вполне естественным передать параметр length
как unsigned; в конце концов, кто захочет использовать отрицательную длину? Критерий остановки i <= length-1
также кажется довольно интуитивным. Однако при запуске с аргументом length
, равным 0
, комбинация этих двух дает неожиданный результат.
Так как параметр length
не имеет знака, вычисление 0-1
выполняется с использованием арифметики без знака, что эквивалентно модулярному добавлению. Результатом является UMax. Сравнение <=
также выполняется с использованием сравнения без знака, и поскольку любое число меньше или равно UMax, сравнение всегда выполняется. Таким образом, код попытается получить доступ к недопустимым элементам массива a
.
Код может быть исправлен либо объявлением length
как int
, либо путем изменения теста цикла for
на i < length
.
Вывод: когда следует использовать неподписанные?
Я не хочу утверждать что-либо слишком спорным здесь, но вот некоторые из правил, которые я часто прилипают, когда я пишу программы в C.
-
НЕ используйте только потому, что число неотрицательно. Легко совершать ошибки, и эти ошибки иногда невероятно тонкие (как показано в примере # 2).
-
Используйте при выполнении модульной арифметики.
-
Используйте, используя биты для представления множеств. Это часто удобно, потому что позволяет выполнять логические сдвиги вправо без расширения знака.
Конечно, могут быть ситуации, в которых вы решите пойти против этих "правил". Но чаще всего, следуя этим рекомендациям, ваш код будет работать легче и менее подвержен ошибкам.