Comment le compilateur c gère-t-il les entiers non signés et signés? Pourquoi le code d’assemblage pour l’opération arithmétique non signée et signée est-il le même?

Je lis le livre: CS-APPe2. C a un type int non signé et signé et, dans la plupart des architectures, utilise l’arithmétique du complément de deux pour implémenter la valeur signée; mais après avoir appris un code d’assemblage, j’ai constaté que très peu d’instructions distinguent entre non signé et signé. Donc ma question est:

  1. Est-ce la responsabilité du compilateur de différencier signé et non signé? Si oui, comment fait-il cela?

  2. Qui implémente l’arithmétique du complément à deux – le processeur ou le compliant?

Ajouter quelques informations supplémentaires:

Après avoir appris quelques instructions supplémentaires, certaines d’entre elles différencient signées et non signées, telles que setg, seta, etc. De plus, CF et OF s’appliquent aux non-signés et respectivement. Mais la plupart des instructions arithmétiques entières traitent les signes non signé et signé de la même manière, par exemple

int s = a + b 

et

 unsigned s = a + b 

générer la même instruction.

Ainsi, lors de l’exécution de ADD sd , le processeur doit-il traiter les significations et signatures non signées ou signées? Ou bien ce n’est pas pertinent, parce que le modèle de bits des deux résultats est identique et que la tâche du compilateur est de convertir le résultat du modèle de bits sous-jacent en non signé ou signé?

PS j’utilise x86 et gcc

C’est assez facile. Des opérations telles que l’addition et la soustraction ne nécessitent aucun ajustement pour les types signés dans l’arithmétique du complément à deux. Il suffit de faire une expérience mentale et d’imaginer un algorithme utilisant uniquement les opérations mathématiques suivantes:

  • incrémenter d’un
  • décrémenter d’un
  • comparer avec zéro

L’addition consiste simplement à prendre des éléments un par un dans un tas et à les placer dans l’autre tas jusqu’à ce que le premier soit vide. La soustraction consiste à prendre les deux en même temps, jusqu’à ce que celui soustrait soit vide. En arithmétique modulaire, il suffit de traiter la plus petite valeur comme la plus grande valeur plus une et cela fonctionne. Le complément à deux est juste une arithmétique modulaire où la plus petite valeur est négative.

Si vous voulez voir une différence, je vous recommande d’essayer des opérations qui ne sont pas sûres en ce qui concerne le débordement. Un exemple est la comparaison ( a < b ).

Est-ce que le respectant l'obligation de différencier signé et non signé? Si oui, comment fait-il cela?

En générant un assemblage différent chaque fois que nécessaire.

Qui implémente l'arithmétique du complément à deux - le processeur ou le compliant?

C'est une question difficile. Le complément à deux est probablement la manière la plus naturelle de travailler avec des entiers négatifs dans un ordinateur. La plupart des opérations pour le complément à deux avec débordement sont les mêmes que pour les entiers non signés avec débordement. Le signe peut être extrait d'un seul bit. La comparaison peut être faite de manière conceptuelle par soustraction (ce qui est indépendant de la signature), extraction de bit de signe et comparaison à zéro.

Ce sont toutefois les fonctionnalités arithmétiques de la CPU qui permettent au compilateur de produire des calculs en complément de deux.

non signé s = a + b

Notez que la façon dont plus est calculé ici ne dépend pas du type du résultat. Cela dépend des types de variables à droite du signe égal.

Ainsi, lors de l'exécution de ADD sd, le processeur doit-il traiter les significations et signatures non signées ou signées?

Les instructions du processeur ne connaissent pas les types, ceux-ci ne sont utilisés que par le compilateur. En outre, il n'y a pas de différence entre l'ajout de deux numéros non signés et l'ajout de deux numéros signés. Il serait stupide d'avoir deux instructions pour la même opération.

Dans de nombreux cas, il n’ya pas de différence au niveau de la machine entre les opérations signées et non signées, et il s’agit simplement d’une question d’interprétation du modèle de bits. Par exemple, considérons l’opération de mot à 4 bits suivante:

 Binary Add Unsigned 2's comp ---------- -------- -------- 0011 3 3 + 1011 + 11 - 5 ------- -------- -------- 1110 14 -2 ------- -------- -------- 

Le modèle binary est le même pour l’opération signée et non signée. Notez que la soustraction est simplement l’addition d’une valeur négative. Lorsqu’une opération SUB est effectuée, l’opérande de droite est complémenté par deux (bits inversés et incrémentés) puis ajouté (le circuit ALU responsable est un additionneur ); pas au niveau d’instruction que vous comprenez, mais au niveau logique, bien qu’il soit possible d’implémenter une machine sans instruction SUB tout en effectuant une soustraction, bien que deux instructions au lieu d’une.

Certaines opérations nécessitent des instructions différentes selon le type, et il incombe au compilateur de générer le code approprié en général – des variantes architecturales peuvent s’appliquer.

