L'algorithme strcasecmp est-il défectueux?

34

J'essaie de réimplémenter la strcasecmpfonction en C et j'ai remarqué ce qui semble être une incohérence dans le processus de comparaison.

De man strcmp

La fonction strcmp () compare les deux chaînes s1 et s2. Les paramètres régionaux ne sont pas pris en compte (pour une comparaison tenant compte des paramètres régionaux, voir strcoll (3)). Il retourne un entier inférieur, égal ou supérieur à zéro si s1 se révèle, respectivement, être inférieur à, correspondre ou être supérieur à s2.

De man strcasecmp

La fonction strcasecmp () effectue une comparaison octet par octet des chaînes s1 et s2, en ignorant la casse des caractères. Il retourne un entier inférieur, égal ou supérieur à zéro si s1 se révèle, respectivement, être inférieur à, correspondre ou être supérieur à s2.

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

Étant donné ces informations, je ne comprends pas le résultat du code suivant:

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

Sortie:

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

Il semble que, si le caractère actuel dans s1est une lettre, il est toujours converti en minuscules, que le caractère actuel dans s2soit une lettre ou non.

Quelqu'un peut-il expliquer ce comportement? Les première et troisième lignes ne devraient-elles pas être identiques?

Merci d'avance!

PS:
J'utilise gcc 9.2.0sur Manjaro.
De plus, lorsque je compile avec le -fno-builtindrapeau, je reçois à la place:

-30
2
2
2

Je suppose que c'est parce que le programme n'utilise pas les fonctions optimisées de gcc, mais la question demeure.

Haltarys
la source
2
Ajoutez un autre cas de test à votre jeu: printf("%i\n", strcasecmp("a", "_"));cela devrait probablement avoir le même résultat que printf("%i\n", strcasecmp("A", "_"));Mais cela signifie que l' un de ces deux appels ne respectant pas la casse va être en désaccord avec son homologue sensible à la casse.
anton.burger
Il semble que la description à laquelle strcasecmpvous faites référence ne soit pas exacte. Plus de détails dans les réponses votées.
Jabberwocky
9
C'est la seule chose qui ait du sens. Une fonction qui dit A < _ && a > _ && A == acauserait tellement de problèmes.
ikegami
En plus: "J'essaye de réimplémenter la fonction strcasecmp en C" -> Bien que le code ne soit pas affiché, assurez-vous de comparer "comme si" unsigned char. C17 / 18 "Gestion des chaînes <string.h>" -> "Pour toutes les fonctions du présent paragraphe, chaque caractère doit être interprété comme s'il avait le type unsigned char". Cela fait une différence une fois que les charvaleurs sont en dehors de la plage ASCII 0-127.
chux
1
Sur les différences de sorties avec et sans intégré: les deux disent la même chose, car leurs résultats sont identiques <0 et> 0, et vous n'avez pas d'exemple pour == 0. Mais vous pouvez voir les algorithmes transparaître: certaines des valeurs renvoyées sont les différences du premier caractère non égal.
le busybee

Réponses:

31

Le comportement est correct.

Selon la str\[n\]casecmp()spécification POSIX :

Lorsque la LC_CTYPEcatégorie des paramètres régionaux utilisés provient des paramètres régionaux POSIX, ces fonctions doivent se comporter comme si les chaînes avaient été converties en minuscules, puis une comparaison d'octets a été effectuée. Sinon, les résultats ne sont pas spécifiés.

Cela fait également partie de la section NOTES de la page de manuel Linux :

La norme POSIX.1-2008 dit de ces fonctions:

Lorsque la catégorie LC_CTYPE des paramètres régionaux utilisés provient des paramètres régionaux POSIX, ces fonctions doivent se comporter comme si les chaînes avaient été converties en minuscules, puis une comparaison d'octets a été effectuée. Sinon, les résultats ne sont pas spécifiés.

Pourquoi?

Comme @HansOlsson l'a souligné dans sa réponse , faire des comparaisons insensibles à la casse entre seulement des lettres et permettre à toutes les autres comparaisons d'avoir leurs résultats "naturels" comme cela le strcmp()ferait romprait le tri.

