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


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

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

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

3 76

3 ответа:

вы столкнулись с некоторым специфическим поведением, которое возникает в C при обработке выражений, содержащих как знаковые, так и беззнаковые величины.

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

что касается вашей вспомогательной функции, обратите внимание, что с 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-разрядной машиной, используя представление дополнения two.

важная концепция для понимания при работе с unsigned / signed переменные в C - это если в одном выражении есть сочетание беззнаковых и подписанных величин, подписанные значения неявно приводятся к unsigned.

Пример 1:

рассмотрим следующее выражение:

-1 < 0U

поскольку второй операнд не имеет знака, первый -неявное приведение без знака, и, следовательно, выражение эквивалентно сравнению,

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;
}

данная функция предназначена для демонстрации того, как легко ошибки могут возникнуть из-за неявное приведение из signed в unsigned. Кажется вполне естественным передать параметр length как без знака; в конце концов, кто когда-либо хотел бы использовать отрицательная длина? Критерий остановки i <= length-1 также, кажется, довольно понятный. Однако, при запуске с аргументом length равна 0 сочетание этих двух дает неожиданный результат.

С параметром length без знака, вычисление 0-1 выполняется с использованием арифметики без знака, что эквивалентно модульному сложению. В результате получается тогда UMax. Элемент <= сравнение также выполняется с использованием сравнения без знака, и так как любое число меньше или равно UMax сравнение всегда держит. Таким образом, код будет пытаться получить доступ к недопустимым элементам массива a.

код может быть установлена как путем объявления length быть int, или путем изменения тест for петля будет i < length.

Вывод: Когда Следует Использовать Unsigned?

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

  • НЕ используйте только потому, что число неотрицательно. Легко ошибиться, и эти ошибки иногда невероятно тонкие (как показано в Примере № 2).

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

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

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

strlen возвращает a size_t что это typedef на unsigned тип.

и

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

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

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

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

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

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