Les déclarateurs de types de données tels que “int” et “char” sont-ils stockés dans la RAM lorsqu'un programme C s'exécute?

74

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, intou 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 aet x? Le plus déroutant, comment l'exécution sait-elle qu'il as'agit d'un caractère et d' xun entier? Je veux dire, le intet charmentionné 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 charou 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 intou 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 intou char?

utilisateur16307
la source
24
Cette information est totalement perdue au moment de l'exécution. Vous (et votre compilateur) devez vous assurer à l’avance que la mémoire sera interprétée correctement. Est-ce la réponse que vous recherchiez?
5gon12eder
4
Ce n'est pas. Parce que cela suppose que vous sachiez ce que vous faites, il faut tout ce qu'il trouve à l'adresse de mémoire que vous avez fournie et l'écrit sur stdout. Si ce qui a été écrit correspond à un caractère lisible, il apparaîtra éventuellement sur la console de quelqu'un en tant que caractère lisible. Si cela ne correspond pas, il apparaîtra sous forme de charabia, ou éventuellement de caractère lisible au hasard.
Robert Harvey
22
@ user16307 La réponse courte est que, dans les langages à typage statique, chaque fois que vous imprimez un caractère, le compilateur produira un code différent de celui qu'il aurait utilisé pour imprimer un int. Au moment de l'exécution, il n'y a plus aucune connaissance qui xsoit 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é.
Ixrec
13
@ user16307 Il est toujours stocké sous la forme d'une représentation binaire du nombre 65. Qu'elle soit imprimée en tant que 65 ou en tant que A dépend du code produit par votre compilateur pour l'imprimer. À côté des 65, il n'y a pas de métadonnées indiquant qu'il s'agit en fait d'un caractère ou d'un entier (du moins, pas dans des langages statiques comme C).
Ixrec
2
Pour bien comprendre les concepts que vous posez ici et les mettre en œuvre par vous-même, vous voudrez peut-être suivre un cours sur le compilateur, par exemple celui de
coursera

Réponses:

122

Pour répondre à la question que vous avez postée dans plusieurs commentaires (que je pense que vous devriez éditer dans votre post):

Ce que je ne comprends pas, c'est comment l'ordinateur sait quand il lit une valeur de variable et une adresse telle que 10001 si est un entier ou un caractère. Imaginons que je clique sur un programme appelé anyprog.exe. Immédiatement, le code commence à s'exécuter. Ce fichier exe contient-il des informations sur le fait de savoir si les variables sont stockées dans ou dans char?

Alors mettons un peu de code dessus. Disons que vous écrivez:

int x = 4;

Et supposons qu'il soit stocké dans la RAM:

0x00010004: 0x00000004

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 0x00010004la valeur 0x000000004. 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:

int x = 4;
x = x + 5;

Nous avons une lecture et une écriture ici. Lorsque votre programme lit en xmémoire, il le trouve 0x00000004. Et votre programme sait y ajouter 0x00000005quelque 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 ajouter 4et 5ensemble. 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:

0x00000004: 0x12345678

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:

char A = 'a';

Comment l'ordinateur sait-il s'afficher adans la console? Eh bien, il y a beaucoup d'étapes à suivre. La première consiste à aller à l’ Aemplacement de s en mémoire et à le lire:

0x00000004: 0x00000061

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, la charmoitié d'un double, un pointeur, une partie d'un tableau, une stringpartie, 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 variable xest 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 2les quatre premiers octets où je suppose en xê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à variable y.

  • 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 plus y? Qu'est - ce qui se passe quand quelqu'un lit xun charou ycomme 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 changer xpour 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 utiliser int xcommestringet 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? xCommence- 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 2 y, 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 est xet lequel On est y, parce que le système n'a aucune idée. Et vous (en tant qu'être humain) devez vous rappeler quels types xet quels types ycar, encore une fois - le système n'a aucune idée.

