Ви стикалися з якоюсь особливою поведінкою, яка виникає в 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);
Ласкаво просимо до чудового світу С! :)
Додаткові приклади
Оскільки цьому питанню нещодавно приділялося багато уваги, я хотів би навести кілька (простих) прикладів, лише для того, щоб переконатись, що я доношу ідею до кінця. Я припущу, що ми працюємо з 32-розрядною машиною, використовуючи представлення двох доповнень.
Важливою концепцією, яку слід зрозуміти під час роботи зі змінними без знака / підпису в 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;
}
Ця функція призначена для демонстрації того, наскільки легко можуть виникати помилки внаслідок неявного кастингу з підписаного на безпідписаний. Здається цілком природним передавати параметр length
як беззнаковий; зрештою, хто коли-небудь захоче використовувати негативну довжину? Критерій зупинки i <= length-1
також здається цілком інтуїтивним. Однак при запуску з аргументом, length
рівним 0
, комбінація цих двох дає несподіваний результат.
Оскільки параметр length
без знака, обчислення 0-1
виконуються з використанням беззнакової арифметики, що еквівалентно модульному додаванню. Результатом є UMax . <=
Порівняння також виконується з допомогою беззнакового порівняння, і оскільки будь-яке число менше або дорівнює Umax , порівняння завжди має місце. Таким чином, код спробує отримати доступ до недопустимих елементів масиву a
.
Код може бути виправлений, оголосивши length
його int
або, змінивши тест for
циклу на i < length
.
Висновок: Коли слід використовувати без підпису?
Я не хочу стверджувати тут нічого надто суперечливого, але ось деякі правила, яких я часто дотримуюсь, коли пишу програми на мові C.
НЕ використовуйте лише тому, що число невід’ємне. Помилки легко зробити, і ці помилки іноді неймовірно тонкі (як показано в Прикладі №2).
НЕ використовуйте при виконанні модульної арифметики.
НЕ використовуйте, коли використовуєте біти для представлення наборів. Це часто зручно, оскільки дозволяє виконувати логічні зрушення вправо без розширення знака.
Звичайно, можуть бути ситуації, коли ви вирішили піти проти цих "правил". Але найчастіше, дотримуючись цих пропозицій, ваш код стане простішим у роботі та менш схильним до помилок.
return strlen(s1) > strlen(s2);
.