Czy algorytm strcasecmp jest wadliwy?

34

Próbuję ponownie wdrożyć strcasecmpfunkcję w C i zauważyłem, co wydaje się być niespójnością w procesie porównywania.

Od man strcmp

Funkcja strcmp () porównuje dwa ciągi s1 i s2. Ustawienia regionalne nie są brane pod uwagę (dla porównania uwzględniającego ustawienia regionalne, patrz strcoll (3)). Zwraca liczbę całkowitą mniejszą, równą lub większą od zera, jeśli okaże się, że s1 jest odpowiednio mniejsza niż, do dopasowania lub większa niż s2.

Od man strcasecmp

Funkcja strcasecmp () wykonuje porównanie bajtów po bajtach ciągów s1 i s2, ignorując wielkość liter w znakach. Zwraca liczbę całkowitą mniejszą, równą lub większą od zera, jeśli okaże się, że s1 jest odpowiednio mniejsza niż, do dopasowania lub większa niż s2.

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

Biorąc pod uwagę te informacje, nie rozumiem wyniku następującego kodu:

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

Ouput:

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

Wygląda na to, że jeśli bieżącym znakiem w s1jest litera, to zawsze jest konwertowany na małe litery, niezależnie od tego, czy bieżącym znakiem s2jest litera, czy nie.

Czy ktoś może wyjaśnić to zachowanie? Czy pierwsza i trzecia linia nie powinny być identyczne?

Z góry dziękuję!

PS:
Używam gcc 9.2.0na Manjaro.
Ponadto, kiedy kompiluję z -fno-builtinflagą, otrzymuję zamiast tego:

-30
2
2
2

Chyba dlatego, że program nie korzysta ze zoptymalizowanych funkcji gcc, ale pozostaje pytanie.

Haltarys
źródło
2
Dodaj kolejny przypadek testowy do swojego zestawu: printf("%i\n", strcasecmp("a", "_"));Prawdopodobnie powinien on mieć taki sam wynik jak printf("%i\n", strcasecmp("A", "_"));Ale Oznacza to, że jedno z tych dwóch połączeń bez rozróżniania wielkości liter nie zgadza się z jego odpowiednikiem z rozróżnianiem wielkości liter.
anton.burger
Wygląda na to, że opis, do strcasecmpktórego się odnosisz, nie jest dokładny. Więcej szczegółów w recenzowanych odpowiedziach.
Jabberwocky
9
To jedyna rzecz, która ma sens. Funkcja, która mówi A < _ && a > _ && A == a, spowodowałaby tyle problemów.
ikegami
Poza tym: „Próbuję ponownie wdrożyć funkcję strcasecmp w C” -> Chociaż kod nie jest wyświetlany, należy porównać „tak, jakby” unsigned char. C17 / 18 „Obsługa ciągów <ciąg.h>” -> „Dla wszystkich funkcji w niniejszej podsekcji każdy znak należy interpretować tak, jakby miał typ unsigned char”. To robi różnicę, gdy charwartości są poza zakresem 0-127 ASCII.
chux - Przywróć Monikę
1
O różnicach w wynikach z wbudowanymi i bez: oba mówią to samo, ponieważ ich wyniki są identyczne <0 i> 0, a nie masz przykładu dla == 0. Ale widać, że algorytmy świecą: niektóre z zwracanych wartości są różnicami pierwszego nierównomiernego znaku.
zajęty

Odpowiedzi:

31

Zachowanie jest poprawne.

Zgodnie ze str\[n\]casecmp()specyfikacją POSIX :

Gdy LC_CTYPEkategoria używanego ustawienia narodowego pochodzi z ustawienia narodowego POSIX, funkcje te powinny zachowywać się tak, jakby łańcuchy zostały przekonwertowane na małe litery, a następnie przeprowadzono porównanie bajtów. W przeciwnym razie wyniki nie są określone.

Który jest również częścią tej UWAGI sekcji strony Linux man :

Standard POSIX.1-2008 mówi o tych funkcjach:

Gdy kategoria LC_CTYPE używanego ustawienia narodowego pochodzi z ustawienia narodowego POSIX, funkcje te powinny zachowywać się tak, jakby łańcuchy zostały przekonwertowane na małe litery, a następnie przeprowadzono porównanie bajtów. W przeciwnym razie wyniki nie są określone.

Dlaczego?

Jak zauważył @HansOlsson w swojej odpowiedzi , dokonywanie bez rozróżniania wielkości liter porównań tylko liter i pozwalanie, aby wszystkie inne porównania miały swoje „naturalne” wyniki, tak jak w przypadku, strcmp()spowodowałoby przerwanie sortowania.

Jeśli 'A' == 'a'(definicja porównania bez rozróżniania wielkości liter), to '_' > 'A'i '_' < 'a'(„naturalny” wynik w zestawie znaków ASCII) nie mogą być prawdziwe.