Si 'A' == 'a'(la définition d'une comparaison insensible à la casse) alors '_' > 'A'et '_' < 'a'(les résultats "naturels" dans le jeu de caractères ASCII) ne peuvent pas être tous les deux vrais.

Andrew Henle
la source
Faire des comparaisons insensibles à la casse entre seulement des lettres n'entraînerait pas '_' > 'A' && '_' < 'a'; ne semble pas être le meilleur exemple.
Asteroids With Wings
1
@AsteroidsWithWings Ce sont les caractères utilisés dans la question. Et si 'a' == 'A' par définition , si vous faites une comparaison entre les valeurs "naturelles" de 'a', 'A'et '_', vous ne pouvez pas faire une comparaison insensible à la casse entre 'A'et 'a'pour obtenir l'égalité et obtenir des résultats de tri cohérents.
Andrew Henle
Je ne le conteste pas, mais le contre-exemple spécifique que vous avez fourni ne semble pas pertinent.
Asteroids With Wings
@AsteroidsWithWings Passez par l'exercice mental de construction d'un arbre binaire à partir de 'a', 'A'et '_', en parcourant les 6 ordres d'insertion dans l'arbre, et en comparant les résultats des "lettres toujours minuscules" spécifiées à la proposition de la question "ne convertir que les lettres" quand c'est une comparaison de lettre à lettre ". Par exemple, en utilisant ce dernier algorithme et en commençant par '_', 'a'et vous vous 'A'retrouvez sur les côtés opposés de l'arbre, mais ils sont définis comme égaux. L'algorithme «convertir uniquement les lettres en minuscules dans les comparaisons lettre à lettre» est rompu et ces 3 caractères le montrent.
Andrew Henle
D'accord, alors je suggère de démontrer cela dans la réponse, car pour le moment, cela revient simplement à souligner que " '_' > 'A' et '_' < 'a'ne peut pas être vrai tous les deux" sans nous dire pourquoi nous aurions jamais pensé que ce serait le cas. (C'est une tâche pour le répondeur, pas pour un million de lecteurs.)
Asteroids With Wings
21

D'autres liens, http://man7.org/linux/man-pages/man3/strcasecmp.3p.html pour strcasecmp, indiquent que la conversion en minuscules est le comportement correct (au moins dans les paramètres régionaux POSIX).

La raison de ce comportement est que si vous utilisez strcasecmp pour trier un tableau de chaînes, il est nécessaire d'obtenir des résultats raisonnables.

Sinon, si vous essayez de trier "A", "C", "_", "b" en utilisant par exemple qsort, le résultat dépendra de l'ordre des comparaisons.

Hans Olsson
la source
3
Sinon, si vous essayez de trier "A", "C", "_", "b" en utilisant par exemple qsort, le résultat dépendra de l'ordre des comparaisons. Bon point. C'est probablement la raison pour laquelle POSIX spécifie le comportement.
Andrew Henle
6
Plus concrètement, vous avez besoin d'un ordre total de tri, ce qui ne serait pas le cas si vous définissez la comparaison comme dans la question (car elle ne serait pas transitive).
Dukeling
8

Il semble que, si le caractère actuel de s1 est une lettre, il est toujours converti en minuscules, que le caractère actuel de s2 soit ou non une lettre.

C'est exact - et c'est ce que la strcasecmp()fonction devrait faire! Il s'agit d'une POSIXfonction, plutôt que d'une partie de la Cnorme, mais, à partir de " The Open Group Base Specifications, Issue 6 ":

Dans l'environnement local POSIX, strcasecmp () et strncasecmp () doivent se comporter comme si les chaînes avaient été converties en minuscules, puis une comparaison d'octets était effectuée. Les résultats ne sont pas spécifiés dans d'autres paramètres régionaux.

Par ailleurs, ce comportement concerne également la _stricmp()fonction (telle qu'utilisée dans Visual Studio / MSCV):

La fonction _stricmp compare ordinairement string1 et string2 après la conversion de chaque caractère en minuscules et renvoie une valeur indiquant leur relation.

Adrian Mole
la source
2

Le code décimal ASCII Aest 65pour _est 95et aest 97, donc strcmp()il fait ce qu'il est supposé faire. Lexicographiquement parlant _est alors plus petit aet plus grand que A.

strcasecmp()sera considéré Acomme étant a*, et comme il aest plus grand que _la sortie est également correct.

* La norme POSIX.1-2008 dit de ces fonctions (strcasecmp () et strncasecmp ()):

Lorsque la catégorie LC_CTYPE des paramètres régionaux utilisés provient des paramètres régionaux POSIX, ces fonctions doivent se comporter comme si les chaînes avaient été converties en minuscules, puis une comparaison d'octets a été effectuée. Sinon, les résultats ne sont pas spécifiés.

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

anastaciu
la source
3
Le point d'OP est que Ac'est "plus grand" que _lors d'une comparaison insensible à la casse, et se demande pourquoi le résultat n'est pas le même que lors d'une comparaison sensible à la casse.
anton.burger
6
L'instruction Since strcasecmp () `est insensible à la casse, elle considérera A comme étant a` est une déduction non valide. Une routine insensible à la casse pourrait traiter toutes les lettres majuscules comme s'il s'agissait de lettres minuscules, pourrait traiter toutes les lettres minuscules comme s'il s'agissait de lettres majuscules, ou pourrait traiter chaque lettre majuscule comme égale à sa lettre minuscule correspondante et vice-versa, mais toujours les comparer aux caractères non alphabétiques avec leurs valeurs brutes. Cette réponse n'indique pas de raison pour préférer l'une de ces possibilités (la raison correcte pour laquelle la documentation dit d'utiliser des minuscules).
Eric Postpischil
@EricPostpischil La norme POSIX.1-2008 dit de ces fonctions (strcasecmp () et strncasecmp ()): Lorsque la catégorie LC_CTYPE des paramètres régionaux utilisés provient des paramètres régionaux POSIX, ces fonctions doivent se comporter comme si les chaînes avaient été converties en minuscule puis une comparaison d'octets effectuée. Sinon, les résultats ne sont pas spécifiés.
anastaciu