Shaz
la source
Explication étonnante. Seule la partie que vous avez écrite "Comment sait-on transformer la valeur entière en un caractère à afficher? Tout simplement, le compilateur s’est assuré de mettre en place toutes les étapes nécessaires pour effectuer cette transition." est encore brumeux pour moi. Disons que le processeur extrait 0x00000061 du registre RAM. À partir de là, dites-vous que d’autres instructions (dans le fichier exe) permettent de faire la transition avec ce que nous voyons à l’écran?
user16307
2
@ user16307 oui, il y a des instructions supplémentaires. Chaque ligne de code que vous écrivez peut potentiellement être transformée en plusieurs instructions. Il y a des instructions pour déterminer le caractère à utiliser, il existe des instructions sur les pixels à modifier et sur la couleur à laquelle ils changent, etc. Par exemple, utiliser std :: cout signifie que vous utilisez une bibliothèque. Votre code à écrire sur la console peut ne comporter qu'une seule ligne, mais les fonctions que vous appelez auront plus de lignes et chaque ligne peut se transformer en plusieurs instructions machine.
Shaz
8
@ user16307 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.
Charles E. Grant
2
Je trouverais une expression différente pour "Le code d'octet lui-même", car le code d'octet (ou bytecode) fait généralement référence à un langage intermédiaire (comme Java Bytecode ou MSIL), qui pourrait en fait stocker ces données pour que le moteur d'exécution les exploite. De plus, on ne sait pas exactement à quoi "code octet" est censé faire référence dans ce contexte. Sinon, bonne réponse.
jpmc26
6
@ user16307 Essayez de ne pas vous soucier de C ++ et C #. Ce que ces personnes disent est bien au-dessus de votre compréhension actuelle du fonctionnement des ordinateurs et des compilateurs. Pour les besoins de ce que vous essayez de comprendre, le matériel ne connaît PAS les types, char, int ou autre. Lorsque vous avez dit au compilateur que la variable était un int, il a généré un code exécutable permettant de gérer un emplacement de mémoire, SI SI c L'emplacement mémoire lui-même ne contient aucune information sur les types; c'est juste que votre programme a décidé de le traiter comme un int. Oubliez tout ce que vous avez entendu sur les informations de type à l'exécution.
Andres F.
43

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 intou exécuter du code qui l’interprète comme un char? "

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 que intdans le programme, il mettrait le code pour le traiter est un char.

Il y a des raisons de conserver le type au moment de l'exécution:

  • Typage dynamique: dans le typage dynamique, la vérification du type a lieu au moment de l'exécution, il est donc évident que le type doit être connu au moment de l'exécution. Mais C n'est pas typé dynamiquement, les types peuvent donc être effacés en toute sécurité. (Notez qu'il s'agit toutefois d'un scénario très différent. Les types dynamiques et les types statiques ne sont pas vraiment identiques, et dans un langage à typage mixte, vous pouvez toujours effacer les types statiques et ne conserver que les types dynamiques.)
  • Polymorphisme dynamique: si vous exécutez un code différent en fonction du type d'exécution, vous devez conserver le type d'exécution. C n’a pas de polymorphisme dynamique (en réalité, sauf dans certains cas spéciaux codés en dur, par exemple l’ +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.
  • Réflexion sur l'exécution: si vous autorisez le programme à réfléchir sur ses types au moment de l'exécution, vous devez évidemment les conserver au moment de l'exécution. Vous pouvez facilement voir cela avec Java, qui conserve les types de premier ordre au moment de l'exécution, mais efface les arguments de type en types génériques lors de la compilation, de sorte que vous ne pouvez réfléchir que sur le constructeur de type ("type brut") mais pas sur l'argument de type. Encore une fois, C n'a pas de réflexion à l'exécution, il n'a donc pas besoin de conserver le type à l'exécution.

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.)

