Vous avez rencontré un comportement particulier qui se produit en C lors du traitement d'expressions contenant à la fois des quantités signées et non signées.
Lorsqu'une opération est effectuée avec un opérande signé et l'autre non signé, C convertira implicitement l'argument signé en non signé et effectuera les opérations en supposant que les nombres sont non négatifs. Cette convention conduit souvent à un comportement non intuitif pour les opérateurs relationnels tels que <
et >
.
Concernant votre fonction d'aide, notez que puisque strlen
renvoie le type size_t
(une quantité non signée), la différence et la comparaison sont toutes deux calculées en utilisant des opérations arithmétiques non signées. Lorsque s1
est plus court que s2
, la différence strlen(s1) - strlen(s2)
devrait être négative, mais devient plutôt un grand nombre non signé, qui est supérieur à 0
. Ainsi,
return strlen(s1) - strlen(s2) > 0;
retourne 1
même si s1
est plus court que s2
. Pour corriger votre fonction, utilisez ce code à la place:
return strlen(s1) > strlen(s2);
Bienvenue dans le merveilleux monde de C! :)
Exemples Supplémentaires
Comme cette question a récemment reçu beaucoup d'attention, j'aimerais fournir quelques exemples (simples) pour m'assurer que j'explique bien l'idée. Je vais supposer que nous travaillons avec une machine 32 bits utilisant une représentation en complément à deux.
Le concept important à comprendre lorsque l'on travaille avec des variables signées/non signées en C est que s'il y a un mélange de quantités signées et non signées dans une seule expression, les valeurs signées sont converties implicitement en non signées.
Exemple #1:
Considérez l'expression suivante:
-1 < 0U
Étant donné que le deuxième opérande est non signé, le premier est coulé implicitement en non signé, et donc l'expression est équivalente à la comparaison,
4294967295U < 0U
ce qui est bien sûr faux. Ce n'est probablement pas le comportement auquel vous vous attendiez.
Exemple #2:
Considérez le code suivant qui tente de sommer les éléments d'un tableau a
, où le nombre d'éléments est donné par le paramètre length
:
int sum_array_elements(int a[], unsigned length) {
int i;
int result = 0;
for (i = 0; i <= length-1; i++)
result += a[i];
return result;
}
Cette fonction est conçue pour démontrer comment les bugs peuvent facilement survenir en raison de la conversion implicite de signé à non signé. Il semble tout à fait naturel de passer le paramètre length
en tant que non signé; après tout, qui voudrait jamais utiliser une longueur négative? Le critère d'arrêt i <= length-1
semble également tout à fait intuitif. Cependant, lorsqu'il est exécuté avec l'argument length
égal à 0
, la combinaison de ces deux éléments produit un résultat inattendu.
Étant donné que le paramètre length
est non signé, le calcul 0-1
est effectué en utilisant une arithmétique non signée, ce qui est équivalent à une addition modulaire. Le résultat est alors UMax. La comparaison <=
est également effectuée en utilisant une comparaison non signée, et étant donné que tout nombre est inférieur ou égal à UMax, la comparaison est toujours vraie. Ainsi, le code va tenter d'accéder à des éléments invalides du tableau a
.
Le code peut être corrigé soit en déclarant length
en tant qu'int
, soit en changeant le test de la boucle for
en i < length
.
Conclusion: Quand Devriez-Vous Utiliser Non Signé?
Je ne veux pas avancer quelque chose de trop controversé ici, mais voici quelques règles auxquelles j'adhère souvent lorsque j'écris des programmes en C.
-
ÉVITEZ d'utiliser juste parce qu'un nombre est non négatif. Il est facile de faire des erreurs, et ces erreurs sont parfois incroyablement subtiles (comme illustré dans l'Exemple #2).
-
UTILISEZ lorsque vous effectuez une arithmétique modulaire.
-
UTILISEZ lorsque vous utilisez des bits pour représenter des ensembles. C'est souvent pratique car cela vous permet d'effectuer des décalages logiques vers la droite sans extension de signe.
Il peut bien sûr y avoir des situations dans lesquelles vous décidez de ne pas suivre ces "règles". Mais le plus souvent, en suivant ces suggestions, votre code sera plus facile à travailler et moins sujet aux erreurs.