Il n’est pas nécessaire de différencier les ints signés et non signés pour la plupart des opérations arithmétiques / logiques. Il suffit souvent de prendre en compte le signe lors de l’impression, de l’extension zéro / du signe qui étend ou de la comparaison de valeurs. En fait, le processeur ne sait rien du type d’une valeur. Une valeur de 4 octets est simplement une série de bits. Elle n’a aucune signification, sauf si l’utilisateur indique qu’il s’agit d’un float, d’un tableau de 4 caractères, d’un unsigned int ou d’un signé int, etc. Par exemple, lors de l’impression d’une variable char En fonction du type et des propriétés de sortie indiquées, il affichera le caractère, l’entier non signé ou l’entier signé. Il est de la responsabilité du programmeur de montrer au compilateur comment traiter cette valeur. Le compilateur émettra alors l’instruction correcte nécessaire pour traiter la valeur.

Cela m’a aussi dérangé pendant longtemps. Je n’ai pas appris à comprendre comment le compilateur fonctionne comme un programme tout en lui donnant ses valeurs par défaut et ses instructions implicites. Mais ma recherche d’une réponse m’a amené aux conclusions suivantes:

Le monde réel utilise uniquement des entiers signés, depuis la découverte des nombres négatifs. c’est la raison pour laquelle int est considéré par défaut comme un entier signé dans le compilateur. J’ignore totalement l’arithmétique des nombres non signés car elle est inutile.

La CPU n’a aucune idée des entiers signés et non signés. Il ne connaît que les bits – 0 et 1. La façon dont vous interprétez sa sortie dépend de vous, en tant que programmeur d’assemblage. Cela rend la programmation d’assemblage fastidieuse. Le traitement des nombres entiers (signé et non signé) impliquait beaucoup de vérification des drapeaux. C’est pourquoi les langages de haut niveau ont été développés. le compilateur enlève toute la douleur.

Comment fonctionne le compilateur est un apprentissage très avancé. J’ai accepté qu’actuellement, cela dépasse mon entendement. Cette acceptation m’a aidé à avancer dans mon parcours.

En architecture x86:

les instructions add et sub modifient les indicateurs dans le registre eflags. Ces indicateurs peuvent ensuite être utilisés conjointement avec les instructions adc et sbb pour créer une arithmétique plus précise. Dans ce cas, nous déplaçons la taille des nombres dans le registre ecx. Le nombre de fois où l’instruction de boucle est exécutée est identique à la taille des nombres en octets.

La sous-instruction prend le complément à 2 de la soustraction, l’ajoute à la minuend, inverse le report. Ceci est fait dans le matériel (implémenté dans le circuit). La sous-instruction “active” un circuit différent. Après avoir utilisé la sous-instruction, le programmeur ou le compilateur vérifie le CF. Si la valeur est 0, le résultat est positif et le résultat de la destination est correct. Si c’est 1, le résultat est négatif et la destination a le complément à 2 du résultat. Normalement, le résultat est laissé dans le complément à 2 et lu comme un nombre signé, mais les instructions NOT et INC peuvent être utilisées pour le changer. L’instruction NOT exécute le complément à 1 de l’opérande, puis l’opérande est incrémenté pour obtenir le complément à 2.

Lorsqu’un programmeur a prévu de lire le résultat d’une instruction d’ajout ou de sous-instruction sous forme d’un numéro signé, il surveillera le drapeau OF. Si elle est définie sur 1, le résultat est faux. Il devrait signer-étendre les numéros avant d’exécuter l’opération entre eux.

On a beaucoup parlé de votre première question, mais j’aime bien dire quelque chose à propos de votre deuxième:

Qui implémente l’arithmétique du complément à deux – le processeur ou le compliant?

Le standard C n’exige pas que les nombres négatifs aient un complément de deux, il ne définit pas du tout comment le matériel exprime les nombres négatifs. La tâche du compilateur est de traduire votre code C en instructions de la CPU qui répondent à vos demandes de code. Ainsi, le fait que le compilateur C crée ou non un code pour l’arithmétique du complément à deux dépend uniquement du fait que votre CPU utilise ou non l’arithmétique du complément à deux. Le compilateur doit savoir comment la CPU fonctionne et créer du code en conséquence. Donc, la réponse correcte à cette question est: le processeur.

Si votre processeur utilisait une représentation en complément, alors un compilateur C pour ce processeur émettrait des instructions en complément. D’autre part, un compilateur C peut émuler le support des nombres négatifs sur une CPU qui ne connaît pas du tout les nombres négatifs. Comme le complément à deux vous permet d’ignorer si un nombre est signé ou non pour de nombreuses opérations, cela n’est pas trop difficile à faire. Dans ce cas, le compilateur aurait mis en œuvre l’arithmétique du complément à deux. Cela pourrait également être fait sur un CPU qui a une représentation pour les nombres négatifs, mais pourquoi le compilateur devrait-il le faire et ne pas utiliser simplement la forme native que le CPU comprend? Donc, il ne le fera pas à moins d’y être obligé.