Jörg W Mittag
la source
3
Non! Pourquoi? À quoi cette information serait-elle nécessaire? Le compilateur renvoie le code permettant de lire a chardans le binaire compilé. Il ne sort intpas le code pour un byte, il ne sort pas le code pour un , il ne sort pas le code pour un pointeur, il ne sort que le code pour un char. 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.
Jörg W Mittag
2
Il n'y a pas. Le compilateur met simplement du code pour imprimer un caractère dans le binaire. Période. Le compilateur sait qu’à cette adresse mémoire, il y a char, il place donc le code permettant d’imprimer un caractère dans le binaire. Si la valeur à cette adresse mémoire pour une raison étrange se trouve ne pas être un caractère, alors, bien, tout l'enfer se déchaîne. C'est comme ça que fonctionne toute une classe d'exploits de sécurité.
Jörg W Mittag
2
Pensez-y: si le processeur était au courant des types de données des programmes, alors tout le monde sur la planète devrait acheter un nouveau processeur à chaque fois que quelqu'un invente un nouveau type. public class JoergsAwesomeNewType {};Voir? Je viens d'inventer un nouveau type! Vous devez acheter un nouveau processeur!
Jörg W Mittag
9
Non, ce n'est pas le cas. Le compilateur sait quel code il doit mettre dans le binaire. Il ne sert à rien de conserver cette information. Si vous imprimez un int, le compilateur mettra le code pour imprimer un int. Si vous imprimez un caractère, le compilateur mettra le code pour imprimer un caractère. Période. Mais c'est juste un motif. Le code pour imprimer un caractère interprètera le motif de bits d’une certaine manière, le code pour imprimer un int interprétera le bit d’une manière différente, mais il n’existe aucun moyen de distinguer un motif de bits qui est un int d’un motif de bits qui est un caractère, c'est une chaîne de bits.
Jörg W Mittag Le
2
@ user16307: "Le fichier exe ne contient-il pas d'informations sur quelle adresse correspond à quel type de données?" Peut être. Si vous compilez avec des données de débogage, les données de débogage incluront des informations sur les noms, les adresses et les types de variables. Et parfois, les données de débogage sont stockées dans le fichier .exe (sous forme de flux binaire). Mais il ne fait pas partie du code exécutable et il n'est pas utilisé par l'application elle-même, mais par un débogueur.
Ben Voigt
12

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.

comment s'appelle-t-il
la source
Comment expliqueriez-vous si le processeur trouve 0x00000061 dans un registre et le récupère? et imaginez le programme de console supposé générer ceci en tant que caractère non int. voulez-vous dire que dans ce fichier exe il y a des codes d'instruction qui savent que l'adresse de 0x00000061 est un caractère et qui se convertit en un caractère en utilisant une table ASCII?
user16307
7
Notez que "tout se bloque" est en fait le meilleur scénario. "Les choses étranges se produisent" est le deuxième meilleur scénario, "des choses subtilement étranges se produisent", c'est encore pire, et le pire des cas est "des choses se produisent dans votre dos que quelqu'un manipulé intentionnellement se passe comme il le souhaite", aka un exploit de sécurité.
Jörg W Mittag
@ user16307: le code dans le programme indiquera à l'ordinateur de récupérer cette adresse, puis de l'afficher en fonction du codage utilisé. Que les données dans l’emplacement mémoire soient des caractères ASCII ou des ordures complètes, l’ordinateur n’est pas concerné. Quelque chose d'autre était responsable de la configuration de cette adresse mémoire pour qu'elle contienne les valeurs attendues. Je pense que cela pourrait vous aider d'essayer une programmation en assembleur.
Whatsisname
1
@ JörgWMittag: en effet. J'ai envisagé de mentionner un débordement de mémoire tampon à titre d'exemple, mais j'ai décidé que cela rendrait les choses encore plus confuses.
Whatsisname
@ user16307: Ce qui affiche des données à l'écran est un programme. Unixen traditionnel est un terminal (un logiciel qui émule le terminal série DEC VT100 - un périphérique matériel avec moniteur et clavier qui affiche tout ce qui entre dans son modem et le moniteur et envoie tout ce qui est saisi sur son clavier à son modem). Sous DOS, c’est DOS (en réalité, le mode texte de votre carte VGA, mais nous l’ignorons) et sous Windows, c’est command.com. Votre programme ne sait pas qu'il imprime réellement des chaînes, mais simplement une séquence d'octets (nombres).
Slebetman
8

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). .

