Чи недолік алгоритму strcasecmp?


34

Я намагаюся повторно реалізувати strcasecmpфункцію в C, і я помітив, що виявляється невідповідністю в процесі порівняння.

З man strcmp

Функція strcmp () порівнює два рядки s1 і s2. Локал не враховується (для порівняння, яке відомо про локали, див. Strcoll (3)). Він повертає ціле число, менше, рівне або більше нуля, якщо виявлено, що s1, відповідно, менше, щоб збігатися, або більше, ніж s2.

З man strcasecmp

Функція strcasecmp () виконує байт-байтове порівняння рядків s1 і s2, ігноруючи регістр символів. Він повертає ціле число, менше, рівне або більше нуля, якщо виявлено, що s1, відповідно, менше, щоб збігатися, або більше, ніж s2.

int strcmp(const char *s1, const char *s2);
int strcasecmp(const char *s1, const char *s2);

Враховуючи цю інформацію, я не розумію результату наступного коду:

#include <stdio.h>
#include <string.h>

int main()
{
    // ASCII values
    // 'A' = 65
    // '_' = 95
    // 'a' = 97

    printf("%i\n", strcmp("A", "_"));
    printf("%i\n", strcmp("a", "_"));
    printf("%i\n", strcasecmp("A", "_"));
    printf("%i\n", strcasecmp("a", "_"));
    return 0;
}

Вихід:

-1  # "A" is less than "_"
1   # "a" is more than "_"
2   # "A" is more than "_" with strcasecmp ???
2   # "a" is more than "_" with strcasecmp

Виявляється, якщо поточний символ у s1букві, він завжди перетворюється на малі регістри, незалежно від того, чи є поточний символ у s2букві чи ні.

Чи може хтось пояснити таку поведінку? Чи не повинні перший і третій рядки бути однаковими?

Заздалегідь спасибі!

PS:
Я використовую gcc 9.2.0Manjaro.
Крім того, коли я компілюю з -fno-builtinпрапором, я отримую натомість:

-30
2
2
2

Я думаю, це тому, що програма не використовує оптимізовані функції gcc, але питання залишається.


2
Додайте до свого набору ще один тестовий випадок: printf("%i\n", strcasecmp("a", "_"));імовірно, це має бути такий же результат, що й printf("%i\n", strcasecmp("A", "_"));Але Але це означає, що один із цих двох викликів, що не враховують регістр, не погодиться зі своїм аналогом, залежним від регістру.
anton.burger

Здається, опис, на strcasecmpякий ви посилаєтесь, не є точним. Більш детально у відповідях, що були обґрунтованими
Jabberwocky

9
Це єдине, що має сенс. Функція, яка каже A < _ && a > _ && A == a, викликала б стільки проблем.
ikegami

Убік: "Я намагаюся повторно реалізувати функцію strcasecmp в C" -> Хоча код не показаний, не забудьте порівняти "як би" unsigned char. C17 / 18 "Обробка рядків <string.h>" -> "Для всіх функцій цього підпункту кожен символ повинен інтерпретуватися так, ніби він має тип unsigned char". Це має charзначення, коли значення знаходяться за межами діапазону ASCII 0-127.
chux

1
Щодо відмінностей у результатах із вбудованими та без: Обидва кажуть те саме, оскільки їх результати однакові <0 та> 0, і ви не маєте прикладу для == 0. Але ви можете бачити, як алгоритми світяться наскрізь: деякі повернуті значення - це відмінності першого нерівного символу.
зайнята бджола

Відповіді:


31

Поведінка правильна.

Відповідно до str\[n\]casecmp()специфікації POSIX :

Коли LC_CTYPEкатегорія використовуваного локалу походить з локалі POSIX, ці функції поводяться так, ніби рядки були перетворені в малі регістри, а потім виконується порівняння байтів. В іншому випадку результати не визначені.

Це також є частиною в NOTES частині сторінки людини Linux :

Стандарт POSIX.1-2008 говорить про такі функції:

Коли категорія LC_CTYPE використовуваного локалу походить з локалі POSIX, ці функції поводяться так, ніби рядки були перетворені в малі регістри, а потім виконується порівняння байтів. В іншому випадку результати не визначені.

Чому?

Як зазначав @HansOlsson у своїй відповіді , порівняння лише букв, що не залежать від регістру, і дозволяє всім порівнянням мати свої "природні" результати, як це strcmp()було зроблено , порушить сортування.

Якщо 'A' == 'a'(визначення порівняння, що не враховує регістр), то '_' > 'A'і '_' < 'a'("природні" результати в наборі символів ASCII) не може бути істинним.


Якщо порівняння лише букв не залежно від регістру, це не призведе до '_' > 'A' && '_' < 'a'; не здається найкращим прикладом.
Астероїди з крилами

1
@AsteroidsWithWings Це символи, які використовуються у питанні. І якщо , 'a' == 'A' за визначенням , якщо зробити порівняння між «природними» значеннями 'a', 'A'і '_", ви не можете зробити регістронезавісімого порівняння 'A'і 'a'отримати рівність і отримати стійкі результати сортування.
Ендрю Генле

