Deux choses me gênent dans la description de la fonction strcmp dans la
norme (C90 tout comme C99) :
1er point :
--------------------------------8<-----------------------------------
7.21.4 Comparison functions
1 The sign of a nonzero value returned by the comparison functions
memcmp, strcmp, and strncmp is determined by the sign of the difference
between the values of the first pair of characters (both interpreted as
unsigned char) that differ in the objects being compared.
-------------------------------->8-----------------------------------
Pourquoi employer l'expression "is determined" qui est assez vague
(pour moi, ici ça veut dire "est déterminé") ? Pourquoi ne pas dire
que les signes sont identiques (donc "coincides with" ou "matches" ou
"is identical to", etc) ?
2ème point :
--------------------------------8<-----------------------------------
7.21.4.2 p3 The strcmp function returns an integer greater than, equal
to, or less than zero, accordingly as the string pointed to by s1 is
greater than, equal to, or less than the string pointed to by s2.
-------------------------------->8-----------------------------------
Où dans la norme la notion de "chaîne plus grande qu'une autre" est-elle
définie ?
"Antoine Leca" a écrit dans le message de news: g7gt6i$jra$
En news:g7g96t$n8s$, Charlie Gordon va escriure:
candide a écrit dans le message de news: 489b67ef$0$19939$
Donc pour reformuler la question, comment un processeur est-il programmé pour comparer des entiers ? fait-il une soustraction puis examine t-il un bit de signe ? Ou alors, procède-t-il justement lexicographiquement ? Pi si ça se trouve ça va dépendre des processeurs.
Dans le cas des x86, le CPU fait effectivement la soustraction mais ne stocke pas le resultat, seulement les flags. C'est en tous cas comme cela qu'était documentée l'instruction SUB dans les manuels Intel il y a une vingtaine d'années.
C'est bien sûr l'instruction CMP qui documente cela.
Oui bien sûr, un lapsus tecladi.
En fait, aujourd'hui il ne stocke pas les flags (qui ne sont plus implantés comme tel ; je me demande d'ailleurs depuis combien de temps les flags sont virtuels dans le silicium, à mon avis cela remonte au 486 : les temps d'exécution de POPF etc. y sont supérieurs à ceux du 386) : il garde seulement la trace des informations nécessaires pour les recalculer le cas échéant. Ainsi si ton CMP est suivi d'un JNE, le flag ZF sera évalué mais le processeur va faire l'économie du calcul de AF, PF et compagnie. Ainsi, il y a eu des annonces de corrections de défaut (errata) concernant des « oublis » de traitement de certains flags dans des cas tordus (genre entrée en mode SMM avec une interruption au milieu).
Tu as raison, mais un peu d'historique correspondait mieux à la question de l'OP.
Les arcanes de l'implementation des processeurs modernes sont au langage machine ce que l'assembleur est au C, ce que le C est au javascript, etc. L'informatique moderne, c'est comme peler un oignon, tu crois avoir enfin enlevé la peau et compris comment ça fonctionne à l'intérieur, et hop il y a encore une couche en dessous... et une complexité insoupçonnée, on en chialerait.
char buf[20]; some_func(buf); if (!strcmp(buf, "err")) return -1;
Le compilateur sait que buf et "err" sont correctement alignés puisqu'il qu'il préside à leur allocation (respectivement dans la pile et un segment de données). Sur une machine 32 bits, il pourrait générer le code quasi-optimal suivant:
if (*(uint32_t*)buf == *(uint32_t)"err") return -1;
Je ne sais pas si d'aucuns compilateurs font cela correctement,
Je ne serais pas surpris qu'ils le fassent, mais je ne vais pas aller vérifier. Il faut dependant noter que cette optimisation n'est possible que parce que "err" compte 4 caractères, cela ne marcherait plus avec 3, 5, 6 ou 7 (OK, cela /peut/ marcher avec 5 sur votre espèce de @[*! de TMS et ses long sur 40 bits réels).
En fait cela peut marcher aussei pour d'autres longueurs, par exemple :
if (((*(uint32_t)buf ^ *(uint32_t)"OK ") & MASK_3_BYTES) == 0)
avec MASK_3_BYTES qui vaut 0xffffff00 en big endian, et 0xffffff en little endian.
Avec des tas de variantes possibles pour differentes tailles et en fonction de l'architecture ciblée.
La vraie contrainte, c'est plutot l'alignement de buf sur les architectures ou c'est nécessaire.
En fait, j'ai déjà vu sur des machines gros-boutiennes (donc pas x86) des comparaisons de chaînes implémentées (y compris le signe) comme des comparaisons d'entiers, donc l'équivalent de strcmp remplacé par un CMP.l dans une boucle serrée ! Cela fonctionnait parce que sur une machine gros boutienne la comparaison des entiers opère de la même manière que la comparaison des chaînes (même opération d'ordre des suites), et parce que dans ce cas-là les fins de chaîne étaient toujours remplies à 0 au-delà du
"Antoine Leca" <root@localhost.invalid> a écrit dans le message de news:
g7gt6i$jra$1@shakotay.alphanet.ch...
En news:g7g96t$n8s$1@registered.motzarella.org, Charlie Gordon va
escriure:
candide a écrit dans le message de news:
489b67ef$0$19939$426a74cc@news.free.fr...
Donc pour reformuler la question, comment un processeur est-il
programmé pour comparer des entiers ? fait-il une soustraction puis
examine t-il un bit de signe ? Ou alors, procède-t-il justement
lexicographiquement ? Pi si ça se trouve ça va dépendre des
processeurs.
Dans le cas des x86, le CPU fait effectivement la soustraction mais ne
stocke pas le resultat, seulement les flags. C'est en tous cas comme
cela qu'était documentée l'instruction SUB dans les manuels Intel il
y a une vingtaine d'années.
C'est bien sûr l'instruction CMP qui documente cela.
Oui bien sûr, un lapsus tecladi.
En fait, aujourd'hui il ne stocke pas les flags (qui ne sont plus
implantés
comme tel ; je me demande d'ailleurs depuis combien de temps les flags
sont
virtuels dans le silicium, à mon avis cela remonte au 486 : les temps
d'exécution de POPF etc. y sont supérieurs à ceux du 386) : il garde
seulement la trace des informations nécessaires pour les recalculer le cas
échéant.
Ainsi si ton CMP est suivi d'un JNE, le flag ZF sera évalué mais le
processeur va faire l'économie du calcul de AF, PF et compagnie.
Ainsi, il y a eu des annonces de corrections de défaut (errata) concernant
des « oublis » de traitement de certains flags dans des cas tordus (genre
entrée en mode SMM avec une interruption au milieu).
Tu as raison, mais un peu d'historique correspondait mieux à la question de
l'OP.
Les arcanes de l'implementation des processeurs modernes sont au langage
machine ce que l'assembleur est au C, ce que le C est au javascript, etc.
L'informatique moderne, c'est comme peler un oignon, tu crois avoir enfin
enlevé la peau et compris comment ça fonctionne à l'intérieur, et hop il y a
encore une couche en dessous... et une complexité insoupçonnée, on en
chialerait.
char buf[20];
some_func(buf);
if (!strcmp(buf, "err"))
return -1;
Le compilateur sait que buf et "err" sont correctement alignés
puisqu'il qu'il préside à leur allocation (respectivement dans la
pile et un segment de données). Sur une machine 32 bits, il pourrait
générer le code quasi-optimal suivant:
if (*(uint32_t*)buf == *(uint32_t)"err")
return -1;
Je ne sais pas si d'aucuns compilateurs font cela correctement,
Je ne serais pas surpris qu'ils le fassent, mais je ne vais pas aller
vérifier.
Il faut dependant noter que cette optimisation n'est possible que parce
que
"err" compte 4 caractères, cela ne marcherait plus avec 3, 5, 6 ou 7 (OK,
cela /peut/ marcher avec 5 sur votre espèce de @[*! de TMS et ses long sur
40 bits réels).
En fait cela peut marcher aussei pour d'autres longueurs, par exemple :
if (((*(uint32_t)buf ^ *(uint32_t)"OK ") & MASK_3_BYTES) == 0)
avec MASK_3_BYTES qui vaut 0xffffff00 en big endian, et 0xffffff en little
endian.
Avec des tas de variantes possibles pour differentes tailles et en fonction
de l'architecture ciblée.
La vraie contrainte, c'est plutot l'alignement de buf sur les architectures
ou c'est nécessaire.
En fait, j'ai déjà vu sur des machines gros-boutiennes (donc pas x86) des
comparaisons de chaînes implémentées (y compris le signe) comme des
comparaisons d'entiers, donc l'équivalent de strcmp remplacé par un CMP.l
dans une boucle serrée ! Cela fonctionnait parce que sur une machine gros
boutienne la comparaison des entiers opère de la même manière que la
comparaison des chaînes (même opération d'ordre des suites), et parce que
dans ce cas-là les fins de chaîne étaient toujours remplies à 0 au-delà du
nécessaire (autrement dit, le même truc ne marcherait en C _que_ si la
longueur d'une des chaînes est connue et multiple de la taille des entiers
machine, retour à le remarque ci-dessus).
En fait c'est le meme probleme qu'en little endian, mais en big endian on
peut calculer aussi le signe de strcmp avec une comparaison non signee
d'entiers. Pour traiter le cas des chaines non paddees, on peut toujours
masquer.
Cependant il y a un vrai piege : CMP.l permettra de brancher correctement
avec l'equivalent d'un JC, JNC, JBE, JAE et bien sûr JE et JNE, mais SUB.l
ne donnera pas directement le bon resultat pour strcmp() à cause de
l'overflow (par exemple pour strcmp("377377377", "