8bittree
la source
Ce que je ne comprends pas, c'est comment l'ordinateur sait quand il lit une valeur de variable et une adresse telle que 10001 si est un entier ou un caractère. Imaginons que je clique sur un programme appelé anyprog.exe. Immédiatement, le code commence à s'exécuter. Ce fichier exe contient-il des informations sur le fait de savoir si les variables sont stockées dans ou dans char? -
user16307
@ user16307 Non, il n'y a pas d'informations supplémentaires pour savoir si quelque chose est un int ou un char. J'ajouterai quelques exemples plus tard, en supposant que personne ne me bat.
8bittree
1
@ user16307: le fichier exe contient cette information indirectement. Le processeur qui exécute le programme ne se soucie pas des types utilisés lors de l'écriture du programme, mais une grande partie peut être déduite des instructions utilisées pour accéder aux différents emplacements de mémoire.
Bart van Ingen Schenau
@ user16307, il y a en fait un peu d'informations supplémentaires. Les fichiers exe savent qu'un entier est de 4 octets. Ainsi, lorsque vous écrivez "int a", le compilateur réserve 4 octets pour la variable a et peut donc calculer l'adresse de a et des autres variables après.
Esben Skov Pedersen
1
@ user16307 il n'y a pas de différence pratique (à part la taille du type) entre int a = 65et char b = 'A'une fois le code compilé.
6

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:

int main( void )
{
  int x, y, z;

  x = 1;
  y = 2;

  z = x + y;

  return 0;
}

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:

main:
.LFB2:
        pushq   %rbp               ;; save the current frame pointer value
.LCFI0:
        movq    %rsp, %rbp         ;; make the current stack pointer value the new frame pointer value
.LCFI1:                            
        movl    $1, -12(%rbp)      ;; x = 1
        movl    $2, -8(%rbp)       ;; y = 2
        movl    -8(%rbp), %eax     ;; copy the value of y to the eax register
        addl    -12(%rbp), %eax    ;; add the value of x to the eax register
        movl    %eax, -4(%rbp)     ;; copy the value in eax to z
        movl    $0, %eax           ;; eax gets the return value of the function
        leave                      ;; exit and restore the stack
        ret

Il y a quelques éléments supplémentaires qui suivent ret, mais ils ne sont pas pertinents pour la discussion.

%eaxest un registre de données à usage général 32 bits. %rspest 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. %rbpest 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 variable xest 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' movlinstruction, qui permet de copier des mots de 32 bits d'un emplacement à un autre. Nous appelons ensuite addl, ce qui ajoute la valeur entière de y(stocké à -8(%rbp)) à la valeur déjà présente %eax. Nous sauvegardons ensuite le résultat dans -4(%rbp), qui est z.

Modifions cela pour que nous ayons affaire à des doublevaleurs plutôt qu'à des intvaleurs:

int main( void )
{
  double x, y, z;

  x = 1;
  y = 2;

  z = x + y;

  return 0;
}

Courir à gcc -Snouveau nous donne:

main:
.LFB2:
        pushq   %rbp                              
.LCFI0:
        movq    %rsp, %rbp
.LCFI1:
        movabsq $4607182418800017408, %rax ;; copy literal 64-bit floating-point representation of 1.00 to rax
        movq    %rax, -24(%rbp)            ;; save rax to x
        movabsq $4611686018427387904, %rax ;; copy literal 64-bit floating-point representation of 2.00 to rax
        movq    %rax, -16(%rbp)            ;; save rax to y
        movsd   -24(%rbp), %xmm0           ;; copy value of x to xmm0 register
        addsd   -16(%rbp), %xmm0           ;; add value of y to xmm0 register
        movsd   %xmm0, -8(%rbp)            ;; save result to z
        movl    $0, %eax                   ;; eax gets return value of function
        leave                              ;; exit and restore the stack
        ret