Я не заперечую це, але конкретний зустрічний приклад, який ви подали, не здається актуальним.
Астероїди з крилами

@AsteroidsWithWings Пройдіть розумову вправу побудови бінарного дерева з 'a', 'A'і '_', пройшовши всі 6 порядків вставки у дерево, та порівнявши результати із заданими "завжди малими літерами" із запропонованим запитанням "лише конвертувати букви коли це порівняння буквою до листа ". Наприклад, використовуючи останній алгоритм і починаючи з '_', 'a'і 'A'звиваючись на протилежних сторонах дерева, але вони визначаються як рівні. Алгоритм "тільки перетворити літери в малі регістри в порівнянні букв-літер" порушений, і ці 3 символи показують це.
Ендрю Генле

Гаразд, тоді я пропоную продемонструвати це у відповіді, оскільки на даний момент це просто перескакує на те, що " '_' > 'A' і '_' < 'a'не може бути обом правдивим", не розповідаючи нам, чому ми коли-небудь могли думати, що це буде. (Це завдання для відповідача, а не для одного з мільйонів читачів.)
Астероїди з крилами

21

Інші посилання, http://man7.org/linux/man-pages/man3/strcasecmp.3p.html для strcasecmp, говорять, що перетворення на малі регістри - це правильна поведінка (принаймні, у локалі POSIX).

Причина такої поведінки полягає в тому, що якщо ви використовуєте strcasecmp для сортування масиву рядків, це потрібно для отримання розумних результатів.

В іншому випадку, якщо ви спробуєте сортувати "A", "C", "_", "b", наприклад, використовуючи qsort, результат залежатиме від порядку порівнянь.


3
В іншому випадку, якщо ви спробуєте сортувати "A", "C", "_", "b", наприклад, використовуючи qsort, результат залежатиме від порядку порівнянь. Гарна думка. Це, ймовірно, причина POSIX визначає поведінку.
Ендрю Генле

6
Більш конкретно, вам потрібно загальне замовлення для сортування, що не було б випадком, якщо ви визначите порівняння як у питанні (оскільки воно не було б перехідним).
Герцогство

8

Здається, що якщо поточний символ у s1 є буквою, він завжди перетворюється на малі регістри, незалежно від того, чи є поточний символ у s2 буквою чи ні.

Це правильно - і це те, що повинна виконувати strcasecmp()функція ! Це функція, а не частина стандарту, але з " Технічної бази відкритої групи, випуск 6 ":POSIXC

У локалі POSIX strcasecmp () та strncasecmp () повинні вести себе так, як ніби рядки були перетворені в малі регістри, а потім виконується порівняння байтів. Результати не визначені в інших регіонах.

До речі, така поведінка також відноситься до _stricmp()функції (як використовується у Visual Studio / MSCV):

Функція _stricmp звичайно порівнює string1 та string2 після перетворення кожного символу в малі регістри та повертає значення, що вказують на їх співвідношення.


2

ASCII десятковий код Aє 65для _це 95і aє 97, тому strcmp()він робить те , що це припустимо , що робити. Лексикографічно кажучи, _то менше aі більше, ніж A.

strcasecmp()буде вважатися Aяк a*, і оскільки aбільший, ніж _вихідний також є правильним.

* Стандарт POSIX.1-2008 говорить про такі функції (strcasecmp () і strncasecmp ()):

Коли категорія LC_CTYPE використовуваного локалу походить з локалі POSIX, ці функції поводяться так, ніби рядки були перетворені в малі регістри, а потім виконується порівняння байтів. В іншому випадку результати не визначені.

Джерело: http://man7.org/linux/man-pages/man3/strcasecmp.3.html


3
Суть ОП у тому, що Aвона "більша", ніж _при порівнянні безрезультатних випадків, і дивується, чому результат не такий, як при порівнянні з урахуванням регістру.
anton.burger

6
Заява Since strcasecmp () `нечутливе до регістру, воно вважатиметься A як a" є недійсним відрахуванням. Програма, що не враховує великі регістри, може поводитись з усіма великими літерами так, ніби вони є малими літерами, можуть ставитися до всіх малих літер так, ніби вони є великими літерами, або як кожна велика літера як однакова до відповідної малої літери і навпаки, але все ж порівнювати їх до не буквених символів із їхніми вихідними значеннями. Ця відповідь не вказує на причину віддати перевагу будь-якій із цих можливостей (правильною причиною, якою є документація, є використання малих літер).
Eric Postpischil

@EricPostpischil Стандарт POSIX.1-2008 говорить про ці функції (strcasecmp () і strncasecmp ()): Коли категорія LC_CTYPE використовуваного локального ресурсу знаходиться з локалі POSIX, ці функції поводяться так, як ніби рядки були перетворені в Проведено порівняння з малих літер, а потім байт. В іншому випадку результати не визначені.
anastaciu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.