L'algoritmo strcasecmp è difettoso?


34

Sto cercando di reimplementare la strcasecmpfunzione in C e ho notato ciò che sembra essere un'incoerenza nel processo di confronto.

A partire dal man strcmp

La funzione strcmp () confronta le due stringhe s1 e s2. Le impostazioni internazionali non vengono prese in considerazione (per un confronto consapevole delle impostazioni locali, vedere strcoll (3)). Restituisce un numero intero minore di, uguale a, o maggiore di zero se si trova, rispettivamente, che s1 è minore di, corrispondente o maggiore di s2.

A partire dal man strcasecmp

La funzione strcasecmp () esegue un confronto byte per byte delle stringhe s1 e s2, ignorando il caso dei caratteri. Restituisce un numero intero minore di, uguale a, o maggiore di zero se si trova, rispettivamente, che s1 è minore di, corrispondente o maggiore di s2.

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

Dato, queste informazioni, non capisco il risultato del seguente codice:

#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

Sembra che, se il carattere corrente in s1è una lettera, viene sempre convertito in minuscolo, indipendentemente dal fatto che il carattere corrente s2sia una lettera o meno.

Qualcuno può spiegare questo comportamento? La prima e la terza riga non dovrebbero essere identiche?

Grazie in anticipo!

PS:
sto usando gcc 9.2.0Manjaro.
Inoltre, quando compilo con la -fno-builtinbandiera ottengo invece:

-30
2
2
2

Immagino sia perché il programma non utilizza le funzioni ottimizzate di gcc, ma la domanda rimane.


2
Aggiungi un altro caso di test al tuo set: printf("%i\n", strcasecmp("a", "_"));questo dovrebbe presumibilmente avere lo stesso risultato di printf("%i\n", strcasecmp("A", "_"));Ma ciò significa che una di queste due chiamate senza distinzione tra maiuscole e minuscole non sarà d'accordo con la sua controparte sensibile al maiuscolo / minuscolo.
anton.burger,

Sembra che la descrizione di strcasecmpcui ti riferisci non sia accurata. Maggiori dettagli nelle risposte votate.
Jabberwocky,

9
È l'unica cosa che ha senso. Una funzione che dice A < _ && a > _ && A == acauserebbe così tanti problemi.
ikegami,

A parte: "Sto cercando di reimplementare la funzione strcasecmp in C" -> Sebbene il codice non sia mostrato, assicurati di confrontare "come se" unsigned char. C17 / 18 "Gestione delle stringhe <string.h>" -> "Per tutte le funzioni di questo sottoclauso, ogni carattere deve essere interpretato come se avesse il tipo unsigned char". Questo fa la differenza quando i charvalori sono al di fuori dell'intervallo ASCII 0-127.
chux - Ripristina Monica il

1
Sulle differenze negli output con built-in e senza: entrambi dicono lo stesso, poiché i loro risultati sono identici <0 e> 0 e non hai un esempio per == 0. Ma puoi vedere brillare gli algoritmi: alcuni dei valori restituiti sono le differenze del primo carattere non uguale.
l'occupato

Risposte:


31

Il comportamento è corretto

Secondo le str\[n\]casecmp()specifiche POSIX :

Quando la LC_CTYPEcategoria della locale in uso proviene dalla locale POSIX, queste funzioni si comporteranno come se le stringhe fossero state convertite in minuscolo e quindi eseguito un confronto di byte. Altrimenti, i risultati non sono specificati.

Questo è anche parte del NOTE sezione della pagina man di Linux :

Lo standard POSIX.1-2008 afferma queste funzioni:

Quando la categoria LC_CTYPE della locale in uso proviene dalla locale POSIX, queste funzioni si comporteranno come se le stringhe fossero state convertite in minuscolo e quindi eseguito un confronto di byte. Altrimenti, i risultati non sono specificati.

Perché?

Come ha sottolineato @HansOlsson nella sua risposta , fare confronti senza distinzione tra maiuscole e minuscole tra sole lettere e consentire a tutti gli altri confronti di avere i loro risultati "naturali" come fatto nel strcmp()rompere l'ordinamento.

Se 'A' == 'a'(la definizione di un confronto senza distinzione tra maiuscole e minuscole) allora '_' > 'A'e '_' < 'a'(il "naturale" risulta nel set di caratteri ASCII) non può essere vero.


Fare confronti senza distinzione tra maiuscole e minuscole tra sole lettere non si tradurrebbe in '_' > 'A' && '_' < 'a'; non sembra il miglior esempio.
Asteroidi con le ali

1
@AsteroidsWithWings Questi sono i personaggi usati nella domanda. E se 'a' == 'A' per definizione , se si esegue un confronto tra i valori "naturali" di 'a', 'A'e '_', non è possibile eseguire un confronto senza distinzione tra maiuscole e minuscole tra 'A'e 'a'per ottenere l'uguaglianza e ottenere risultati di ordinamento coerenti.
Andrew Henle,

Non lo sto contestando, ma il controesempio specifico che hai fornito non sembra essere pertinente.
Asteroidi con le ali