Plusieurs différences Au lieu de movlet addl, nous utilisons movsdet addsd(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.

John Bode
la source
4

Historiquement , C considérait la mémoire comme composée d’un certain nombre de groupes d’emplacements numérotés de typeunsigned 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é 0x12345678sur un fichier 32 bits unsigned int, puis tenter de lire deux unsigned intvaleurs consécutives de 16 bits à partir de son adresse et de l'adresse ci-dessus, puis en fonction de la moitié de celle-ci unsigned intstocké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 type unsigned charou 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é:

unsigned int a = 0x12345678;
unsigned short p = (unsigned short *)&a;
printf("0x%04X",*p);

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 aincluent 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,

supercat
la source
1
Mentionner un comportement indéfini lorsque taper du texte à une personne qui ne comprend pas qu'il n'y a pas de RTTI semble contre-intuitif
Cole Johnson
@ColeJohnson: Il est dommage qu'il n'y ait pas de nom officiel ou de norme pour le dialecte du C supporté par 99% des compilateurs antérieurs à 2009, car ils devraient être considérés comme des langues fondamentalement différentes, tant du point de vue de l'enseignement que de la pratique. Etant donné que le même nom est attribué au dialecte qui a développé un certain nombre de comportements prévisibles et optimisables sur une période de 35 ans, dialecte rejetant de tels comportements dans le but supposé d'optimisation, il est difficile d'éviter la confusion lorsque l'on parle de choses qui fonctionnent différemment en eux. .
Supercat
Historiquement, C utilisait des machines Lisp qui ne permettaient pas de jouer avec des types aussi lâches. Je suis à peu près sûr que bon nombre des "comportements prévisibles et optimisables" observés il y a 30 ans ne fonctionnaient tout simplement ailleurs que sous BSD Unix sur le VAX.
prosfilaes
@prosfilaes: Peut-être que "99% des compilateurs utilisés de 1999 à 2009" seraient plus précis? Même lorsque les compilateurs avaient des options pour des optimisations d’entiers plutôt agressives, ils n’étaient que cela - des options. Je ne sais pas si j'ai déjà vu un compilateur avant 1999 qui n'avait pas de mode qui ne garantissait pas que, étant donné int x,y,z;l'expression, x*y > zil 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.
Supercat
1
... où les unsigned charvaleurs utilisées pour construire un type "viennent de". Si un programme décompose un pointeur en un unsigned char[]fichier, affiche brièvement son contenu hexagonal à l'écran, puis efface le pointeur, le unsigned 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é.
Supercat
3

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).

Mike Harris
la source
Ce que je ne comprends pas, c'est comment l'ordinateur sait quand il lit une valeur de variable et une adresse telle que 10001 si est un entier ou un caractère. Imaginons que je clique sur un programme appelé anyprog.exe. Immédiatement, le code commence à s'exécuter. Ce fichier exe contient-il des informations sur le fait de savoir si les variables sont stockées dans ou dans char? -
user16307
1
@ user16307 Essentiellement non, toutes ces informations sont complètement perdues. Le code machine doit être suffisamment bien conçu pour faire son travail correctement, même sans ces informations. Tout ce qui importe à l’ordinateur, c’est qu’il ya huit bits consécutifs dans une adresse 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.
Panzercrisis
1
Notez que le typage dynamique n'est pas la seule raison de conserver les types. Java est typé statiquement, mais il doit toujours conserver les types, car il permet de réfléchir dynamiquement sur le type. De plus, il possède un polymorphisme d'exécution, c'est-à-dire une répartition de méthode basée sur le type d'exécution, pour lequel il a également besoin de ce type. C ++ place le code de dispatch de la méthode dans l'objet (ou plutôt la classe) lui-même, ainsi, il n'a pas besoin du type dans un sens (bien que la vtable soit en quelque sorte une partie du type, donc vraiment au moins une partie de le type est conservé), mais en Java, le code d’envoi de la méthode est centralisé.
Jörg W Mittag
regardez ma question j'ai écrit "quand un programme C s'exécute?" ne sont-ils pas indirectement stockés dans le fichier exe parmi les codes d’instruction et finissent par se placer en mémoire? J'écris encore ceci pour vous: Si la CPU trouve 0x00000061 dans un registre et le récupère; et imaginez le programme de console supposé générer ceci en tant que caractère non int. y a-t-il dans ce fichier exe (code machine / binaire) des codes d'instruction qui connaissent l'adresse de 0x00000061 est un caractère et qui convertit en un caractère en utilisant une table ASCII? Si c'est le cas, cela signifie que les identifiants de char int sont indirectement dans le binaire ???
user16307
Si la valeur est 0x61 et est déclarée comme un caractère (c'est-à-dire 'a') et que vous appelez une routine pour l'afficher, il y aura [éventuellement] un appel système pour afficher ce caractère. Si vous l'avez déclaré en tant qu'int et avez appelé la routine d'affichage, le compilateur saura générer le code permettant de convertir 0x61 (décimal 97) en séquence ASCII 0x39, 0x37 ('9', '7'). En bout de ligne: le code généré est différent car le compilateur sait les traiter différemment.
Mike Harris
3

Vous devez faire la distinction entre compiletimeet runtimed’une part codeet datade l’autre.

Du point de vue de la machine , il n'y a pas de différence entre ce que vous appelez codeou instructionset ce que vous appelez data. Tout se résume aux chiffres. Mais certaines séquences - ce que nous appellerions code- font quelque chose que nous trouvons utile, d’autres seraient simplement crashla machine.

Le travail effectué par la CPU est une simple boucle en 4 étapes:

  • Récupère les "données" d'une adresse donnée
  • Décoder l'instruction (c'est-à-dire "interpréter" le nombre en tant que instruction)
  • Lire une adresse effective
  • Exécuter et stocker les résultats

