76 votes

"strlen(s1) - strlen(s2)" n'est jamais inférieur à zéro

Je suis actuellement 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 renvoie vrai même lorsque s1 a une longueur plus courte que s2. Quelqu'un pourrait-il s'il vous plaît expliquer ce comportement étrange?

174voto

Alex Lockwood Points 31578

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.

25voto

pmg Points 52636

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

Donc,

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

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

1voto

Mr Fooz Points 21092

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

Parfois, il peut être judicieux de convertir explicitement en une forme signée de size_t: ptrdiff_t, par exemple.

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

Si vous le faites, assurez-vous que la valeur size_t rentre dans un ptrdiff_t (qui a un bit de mantisse en moins).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X