@AsteroidsWithWings Passa attraverso l'esercizio mentale di costruzione di un albero binario da 'a', 'A'e '_', passando attraverso tutti e 6 gli ordini di inserimento nell'albero, e confrontando i risultati delle "lettere minuscole" come specificato con le domande proposte "converti solo le lettere quando si tratta di un confronto da lettera a lettera ". Ad esempio, usando quest'ultimo algoritmo e iniziando con '_', 'a'e 'A'finendo su lati opposti dell'albero, sono definiti uguali. L'algoritmo "converti solo lettere in lettere minuscole in confronti di lettere" è rotto e quei 3 caratteri lo dimostrano.
Andrew Henle,

Va bene, allora suggerisco di dimostrarlo nella risposta perché al momento salta semplicemente a sottolineare che " '_' > 'A' e '_' < 'a'non può essere vero" senza dirci perché avremmo mai pensato che sarebbe stato. (Questo è un compito per chi risponde, non per uno dei milioni di lettori.)
Asteroids With Wings

21

Altri collegamenti, http://man7.org/linux/man-pages/man3/strcasecmp.3p.html per strcasecmp dicono che la conversione in minuscolo è il comportamento corretto (almeno nelle impostazioni internazionali POSIX).

Il motivo di tale comportamento è che se si utilizza strcasecmp per ordinare una matrice di stringhe, è necessario ottenere risultati ragionevoli.

Altrimenti, se si tenta di ordinare "A", "C", "_", "b" utilizzando ad es. Qsort il risultato dipenderà dall'ordine dei confronti.


3
Altrimenti, se si tenta di ordinare "A", "C", "_", "b" utilizzando ad es. Qsort il risultato dipenderà dall'ordine dei confronti. Buon punto. Questo è probabilmente il motivo per cui POSIX specifica il comportamento.
Andrew Henle,

6
Più concretamente, è necessario un ordine totale per l'ordinamento, il che non sarebbe il caso se si definisse il confronto come nella domanda (poiché non sarebbe transitivo).
Dukeling,

8

Sembra che, se il carattere corrente in s1 è una lettera, viene sempre convertito in minuscolo, indipendentemente dal fatto che il carattere corrente in s2 sia o meno una lettera.

È corretto - ed è ciò che la strcasecmp()funzione dovrebbe fare! È una POSIXfunzione, piuttosto che parte dello Cstandard ma, dalle " Specifiche di base del gruppo aperto, numero 6 ":

Nella locale POSIX, strcasecmp () e strncasecmp () devono comportarsi come se le stringhe fossero state convertite in minuscolo e quindi eseguito un confronto di byte. I risultati non sono specificati in altre lingue.

Per inciso, questo comportamento è anche pertinente alla _stricmp()funzione (come usata in Visual Studio / MSCV):

La funzione _stricmp confronta normalmente string1 e string2 dopo aver convertito ogni carattere in minuscolo e restituisce un valore che indica la loro relazione.


2

Il codice decimale ASCII per Aè 65per _è 95e per aè 97, quindi strcmp()sta facendo quello che dovrebbe fare. Lessicograficamente parlando _è più piccolo allora ae più grande di A.

strcasecmp()considererà Acome a*, e poiché aè più grande _dell'output è anche corretto.

* Lo standard POSIX.1-2008 dice di queste funzioni (strcasecmp () e strncasecmp ()):

Quando la categoria LC_CTYPE della locale in uso proviene dalla locale POSIX, queste funzioni si comporteranno come se le stringhe fossero state convertite in minuscolo e quindi eseguito un confronto di byte. Altrimenti, i risultati non sono specificati.

Fonte: http://man7.org/linux/man-pages/man3/strcasecmp.3.html


3
Il punto di OP è che Aè "più grande" rispetto a _quando si fa un confronto senza distinzione tra maiuscole e minuscole e si chiede perché il risultato non sia lo stesso di quando si confronta un distinzione tra maiuscole e minuscole.
anton.burger,

6
L'istruzione Since strcasecmp () `non fa distinzione tra maiuscole e minuscole e considererà A come essere a` è una detrazione non valida. Una routine senza distinzione tra maiuscole e minuscole potrebbe trattare tutte le lettere maiuscole come se fossero lettere minuscole, potrebbe trattare tutte le lettere minuscole come se fossero lettere maiuscole o potrebbe trattare ciascuna lettera maiuscola come uguale alla lettera minuscola corrispondente e viceversa, ma comunque confrontarle ai caratteri non lettera con i loro valori grezzi. Questa risposta non indica un motivo per preferire nessuna di queste possibilità (il motivo corretto per cui la documentazione dice di usare le lettere minuscole).
Eric Postpischil,

@EricPostpischil Lo standard POSIX.1-2008 dice di queste funzioni (strcasecmp () e strncasecmp ()): quando la categoria LC_CTYPE della locale in uso proviene dalla locale POSIX, queste funzioni devono comportarsi come se le stringhe fossero state convertite in minuscoli e quindi eseguito un confronto di byte. Altrimenti, i risultati non sono specificati.
anastaciu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.