"strlen (s1) - strlen (s2)" никогда не меньше нуля

В настоящее время я пишу программу на C, которая требует частого сравнения длин строк, поэтому я написал следующую вспомогательную функцию:

int strlonger(char *s1, char *s2) {
    return strlen(s1) - strlen(s2) > 0;
}

Я заметил, что функция возвращает true, даже если s1 имеет более короткую длину, чем s2. Может ли кто-нибудь объяснить это странное поведение?

Ответ 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).

  • Используйте при выполнении модульной арифметики.

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

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

Ответ 2

strlen возвращает a size_t, который является typedef для типа unsigned.

Итак,

(unsigned) 4 - (unsigned) 7 == (unsigned) - 3

Все значения unsigned больше или равны 0. Попробуйте преобразовать переменные, возвращаемые с помощью strlen в long int.

Ответ 3

Алекс Локвуд answer - лучшее решение (компактная, понятная семантика и т.д.).

Иногда имеет смысл явно преобразовать в подписанную форму size_t: ptrdiff_t, например.

return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;

Если вы сделаете это, вам нужно быть уверенным, что значение size_t соответствует значению ptrdiff_t (в котором содержится меньше бит мантиссы).