Lorsqu'un programme C est en cours d'exécution, les données sont stockées sur le tas ou la pile. Les valeurs sont stockées dans des adresses RAM. Mais qu'en est-il des indicateurs de type (par exemple, int
ou char
)? Sont-ils également stockés?
Considérons le code suivant:
char a = 'A';
int x = 4;
J'ai lu que A et 4 sont stockés dans des adresses RAM ici. Mais qu'en est-il a
et x
? Le plus déroutant, comment l'exécution sait-elle qu'il a
s'agit d'un caractère et d' x
un entier? Je veux dire, le int
et char
mentionné quelque part dans la RAM?
Supposons qu'une valeur est stockée quelque part dans la RAM sous la forme 10011001; si je suis le programme qui exécute le code, comment saurai-je si ce 10011001 est un char
ou un int
?
Ce que je ne comprends pas, c’est ce que l’ordinateur sait quand il lit la valeur d’une variable à partir d’une adresse telle que 10001, que ce soit un int
ou char
. Imaginez que je clique sur un programme appelé anyprog.exe
. Immédiatement, le code commence à s'exécuter. Ce fichier exécutable contient-il des informations indiquant si les variables stockées sont du type int
ou char
?
x
soit un caractère, mais c'est le code d'impression de caractère qui est exécuté, parce que c'est ce que le compilateur a sélectionné.Réponses:
Pour répondre à la question que vous avez postée dans plusieurs commentaires (que je pense que vous devriez éditer dans votre post):
Alors mettons un peu de code dessus. Disons que vous écrivez:
Et supposons qu'il soit stocké dans la RAM:
La première partie étant l'adresse, la deuxième partie étant la valeur. Lorsque votre programme (qui s’exécute en tant que code machine) s’exécute, il ne voit que
0x00010004
la valeur0x000000004
. Il ne «connaît» pas le type de ces données et ne sait pas comment il est «supposé» être utilisé.Alors, comment votre programme détermine-t-il la bonne chose à faire? Considérons ce code:
Nous avons une lecture et une écriture ici. Lorsque votre programme lit en
x
mémoire, il le trouve0x00000004
. Et votre programme sait y ajouter0x00000005
quelque chose. Et la raison pour laquelle votre programme "sait" qu'il s'agit d'une opération valide, c'est parce que le compilateur s'assure que l'opération est valide par le biais de la sécurité de type. Votre compilateur a déjà vérifié que vous pouvez ajouter4
et5
ensemble. Ainsi, lorsque votre code binaire s'exécute (le fichier exe), il n'a pas à effectuer cette vérification. Il exécute chaque étape à l'aveuglette, en supposant que tout va bien (les mauvaises choses arrivent lorsqu'elles le sont, en fait, ce n'est pas OK).Une autre façon de penser est comme ça. Je vous donne cette information:
Même format que précédemment - adresse à gauche, valeur à droite. De quel type est la valeur? À ce stade, vous connaissez autant d'informations sur cette valeur que votre ordinateur en exécutant du code. Si je vous disais d'ajouter 12743 à cette valeur, vous pourriez le faire. Vous ne savez pas quelles seront les répercussions de cette opération sur l'ensemble du système, mais vous êtes vraiment doué pour ajouter deux chiffres, vous pouvez donc le faire. Est-ce que cela fait de la valeur un
int
? Pas nécessairement - Tout ce que vous voyez est deux valeurs 32 bits et l'opérateur d'addition.Peut-être qu'une partie de la confusion est alors de récupérer les données. Si nous avons:
Comment l'ordinateur sait-il s'afficher
a
dans la console? Eh bien, il y a beaucoup d'étapes à suivre. La première consiste à aller à l’A
emplacement de s en mémoire et à le lire:La valeur hexadécimale correspondant
a
à ASCII est 0x61. Il est donc possible que ce qui précède apparaisse dans la mémoire. Alors maintenant, notre code machine connaît la valeur entière. Comment sait-il transformer la valeur entière en un caractère pour l'afficher? En bref, le compilateur s’est assuré de mettre en place toutes les étapes nécessaires à cette transition. Mais votre ordinateur lui-même (ou le programme / exe) n'a aucune idée du type de ces données. Cette valeur de 32 bits peut être n'importe quoi -int
, lachar
moitié d'undouble
, un pointeur, une partie d'un tableau, unestring
partie, une partie d'une instruction, etc.Voici une brève interaction de votre programme (exe) avec l’ordinateur / le système d’exploitation.
Programme: Je veux commencer. J'ai besoin de 20 Mo de mémoire.
Système d'exploitation: trouve 20 Mo de mémoire libre qui ne sont pas utilisés et les remet
(La remarque importante est que cela pourrait revenir tout 20 Mo de mémoire, ils ne sont même pas être contigus. À ce stade, le programme peut maintenant fonctionner dans la mémoire , il a sans parler à l'OS)
Programme: Je vais supposer que la première place en mémoire est une variable entière de 32 bits
x
.(Le compilateur s'assure que les accès aux autres variables ne toucheront jamais cet emplacement en mémoire. Rien dans le système ne dit que le premier octet est variable
x
, ou que cette variablex
est un entier. Une analogie: vous avez un sac. Vous dites aux gens que vous ne mettrez que des balles de couleur jaune dans ce sac. Lorsque quelqu'un retirera quelque chose du sac, il serait choquant de pouvoir sortir quelque chose de bleu ou un cube - quelque chose a mal tourné. Il en va de même pour les ordinateurs: votre programme suppose maintenant que le premier emplacement de mémoire est une variable x et qu’il s’agit d’un entier. Si quelque chose d’autre est écrit sur cet octet de mémoire ou si on suppose qu’il est autre chose, il s’est passé quelque chose d’horrible. Le compilateur assure ce genre de choses pas arriver)Programme: J'écrirai maintenant
2
les quatre premiers octets où je suppose enx
être à.Programme: Je veux ajouter 5 à
x
.Lit la valeur de X dans un registre temporaire
Ajoute 5 au registre temporaire
Stocke la valeur du registre temporaire dans le premier octet, qui est toujours supposé être
x
.Programme: Je vais supposer que le prochain octet disponible est la variable char
y
.Programme: je vais écrire
a
à variabley
.Une bibliothèque est utilisée pour trouver la valeur d'octet pour
a
L'octet est écrit à l'adresse que le programme suppose
y
.Programme: Je veux afficher le contenu de
y
Lit la valeur dans le deuxième emplacement mémoire
Utilise une bibliothèque pour convertir d'octet en caractère
Utilise des bibliothèques graphiques pour modifier l'écran de la console (définition des pixels du noir au blanc, défilement d'une ligne, etc.)
(Et ça continue d'ici)
Ce qui vous préoccupe probablement, c’est que se passe-t-il lorsque la première place dans la mémoire n’est plus
x
? ou la seconde n'est plusy
? Qu'est - ce qui se passe quand quelqu'un litx
unchar
ouy
comme un pointeur? En bref, de mauvaises choses arrivent. Certaines de ces choses ont un comportement bien défini, et d'autres ont un comportement indéfini. Un comportement indéfini est exactement cela: tout peut arriver, qu’il s’agisse de rien, de planter le programme ou le système d’exploitation. Même un comportement bien défini peut être malveillant. Si je peux changerx
pour un pointeur vers mon programme et obtenir que votre programme l'utilise comme pointeur, je peux le faire exécuter par mon programme - ce qui est exactement ce que font les pirates. Le compilateur est là pour nous aider à ne pas utiliserint x
commestring
et des choses de cette nature. Le code machine lui-même ne connaît pas les types et il ne fera que ce que les instructions lui indiquent. Il existe également une grande quantité d'informations découvertes au moment de l'exécution: quels octets de mémoire le programme est-il autorisé à utiliser?x
Commence- t-on au premier octet ou au 12?Mais vous pouvez imaginer à quel point il serait horrible d’écrire des programmes comme celui-ci (et vous pouvez le faire, en assembleur). Vous commencez par "déclarer" vos variables - vous vous dites que l'octet 1 est
x
, l'octet 2y
, et que vous écrivez chaque ligne de code, le chargement et le stockage des registres, vous (en tant qu'être humain) devez vous rappeler lequel estx
et lequel On esty
, parce que le système n'a aucune idée. Et vous (en tant qu'être humain) devez vous rappeler quels typesx
et quels typesy
car, encore une fois - le système n'a aucune idée.la source
Otherwise how can console or text file outputs a character instead of int
Parce qu'il existe une séquence d'instructions différente pour la sortie du contenu d'un emplacement de mémoire sous forme d'entier ou de caractères alphanumériques. Le compilateur connaît les types de variables, choisit la séquence d'instructions appropriée au moment de la compilation et l'enregistre dans le fichier EXE.Je pense que votre question principale semble être: "Si le type est effacé au moment de la compilation et n’est pas conservé au moment de l’exécution, comment l’ordinateur sait-il s’il faut exécuter du code qui l’interprète comme un
int
ou exécuter du code qui l’interprète comme unchar
? "Et la réponse est… l'ordinateur ne le fait pas. Cependant, le compilateur est au courant et il aura simplement placé le code correct dans le fichier binaire en premier lieu. Si la variable était saisie en tant que
char
, le compilateur ne mettrait pas le code pour le traiter en tant queint
dans le programme, il mettrait le code pour le traiter est unchar
.Il y a des raisons de conserver le type au moment de l'exécution:
+
opérateur), il n’a donc pas besoin du type d’exécution pour cette raison. Cependant, encore une fois, le type d’exécution est de toute façon différent du type statique. Par exemple, en Java, vous pouvez théoriquement effacer les types statiques tout en conservant le type d’exécution pour le polymorphisme. Notez également que si vous décentralisez et spécialisez le code de recherche de type et le placez à l'intérieur de l'objet (ou de la classe), vous n'avez pas nécessairement besoin du type d'exécution, par exemple C ++ vtables.La seule raison de conserver le type au moment de l'exécution en C serait pour le débogage. Toutefois, le débogage est généralement effectué avec la source disponible. Vous pouvez alors simplement rechercher le type dans le fichier source.
Le type effacement est tout à fait normal. Cela n'a pas d'impact sur la sécurité des types: les types sont vérifiés au moment de la compilation, une fois que le compilateur est convaincu que le programme est sécurisé au type, les types ne sont plus nécessaires (pour cette raison). Cela n'a pas d'impact sur le polymorphisme statique (surcharge): une fois la résolution de la surcharge terminée et le compilateur ayant sélectionné la surcharge appropriée, il n'a plus besoin des types. Les types peuvent également guider l'optimisation, mais là encore, une fois que l'optimiseur a sélectionné ses optimisations en fonction des types, il n'en a plus besoin.
La conservation des types au moment de l'exécution n'est requise que si vous souhaitez utiliser les types au moment de l'exécution.
Haskell est l’un des langages statiques typés les plus stricts, les plus rigoureux et les plus sécurisés, et les compilateurs Haskell effacent généralement tous les types. (L'exception étant le passage de dictionnaires de méthodes pour les classes de types, je crois.)
la source
char
dans le binaire compilé. Il ne sortint
pas le code pour unbyte
, il ne sort pas le code pour un , il ne sort pas le code pour un pointeur, il ne sort que le code pour unchar
. Aucune décision d'exécution n'est prise en fonction du type. Vous n'avez pas besoin du type. C'est complètement et totalement hors de propos. Toutes les décisions pertinentes ont déjà été prises au moment de la compilation.public class JoergsAwesomeNewType {};
Voir? Je viens d'inventer un nouveau type! Vous devez acheter un nouveau processeur!L'ordinateur ne "sait" pas quelles adresses sont quoi, mais la connaissance de ce qui est cuit dans les instructions de votre programme.
Lorsque vous écrivez un programme C qui écrit et lit une variable de caractère, le compilateur crée un code assembleur qui écrit cette donnée quelque part sous forme de caractère, et il existe un autre code ailleurs qui lit une adresse de mémoire et l'interprète en tant que caractère. La seule chose qui lie ces deux opérations est l’emplacement de cette adresse mémoire.
Quand vient le temps de lire, les instructions ne disent pas "voir quel type de données est là", il dit simplement quelque chose comme "charge cette mémoire en tant que float". Si l'adresse à lire a été modifiée ou si quelque chose a écrasé cette mémoire avec autre chose qu'un float, le processeur chargera simplement cette mémoire avec plaisir comme une float, et toutes sortes de choses étranges peuvent en résulter.
Mauvaise analogie: imaginez un entrepôt d’expédition complexe, où l’entrepôt est une mémoire et où les utilisateurs choisissent des choses, c’est le processeur. Une partie du programme de l'entrepôt place divers articles sur les étagères. Un autre programme va chercher des articles à l’entrepôt et les met dans des boîtes. Quand ils sont retirés, ils ne sont pas vérifiés, ils vont simplement à la poubelle. L’ensemble de l’entrepôt fonctionne de manière synchrone, les bons articles étant toujours au bon endroit au bon moment, sinon tout se bloque, comme dans un programme réel.
la source
Ce n'est pas. Une fois que C est compilé en code machine, la machine ne voit plus que quelques bits. La façon dont ces bits sont interprétés dépend des opérations effectuées sur ces bits, par opposition à des métadonnées supplémentaires.
Les types que vous entrez dans votre code source sont uniquement destinés au compilateur. Vous devez définir le type de données que vous supposez être et, dans la mesure du possible, essayer de vous assurer que ces données ne sont utilisées que de manière sensée. Une fois que le compilateur a effectué le travail le mieux possible en vérifiant la logique de votre code source, il le convertit en code machine et supprime les données de type, car le code machine n'a aucun moyen de le représenter (du moins sur la plupart des machines). .
la source
int a = 65
etchar b = 'A'
une fois le code compilé.La plupart des processeurs fournissent des instructions différentes pour travailler avec des données de types différents. Par conséquent, les informations de type sont généralement intégrées au code machine généré. Il n'est pas nécessaire de stocker des métadonnées de type supplémentaires.
Quelques exemples concrets pourraient aider. Le code machine ci-dessous a été généré à l'aide de gcc 4.1.2 sur un système x86_64 exécutant SuSE Linux Enterprise Server (SLES) 10.
Supposons le code source suivant:
Voici la viande du code d'assemblage généré correspondant à la source ci-dessus (en utilisant
gcc -S
), avec des commentaires ajoutés par moi:Il y a quelques éléments supplémentaires qui suivent
ret
, mais ils ne sont pas pertinents pour la discussion.%eax
est un registre de données à usage général 32 bits.%rsp
est un registre 64 bits réservé à la sauvegarde du pointeur de pile , qui contient l'adresse de la dernière chose déposée sur la pile.%rbp
est un registre de 64 bits réservé à la sauvegarde du pointeur de trame , qui contient l'adresse de la trame actuelle de la pile . Un cadre de pile est créé sur la pile lorsque vous entrez une fonction et il laisse de la place pour les arguments et les variables locales de la fonction. Les arguments et les variables sont accessibles en utilisant des décalages à partir du pointeur du cadre. Dans ce cas, la mémoire de la variablex
est de 12 octets "en dessous" de l'adresse stockée dans%rbp
.Dans le code ci-dessus, nous copions la valeur entière de
x
(1, stockée dans-12(%rbp)
) dans le registre%eax
à l'aide de l'movl
instruction, qui permet de copier des mots de 32 bits d'un emplacement à un autre. Nous appelons ensuiteaddl
, ce qui ajoute la valeur entière dey
(stocké à-8(%rbp)
) à la valeur déjà présente%eax
. Nous sauvegardons ensuite le résultat dans-4(%rbp)
, qui estz
.Modifions cela pour que nous ayons affaire à des
double
valeurs plutôt qu'à desint
valeurs:Courir à
gcc -S
nouveau nous donne:Plusieurs différences Au lieu de
movl
etaddl
, nous utilisonsmovsd
etaddsd
(assignons et ajoutons des flotteurs à double précision). Au lieu de stocker des valeurs intermédiaires%eax
, nous utilisons%xmm0
.C'est ce que je veux dire quand je dis que le type est "cuit" dans le code machine. Le compilateur génère simplement le bon code machine pour gérer ce type particulier.
la source
Historiquement , C considérait la mémoire comme composée d’un certain nombre de groupes d’emplacements numérotés de type
unsigned char
(également appelé "octet", bien que cela ne doive pas toujours être 8 bits). Tout code qui utilise quelque chose stocké en mémoire doit savoir quel est le ou les emplacements dans lesquels sont stockées les informations et savoir comment utiliser ces informations [par exemple, "interprète les quatre octets commençant à l'adresse 123: 456 comme un fichier 32 bits valeur en virgule flottante "ou" stocker les 16 bits inférieurs de la dernière quantité calculée en deux octets à partir de l'adresse 345: 678]. La mémoire elle-même ne saurait ni ce que se soucient de ce que les valeurs stockées dans les emplacements de mémoire "signifient". Si le code essayait d'écrire en utilisant un type de mémoire et de le lire comme un autre, les modèles de bits stockés par l'écriture seraient interprétés conformément aux règles du second type, avec les conséquences qui pourraient en résulter.Par exemple, si le code devait être stocké
0x12345678
sur un fichier 32 bitsunsigned int
, puis tenter de lire deuxunsigned int
valeurs consécutives de 16 bits à partir de son adresse et de l'adresse ci-dessus, puis en fonction de la moitié de celle-ciunsigned int
stockée, le code pourrait lire les valeurs. 0x1234 et 0x5678 ou 0x5678 et 0x1234.Toutefois, la norme C99 n’exige plus que la mémoire se comporte comme un groupe d’emplacements numérotés qui ne savent rien de ce que leurs modèles de bits représentent . Un compilateur est autorisé à se comporter comme si les emplacements de mémoire connaissaient les types de données qui y étaient stockés et autorisaient uniquement les données écrites sous un type autre que celui
unsigned char
à lire en utilisant le typeunsigned char
ou le même type tel qu'il avait été écrit. avec; les compilateurs sont en outre autorisés à se comporter comme si les emplacements mémoire avaient le pouvoir et la volonté de corrompre arbitrairement le comportement de tout programme qui tente d'accéder à la mémoire d'une manière contraire à ces règles.Donné:
certaines implémentations peuvent imprimer 0x1234, et d'autres imprimer 0x5678, mais en vertu de la norme C99, il serait légal pour une implémentation d'imprimer "FRINK RULES!" ou faire autre chose, en partant du principe qu'il serait légal que les emplacements de mémoire conservés
a
incluent du matériel enregistrant le type utilisé pour les écrire, et qu'un tel matériel réponde de quelque manière que ce soit à une tentative de lecture non valide, notamment en provoquant "RÈGLES FRINK!" être sortie.Notez que l’importation d’un tel matériel importe peu - le fait qu’un tel matériel puisse exister légalement rend légale la possibilité pour les compilateurs de générer du code qui se comporte comme s’il fonctionnait sur un tel système. Si le compilateur peut déterminer qu'un emplacement de mémoire particulier sera écrit comme un type et lu comme un autre, il peut prétendre qu'il fonctionne sur un système dont le matériel peut prendre une telle décision et qu'il peut réagir avec le degré de caprice voulu par l'auteur du compilateur. .
L'objectif de cette règle était de permettre aux compilateurs sachant qu'un groupe d'octets contenant une valeur d'un type avaient une valeur particulière à un moment donné, et qu'aucune valeur de ce même type n'avait été écrite depuis, pour en déduire que d'octets contiendrait toujours cette valeur. Par exemple, un processeur avait lu un groupe d’octets dans un registre, puis, après avoir voulu utiliser les mêmes informations pendant qu’il était encore dans le registre, le compilateur pouvait utiliser le contenu du registre sans avoir à relire la valeur à partir de la mémoire. Une optimisation utile. Violer cette règle pendant environ les dix premières années de la règle signifierait généralement que si une variable est écrite avec un type autre que celui utilisé pour la lire, l'écriture peut ou non affecter la valeur lue. Un tel comportement peut dans certains cas être désastreux, mais peut être inoffensif dans d’autres cas,
Vers 2009, cependant, les auteurs de compilateurs tels que CLANG ont déterminé que, puisque la norme permet aux compilateurs de faire ce qu’ils veulent dans les cas où la mémoire est écrite en utilisant un type et lue comme un autre, les compilateurs doivent en déduire que les programmes ne recevront jamais d’entrée provoquer une telle chose à se produire. Puisque la norme dit que le compilateur est autorisé à faire tout ce qu'il veut quand une telle entrée invalide est reçue, le code qui n'aurait d'effet que dans les cas où la norme n'impose aucune exigence peut (et de l'avis de certains auteurs du compilateur, devrait) être omis comme non pertinent. Cela modifie le comportement des violations de crénelage comme une mémoire, laquelle, avec une demande de lecture, peut renvoyer arbitrairement la dernière valeur écrite en utilisant le même type qu'une demande de lecture ou toute valeur plus récente écrite en utilisant un autre type,
la source
int x,y,z;
l'expression,x*y > z
il ne ferait jamais autre chose que de renvoyer 1 ou 0, ou dont les violations d'alias auraient un effet quelconque. autre que de laisser le compilateur renvoyer arbitrairement une ancienne ou une nouvelle valeur.unsigned char
valeurs utilisées pour construire un type "viennent de". Si un programme décompose un pointeur en ununsigned char[]
fichier, affiche brièvement son contenu hexagonal à l'écran, puis efface le pointeur, leunsigned char[]
, puis accepte certains numéros hexadécimaux du clavier, les recopie dans un pointeur, puis déréférence ce pointeur. , le comportement serait bien défini dans le cas où le numéro saisi correspondait au numéro affiché.En C, ça ne l'est pas. D'autres langues (par exemple, Lisp, Python) ont des types dynamiques mais C est typé de manière statique. Cela signifie que votre programme doit savoir quel type de données doit être interprété correctement: un caractère, un entier, etc.
Généralement, le compilateur s'en occupe pour vous et si vous faites quelque chose de mal, vous obtiendrez une erreur de compilation (ou un avertissement).
la source
10001
. C’est votre cas ou celui du compilateur , selon le cas, de vous tenir au courant de ce genre de choses manuellement lors de l’écriture du code machine ou assembleur.Vous devez faire la distinction entre
compiletime
etruntime
d’une partcode
etdata
de l’autre.Du point de vue de la machine , il n'y a pas de différence entre ce que vous appelez
code
ouinstructions
et ce que vous appelezdata
. Tout se résume aux chiffres. Mais certaines séquences - ce que nous appellerionscode
- font quelque chose que nous trouvons utile, d’autres seraient simplementcrash
la machine.Le travail effectué par la CPU est une simple boucle en 4 étapes:
instruction
)C'est ce qu'on appelle le cycle d'instruction .
a
etx
sont des variables, qui sont des espaces réservés pour les adresses, où le programme pourrait trouver le "contenu" des variables. Donc, chaque fois que la variablea
est utilisée, il y a effectivement l'adresse du contenua
utilisé.L'exécution ne sait rien. D'après ce qui a été dit dans l'introduction, la CPU récupère uniquement les données et interprète ces données comme des instructions.
La fonction printf est conçue pour "connaître" le type d'entrée que vous y mettez, c'est-à-dire que son code résultant donne les bonnes instructions pour gérer un segment de mémoire spécial. Bien sûr, il est possible de générer une sortie non-sens: utiliser une adresse dans laquelle aucune chaîne n'est stockée avec "% s"
printf()
entraîne une sortie non-sens arrêtée uniquement par un emplacement de mémoire aléatoire, où 0 (\0
) est.Il en va de même pour le point d'entrée d'un programme. Sous le C64, il était possible de mettre vos programmes dans (presque) toutes les adresses connues. Les programmes d'assemblage ont été lancés avec une instruction appelée
sys
suivie d'une adresse:sys 49152
était un endroit commun pour mettre votre code assembleur. Mais rien ne vous empêche de charger, par exemple, des données graphiques49152
, ce qui entraîne un crash de la machine après le "démarrage" de ce point. Dans ce cas, le cycle d'instruction commençait par la lecture de "données graphiques" et par la tentative de l'interpréter comme un "code" (ce qui n'avait évidemment aucun sens); les effets étaient parfois stupéfiants;)Comme dit: Le "contexte" - c'est-à-dire les instructions précédentes et suivantes - aide à traiter les données de la manière souhaitée. Du point de vue de la machine, il n’ya aucune différence entre les emplacements de mémoire.
int
etchar
n’est que du vocabulaire, ce qui a un sens encompiletime
; pendantruntime
(au niveau de l'assemblage), il n'y a paschar
ouint
.L'ordinateur ne sait rien. Le programmeur fait. Le code compilé génère le contexte , ce qui est nécessaire pour générer des résultats significatifs pour les humains.
Oui et Non . L'information, que ce soit un
int
ou un,char
est perdue. Mais d’autre part, le contexte (les instructions qui indiquent comment traiter les emplacements de mémoire, où les données sont stockées) est préservé; donc implicitement oui, les "informations" sont implicitement disponibles.la source
Laissons cette discussion au langage C uniquement.
Le programme auquel vous faites référence est écrit dans un langage de haut niveau comme C. L’ordinateur ne comprend que le langage de la machine. Les langages de niveau supérieur permettent au programmeur d’exprimer la logique d’une manière plus conviviale, qui est ensuite traduite en code machine que le microprocesseur peut décoder et exécuter. Parlons maintenant du code que vous avez mentionné:
Essayons d'analyser chaque partie:
Ainsi, les identificateurs de type de données int / char ne sont utilisés que par le compilateur et non par le microprocesseur lors de l'exécution du programme. Par conséquent, ils ne sont pas stockés en mémoire.
la source
Ma réponse ici est quelque peu simplifiée et ne fera référence qu'à C.
Non, les informations de type ne sont pas stockées dans le programme.
int
ouchar
ne sont pas des indicateurs de type à la CPU; seulement au compilateur.Le fichier exe créé par le compilateur aura des instructions pour manipuler
int
s si la variable a été déclarée en tant queint
. De même, si la variable a été déclarée en tant quechar
, le fichier exe contiendra des instructions pour manipuler achar
.En C:
Ce programme imprimera son message, car les
char
etint
ont les mêmes valeurs en RAM.Maintenant, si vous vous demandez comment
printf
gérer la sortie65
pour unint
etA
pour unchar
, c'est parce que vous devez spécifier dans la "chaîne de formatage" commentprintf
la valeur doit être traitée .(Par exemple,
%c
signifie traiter la valeur en tant quechar
, et%d
signifie traiter la valeur en tant qu'entier; même valeur dans tous les cas, cependant.)la source
printf
. @OP:int a = 65; printf("%c", a)
va sortir'A'
. Pourquoi? Parce que le processeur s'en fiche. Pour lui, tout ce qu'il voit sont des bits. Votre programme a demandé au processeur de stocker 65 (comme par hasard la valeur'A'
en ASCII)a
puis de générer un caractère, ce qui est le cas. Pourquoi? Parce qu'il s'en fiche.Au niveau le plus bas, dans la CPU physique réelle, il n'y a aucun type (en ignorant les unités à virgule flottante). Juste des motifs de bits. Un ordinateur travaille en manipulant des motifs de bits, très, très vite.
C'est tout ce que le processeur fait, tout ce qu'il peut faire. Il n'y a rien de tel qu'un int ou un char.
S'exécutera en tant que:
L'instruction iadd déclenche un matériel qui se comporte comme si les registres 1 et 2 étaient des entiers. S'ils ne représentent pas réellement des entiers, toutes sortes de choses peuvent mal se passer plus tard. Le meilleur résultat est généralement le crash.
C'est au compilateur de choisir la bonne instruction en fonction des types indiqués dans le source, mais dans le code machine réel exécuté par la CPU, il n'y a pas de types, nulle part.
edit: Notez que le code machine actuel ne mentionne en fait ni 4, ni 5, ni aucun nombre entier. il ne s'agit que de deux modèles de bits, et une instruction qui prend deux modèles de bits, suppose qu'ils sont internes et les ajoute.
la source
Réponse courte, le type est codé dans les instructions de la CPU générées par le compilateur.
Bien que les informations sur le type ou la taille des informations ne soient pas directement stockées, le compilateur en assure le suivi lors de l'accès, de la modification et du stockage des valeurs dans ces variables.
Ce n’est pas le cas, mais lorsque le compilateur produit le code machine, il le sait. Un
int
et unchar
peuvent être de différentes tailles. Dans une architecture où char est la taille d'un octet et int est de 4 octets, la variablex
n'est pas à l'adresse 10001, mais également à 10002, 10003 et 10004. Lorsque le code doit charger la valeur dex
dans un registre de CPU, il utilise l'instruction pour charger 4 octets. Lors du chargement d'un caractère, il utilise l'instruction pour charger 1 octet.Comment choisir laquelle des deux instructions? Le compilateur décide pendant la compilation que ce n'est pas fait au moment de l'exécution après avoir inspecté les valeurs en mémoire.
Notez également que les registres peuvent être de tailles différentes. Sur les processeurs Intel x86, le format EAX a une largeur de 32 bits, dont la moitié est AX (16) et AX est scindé en AH et AL (tous deux en 8 bits).
Ainsi, si vous souhaitez charger un entier (sur des processeurs x86), vous utilisez l'instruction MOV pour les entiers. Pour charger un caractère, vous utilisez l'instruction MOV pour les caractères. Ils s'appellent tous les deux MOV, mais ils ont des codes d'opération différents. Être effectivement deux instructions différentes. Le type de la variable est codé dans l'instruction à utiliser.
La même chose se produit avec d'autres opérations. Il existe de nombreuses instructions pour effectuer une addition, en fonction de la taille des opérandes et même s'ils sont signés ou non. Voir https://en.wikipedia.org/wiki/ADD_(x86_instruction) qui répertorie les différents ajouts possibles.
Premièrement, un caractère serait 10011001, mais un int serait 00000000 00000000, 00000000, 10011001, car ils sont de tailles différentes (sur un ordinateur ayant les mêmes tailles que celles mentionnées ci-dessus). Mais laisse envisager le cas
signed char
vsunsigned char
.Ce qui est stocké dans un emplacement de mémoire peut être interprété comme vous le souhaitez. Une partie des responsabilités du compilateur C consiste à s'assurer que ce qui est stocké et lu à partir d'une variable est fait de manière cohérente. Ce n’est donc pas que le programme sache ce qui est stocké dans un emplacement de mémoire, mais qu’il convient au préalable de lire et d’écrire les mêmes choses là-bas. (sans compter des choses comme les types de casting).
la source
Dans les langages à vérification de type tels que C #, la vérification de type est effectuée par le compilateur. Le code benji a écrit:
Refuserait simplement de compiler. De même, si vous essayez de multiplier une chaîne et un entier (j'allais dire ajouter, mais l'opérateur '+' est surchargé de concaténation de chaînes et cela pourrait fonctionner).
Le compilateur refuserait simplement de générer du code machine à partir de ce C #, peu importe combien votre chaîne serait embrassée.
la source
Les autres réponses sont correctes en ce sens que chaque périphérique grand public que vous rencontrerez ne stocke pas les informations de type. Cependant, il y a eu plusieurs conceptions matérielles dans le passé (et aujourd'hui, dans un contexte de recherche) qui utilisent une architecture balisée - elles stockent à la fois les données et le type (et éventuellement d'autres informations). Celles-ci incluraient le plus clairement les machines Lisp .
Je me souviens vaguement d'avoir entendu parler d'une architecture matérielle conçue pour la programmation orientée objet et présentant quelque chose de similaire, mais je ne la trouve pas pour le moment.
la source