C'est ce qu'on appelle le cycle d'instruction .

J'ai lu que A et 4 sont stockés dans des adresses RAM ici. Mais qu'en est-il a et x?

aet xsont 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 variable aest utilisée, il y a effectivement l'adresse du contenu autilisé.

Le plus déroutant, comment l'exécution sait-elle que a est un caractère et que x est un int?

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 syssuivie 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 graphiques 49152, 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;)

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 caractère ou un entier?

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. intet charn’est que du vocabulaire, ce qui a un sens en compiletime; pendant runtime(au niveau de l'assemblage), il n'y a pas charou int.

Ce que je ne comprends pas, c'est comment l'ordinateur sait, lorsqu'il lit la valeur d'une variable à partir d'une adresse telle que 10001, que ce soit un entier ou un caractère.

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.

Ce fichier exécutable contient-il des informations indiquant si les variables stockées sont du type int ou char

Oui et Non . L'information, que ce soit un intou un, charest 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.

Thomas Junk
la source
Belle distinction entre compilation et exécution.
Michael Blackburn
2

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é:

char a = 'A';
int x = 4;

Essayons d'analyser chaque partie:

char / int sont appelés types de données. Ceux-ci disent au compilateur d'allouer de la mémoire. Dans le cas de charce sera 1 octet et int2 octets. (Veuillez noter que la taille de la mémoire dépend à nouveau du microprocesseur).

a / x sont appelés identificateurs. Maintenant, vous pouvez dire des noms "conviviaux" attribués aux emplacements de mémoire dans la RAM.

= indique au compilateur de stocker "A" à l'emplacement de mémoire aet 4 à l'emplacement de mémoire x.

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.

prasad
la source
Les identifiants de type de données ok int / char ne sont pas directement stockés en mémoire en tant que variables, mais ne sont-ils pas indirectement stockés dans un fichier exe parmi les codes d’instruction et finissent par être placés en mémoire? J'écris encore ceci pour vous: Si la CPU trouve 0x00000061 dans un registre et le récupère; et imaginez le programme de console supposé générer ceci en tant que caractère non int. y a-t-il dans ce fichier exe (code machine / binaire) des codes d'instruction qui connaissent l'adresse de 0x00000061 est un caractère et qui convertit en un caractère en utilisant une table ASCII? Si c'est le cas, cela signifie que les identifiants de char int sont indirectement dans le binaire ???
user16307
Non pour la CPU, c'est tous ses numéros. Pour votre exemple spécifique, l’impression sur console n’est pas dépendante du fait que variable soit char ou int. Je mettrai à jour ma réponse en indiquant en détail comment un programme de haut niveau est converti en langage machine jusqu'à l'exécution du programme.
Prasad
2

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.

intou charne sont pas des indicateurs de type à la CPU; seulement au compilateur.

Le fichier exe créé par le compilateur aura des instructions pour manipuler ints si la variable a été déclarée en tant que int. De même, si la variable a été déclarée en tant que char, le fichier exe contiendra des instructions pour manipuler a char.

En C:

int main()
{
    int a = 65;
    char b = 'A';
    if(a == b)
    {
        printf("Well, what do you know. A char can equal an int.\n");
    }
    return 0;
}

Ce programme imprimera son message, car les charet intont les mêmes valeurs en RAM.

Maintenant, si vous vous demandez comment printfgérer la sortie 65pour un intet Apour un char, c'est parce que vous devez spécifier dans la "chaîne de formatage" comment printfla valeur doit être traitée .
(Par exemple, %csignifie traiter la valeur en tant que char, et %dsignifie traiter la valeur en tant qu'entier; même valeur dans tous les cas, cependant.)

BenjiWiebe
la source
2
J'espérais que quelqu'un utiliserait un exemple en utilisant 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) apuis de générer un caractère, ce qui est le cas. Pourquoi? Parce qu'il s'en fiche.
Cole Johnson
mais pourquoi certains disent ici en cas C #, ce n'est pas l'histoire? J'ai lu d'autres commentaires et ils disent en C # et C ++ que l'histoire (informations sur les types de données) est différente et que même le processeur ne fait pas l'informatique. Des idées à ce sujet?
user16307
@ user16307 Si le CPU ne fait pas l'informatique, le programme n'est pas en cours d'exécution. :) En ce qui concerne C #, je ne sais pas, mais je pense que ma réponse vaut également pour cela. En ce qui concerne C ++, je sais que ma réponse s’applique là-bas.
BenjiWiebe
0

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.

x = 4 + 5

S'exécutera en tant que:

  1. Charger 00000100 dans le registre 1
  2. Charger 00000101 dans le registre 2
  3. Ajouter le registre 1 au registre 2 et le stocker dans le registre 1

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.

Leliel
la source
0

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.

comment l'exécution sait-elle que a est un caractère et que x est un int?

Ce n’est pas le cas, mais lorsque le compilateur produit le code machine, il le sait. Un intet un charpeuvent être de différentes tailles. Dans une architecture où char est la taille d'un octet et int est de 4 octets, la variable xn'est pas à l'adresse 10001, mais également à 10002, 10003 et 10004. Lorsque le code doit charger la valeur de xdans 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.

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 caractère ou un int

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 charvs unsigned 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).

