“Strlen (s1) – strlen (s2)” n’est jamais inférieur à zéro

Je suis en train d’écrire un programme C qui nécessite des comparaisons fréquentes de longueurs de chaînes, j’ai donc écrit la fonction d’aide suivante:

int strlonger(char *s1, char *s2) { return strlen(s1) - strlen(s2) > 0; } 

J’ai remarqué que la fonction retourne vrai même si s1 a une longueur plus courte que s2 . Quelqu’un peut-il s’il vous plaît expliquer ce comportement étrange?

Ce que vous avez rencontré, c’est un comportement particulier qui se présente dans C lors du traitement d’expressions contenant des quantités signées et non signées.

Lorsqu’une opération est effectuée lorsque l’un des opérandes est signé et l’autre non signé, C convertit implicitement l’argument signé en non signé et exécute les opérations en supposant que les nombres ne sont pas négatifs. Cette convention conduit souvent à un comportement non intuitif pour les opérateurs relationnels tels que < et > .

En ce qui concerne 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 l'arithmétique non signée. Lorsque s1 est plus court que s2 , la différence strlen(s1) - strlen(s2) doit être négative, mais devient un grand nombre non signé, supérieur à 0 . Ainsi,

 return strlen(s1) - strlen(s2) > 0; 

renvoie 1 même si s1 est plus court que s2 . Pour corriger votre fonction, utilisez plutôt ce code:

 return strlen(s1) > strlen(s2); 

Bienvenue dans le monde merveilleux de C! 🙂


Exemples supplémentaires

Étant donné que cette question a récemment reçu beaucoup d’attention, je voudrais vous donner quelques exemples (simples), simplement pour vous assurer que l’idée est bien comprise. Je vais supposer que nous travaillons avec une machine 32 bits en utilisant la représentation du complément à deux.

Le concept important à comprendre lorsque l'on travaille avec des variables non signées / signées dans C est que, s'il y a un mélange de quantités non signées et signées dans une seule expression, les valeurs signées sont implicitement converties en non signées .

Exemple 1:

Considérons l'expression suivante:

 -1 < 0U 

Comme le deuxième opérande est non signé, le premier est implicitement converti en unsigned, et donc l'expression est équivalente à la comparaison,

 4294967295U < 0U 

ce qui est bien sûr faux. Ce n'est probablement pas le comportement que vous attendiez.

Exemple # 2:

Considérons le code suivant qui tente de sumr les éléments d'un tableau a , où le nombre d'éléments est donné par la length paramètre:

 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 la facilité avec laquelle des bogues peuvent survenir en raison de la diffusion implicite de signée à non signée. Il semble tout à fait naturel de passer la length paramètre comme non signé; après tout, qui voudrait utiliser une longueur négative? Le critère d'arrêt i <= length-1 semble également assez intuitif. Cependant, lorsqu'elle est exécutée avec une length argument égale à 0 , la combinaison de ces deux éléments produit un résultat inattendu.

Comme la length paramètre n'est pas signée, le calcul 0-1 est effectué en utilisant une arithmétique non signée, ce qui équivaut à une addition modulaire. Le résultat est alors UMax . La comparaison <= est également effectuée en utilisant une comparaison non signée, et comme tout nombre est inférieur ou égal à UMax , la comparaison est toujours valable. Ainsi, le code tentera d’accéder aux éléments non valides du tableau a .

Le code peut être corrigé soit en déclarant que la length est un int , soit en modifiant le test de la boucle for pour qu'il soit i < length .

Conclusion: quand faut-il utiliser Unsigned?

Je ne veux rien dire de trop controversé ici, mais voici certaines des règles auxquelles j'adhère souvent lorsque j'écris des programmes en C.

  • NE PAS 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 n ° 2).

  • DOIT utiliser lors de l'exécution de l'arithmétique modulaire.

  • Utilisez lorsque vous utilisez des bits pour représenter des ensembles. Ceci est souvent pratique car il vous permet d'effectuer des changements logiques corrects sans extension de signe.

Bien sûr, il peut y avoir des situations dans lesquelles vous décidez d'aller à l'encontre de ces "règles". Mais le plus souvent, suivre ces suggestions rendra votre code plus facile à utiliser et moins sujet aux erreurs.

strlen renvoie un size_t qui est un typedef pour un type unsigned .

Alors,

 (unsigned) 4 - (unsigned) 7 == (unsigned) - 3 

Toutes les valeurs unsigned sont supérieures ou égales à 0 . Essayez de convertir les variables renvoyées par strlen en long int .

La réponse d’ Alex Lockwood est la meilleure solution (sémantique compacte, claire, etc.).

Parfois, il est logique de convertir explicitement une forme signée de size_t : ptrdiff_t , par exemple

 return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0; 

Si vous faites cela, vous devez être certain que la valeur size_t rentre dans un ptrdiff_t (qui contient un nombre de bits de mantisse inférieur).