Andrew Henle
źródło
Wykonywanie porównań bez rozróżniania wielkości liter tylko liter nie spowodowałoby '_' > 'A' && '_' < 'a'; nie wydaje się najlepszym przykładem.
Asteroidy ze skrzydłami
1
@AsteroidsWithWings Są to znaki użyte w pytaniu. A jeśli 'a' == 'A' z definicji , jeśli zrobić porównanie pomiędzy „naturalnych” wartości 'a', 'A'i '_”, to nie można zrobić bez uwzględniania wielkości liter porównanie 'A'i 'a'uzyskać równość i uzyskać spójne wyniki sortowania.
Andrew Henle
Nie kwestionuję tego, ale podany przez ciebie konkretny kontrprzykład wydaje się nie mieć znaczenia.
Asteroidy ze skrzydłami
@AsteroidsWithWings przejść przez umysłowego wysiłku budowania binarne drzewo z 'a', 'A'i '_', przechodząc przez wszystkie 6 rzędów wstawienie do drzewa, i porównując wyniki z AS-wymienionych „zawsze małe litery” na pytanie zaproponował „tylko konwertować litery gdy jest to porównanie liter do liter ". Na przykład, używając drugiego algorytmu i zaczynając od '_', 'a'i 'A'kończymy po przeciwnych stronach drzewa, ale są one zdefiniowane jako równe. Algorytm „konwertuj tylko litery na małe litery w porównaniach liter i liter” jest zepsuty i pokazują to 3 znaki.
Andrew Henle
Okej, sugeruję więc wykazanie tego w odpowiedzi, ponieważ w tej chwili przeskakuję do wskazania, że '_' > 'A' i '_' < 'a'nie może to być prawda”, nie mówiąc nam, dlaczego powinniśmy tak sądzić. (Jest to zadanie dla osoby odpowiadającej, a nie dla jednego z milionów czytelników.)
Asteroids With Wings
21

Inne linki, http://man7.org/linux/man-pages/man3/strcasecmp.3p.html dla strcasecmp mówią, że konwersja na małe litery jest poprawnym zachowaniem (przynajmniej w ustawieniach regionalnych POSIX).

Powodem takiego zachowania jest to, że jeśli używasz strcasecmp do sortowania tablicy ciągów, potrzebne jest uzyskanie rozsądnych wyników.

W przeciwnym razie, jeśli spróbujesz posortować „A”, „C”, „_”, „b” przy użyciu np. Qsort, wynik będzie zależał od kolejności porównań.

Hans Olsson
źródło
3
W przeciwnym razie, jeśli spróbujesz posortować „A”, „C”, „_”, „b” przy użyciu np. Qsort, wynik będzie zależał od kolejności porównań. Słuszna uwaga. Jest to prawdopodobnie powód, dla którego POSIX określa zachowanie.
Andrew Henle
6
Mówiąc konkretniej, do sortowania potrzebna jest całkowita kolejność , co nie miałoby miejsca, gdyby zdefiniować porównanie jak w pytaniu (ponieważ nie byłoby przechodnie).
Dukeling
8

Wygląda na to, że jeśli bieżącym znakiem w s1 jest litera, zawsze jest konwertowany na małe litery, niezależnie od tego, czy obecny znak w s2 jest literą, czy nie.

Zgadza się - i taka powinna byćstrcasecmp() funkcja ! Jest to funkcja, a nie część standardu, ale z „ Podstawowych specyfikacji grupy otwartej, wydanie 6 ”:POSIXC

W ustawieniach regionalnych POSIX strcasecmp () i strncasecmp () zachowują się tak, jakby ciągi zostały przekonwertowane na małe litery, a następnie przeprowadzono porównanie bajtów. Wyniki nie są określone w innych lokalizacjach.

Nawiasem mówiąc, to zachowanie dotyczy również _stricmp()funkcji (stosowanej w Visual Studio / MSCV):

Funkcja _stricmp zwykle porównuje łańcuch1 i łańcuch2 po konwersji każdego znaku na małe litery i zwraca wartość wskazującą na ich związek.

Adrian Mole
źródło
2

ASCII kod dziesiętny za Ato 65za _to 95i za ato 97, więc strcmp()to co robi to przypuszczać, aby zrobić. Mówiąc leksykograficznie, _jest on wtedy mniejszy ai większy niż A.

strcasecmp()będzie Auważany za a*, a ponieważ ajest większy niż _wynik jest również poprawny.

* Standard POSIX.1-2008 mówi o tych funkcjach (strcasecmp () i strncasecmp ()):

Gdy kategoria LC_CTYPE używanego ustawienia narodowego pochodzi z ustawienia narodowego POSIX, funkcje te powinny zachowywać się tak, jakby łańcuchy zostały przekonwertowane na małe litery, a następnie przeprowadzono porównanie bajtów. W przeciwnym razie wyniki nie są określone.

Źródło: http://man7.org/linux/man-pages/man3/strcasecmp.3.html

anastaciu
źródło
3
Chodzi o to, że OP Ajest „większy” niż _przy porównywaniu bez rozróżniania wielkości liter i zastanawia się, dlaczego wynik nie jest taki sam, jak przy porównywaniu rozróżniania wielkości liter.
anton.burger
6
Instrukcja Since strcasecmp () `nie rozróżnia wielkości liter, uzna, że ​​A jako a` jest niepoprawnym wnioskiem. Procedura bez rozróżniania wielkości liter może traktować wszystkie wielkie litery tak, jakby były małymi literami, może traktować wszystkie małe litery tak, jakby były dużymi literami, lub może traktować każdą wielką literę jako równą odpowiadającej jej małej i odwrotnie, ale nadal je porównywać do znaków innych niż litery z ich surowymi wartościami. Ta odpowiedź nie podaje powodu preferowania żadnej z tych możliwości (prawidłowy powód, dla którego dokumentacja mówi, aby używać małych liter).
Eric Postpischil
@EricPostpischil Standard POSIX.1-2008 mówi o tych funkcjach (strcasecmp () i strncasecmp ()): Gdy kategoria LC_CTYPE używanego ustawienia narodowego pochodzi z ustawień narodowych POSIX, funkcje te powinny zachowywać się tak, jakby ciągi zostały przekonwertowane na dokonano porównania małych liter, a następnie porównania bajtów. W przeciwnym razie wyniki nie są określone.
anastaciu