frozenkoi
la source
mais pourquoi certains disent ici en cas C #, ce n'est pas l'histoire? J'ai lu d'autres commentaires et ils disent en C # et C ++ que l'histoire (informations sur les types de données) est différente et que même le processeur ne fait pas l'informatique. Des idées à ce sujet?
user16307
0

mais pourquoi certains disent ici en cas C #, ce n'est pas l'histoire? J'ai lu d'autres commentaires et ils disent en C # et C ++ que l'histoire (informations sur les types de données) est différente et que même le processeur ne fait pas l'informatique. Des idées à ce sujet?

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:

int main()
{
    int a = 65;
    char b = 'A';
    if(a == b)
    {
        printf("Well, what do you know. A char can equal an int.\n");
    }
    return 0;
}

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).

int a = 42;
string b = "Compilers are awesome.";
double[] c = a * b;

Le compilateur refuserait simplement de générer du code machine à partir de ce C #, peu importe combien votre chaîne serait embrassée.

Michael Blackburn
la source
-4

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.

Nathan Ringo
la source
3
La question indique spécifiquement qu'il fait référence au langage C (pas Lisp) et que le langage C ne stocke pas de métadonnées variables. Cela est certainement possible pour une implémentation C, mais comme la norme ne l’interdit pas, en pratique cela ne se produit jamais. Si vous avez des exemples pertinents à la question, s'il vous plaît fournir des citations spécifiques et fournir des références qui se rapportent à la langue C .
Eh bien, vous pourriez écrire un compilateur C pour une machine Lisp, mais personne n’utilise les machines Lisp de nos jours. Au fait, l'architecture orientée objet était Rekursiv .
Nathan Ringo
2
Je pense que cette réponse n'est pas utile. Cela complique les choses bien au-delà du niveau de compréhension actuel du PO. Il est clair que l'OP ne comprend pas le modèle d'exécution de base d'un processeur + RAM et comment un compilateur convertit une source symbolique de haut niveau en un binaire exécutable. La mémoire balisée, RTTI, Lisp, etc., va bien au-delà de ce que le demandeur a besoin de savoir à mon avis, et ne fera que le troubler davantage.
Andres F.
mais pourquoi certains disent ici en cas C #, ce n'est pas l'histoire? J'ai lu d'autres commentaires et ils disent en C # et C ++ que l'histoire (informations sur les types de données) est différente et que même le processeur ne fait pas l'informatique. Des idées à ce sujet?
user16307