ProgrammationDéveloppeur système en C

Expliquez les particularités de la conversion de types entre les nombres signés et non signés (signed/unsigned) en C, quels pièges peuvent survenir, comment éviter des conséquences inattendues lors des opérations arithmétiques et des comparaisons entre les types signés et non signés, et comment cela influence la portabilité des programmes ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En C, la conversion automatique des types fonctionne selon le principe des "usages arithmétiques habituels". Lorsqu'un opérande signé et un opérande non signé sont impliqués dans une expression, les règles de conversion suivantes s'appliquent :

  • Si l'un des opérandes est non signé et l'autre est signé, le signed est converti en unsigned automatiquement.
  • Cela peut entraîner un débordement inattendu, en particulier lors de comparaisons ou d'opérations arithmétiques.
  • La taille des types influence également : si l'unsigned est de plus grande largeur, le signed est converti en unsigned.

Exemple de programmation dangereuse :

int a = -1; // signé unsigned int b = 1; printf("%d\n", a < b); // toujours faux, car a est converti en un très grand unsigned

Le résultat : -1, lorsqu'il est converti en unsigned, devient un nombre positif très grand.

Ce qu'il est important de retenir :

  • Toujours effectuer des conversions de type explicites s'il existe une ambiguïté concernant le signe.
  • Faire attention aux tailles des types (int, long, uint32_t, etc.) pour que les conversions se produisent de manière prévisible.
  • Séparer la logique qui concerne les variables signées et non signées, en particulier lors des vérifications limites et des opérations arithmétiques.

Question piégeuse

Question : Quel résultat l'expression (int)(unsigned)-1 renverra-t-elle ?

Réponse attendue incorrecte : "-1, car -1 doit être converti en int."

Réponse correcte : Dans l'expression (unsigned)-1, -1 est d'abord converti en unsigned (sur une plate-forme 32 bits, cela correspond à 0xFFFFFFFF), puis à nouveau en signed int, ce qui dépend également de l'implémentation, mais souvent cela donnera à nouveau -1 (si l'on utilise le complément à 2). Cependant, il est plus précis de dire : Le résultat dépend des normes de représentation des nombres signés, mais dans la plupart des implémentations, cela donnera -1.

Exemple :

int x = (int)(unsigned)-1; // x == -1 sur la plupart des plates-formes

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet


Histoire

Dans un gestionnaire de chaînes, une fonction de comparaison de taille était utilisée : si la longueur de la chaîne pouvait être négative, le programme signalait une erreur. Cependant, la longueur était de type size_t (non signé), et la comparaison avec if(length < 0) retourne toujours faux, ce qui a conduit à une boucle infinie et à un débordement de mémoire.


Histoire

Lors de l'analyse des protocoles, les paquets réseau contenaient des champs non signés, tandis que les variables locales étaient signées. En raison du débordement des non signés lors du traitement de certaines valeurs, des calculs incorrects de la longueur des paquets se sont produits, ce qui a conduit à une vulnérabilité de débordement de tampon.


Histoire

Le module de comparaison de dates dans les journaux stockait la date en tant qu'unsigned int, mais cherchait à vérifier un intervalle de dates en int. Certaines valeurs limites, au lieu de déclencher l'exception attendue, ont conduit à un filtrage incorrect des enregistrements et à la perte de journaux importants.