"strlen (s1) - strlen(s2)" никогда не меньше нуля
В настоящее время я пишу программу на C, которая требует частого сравнения длины строк, поэтому я написал следующую вспомогательную функцию:
int strlonger(char *s1, char *s2) {
return strlen(s1) - strlen(s2) > 0;
}
Я заметил, что функция возвращает true, даже когда s1
имеет меньшую длину, чем s2
. Может кто-нибудь объяснить это странное поведение?
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
возвращает asize_t
что этоtypedef
наunsigned
тип.и
(unsigned) 4 - (unsigned) 7 == (unsigned) - 3
все
unsigned
значения больше или равно0
. Попробуйте преобразовать переменные, возвращаемыеstrlen
tolong int
.
Алекс Локвуд ответ это лучшее решение (компактная, четкая семантика и т. д.).
иногда имеет смысл явно преобразовать в подписанную форму
size_t
:ptrdiff_t
, например,return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;
если вы сделаете это, вы хотите быть уверены в том, что
size_t
значение помещается вptrdiff_t
(который имеет на один бит меньше мантиссы).