Mise à JOUR: j'ai bien aimé cette question tellement j'en ai fait le sujet de mon blog, le 18 novembre 2011. Merci pour la grande question!
Je me suis toujours demandé: quel est le but de la pile?
Je suppose que vous voulez dire la pile d'évaluation de la MSIL langue, et non pas le par-de la pile lors de l'exécution.
Pourquoi est-il un transfert à partir de la mémoire de la pile ou de la "chargement?" D'autre part, pourquoi il y a un transfert de la pile de la mémoire ou de "stockage"? Pourquoi ne pas les avoir tous mis dans la mémoire?
MSIL est une "machine virtuelle" de la langue. Compilateurs comme le compilateur C# générer CIL, puis lors de l'exécution d'un autre compilateur appelé le JIT (Just In Time) compilateur transforme le IL en code machine qui peut s'exécuter.
Alors d'abord, nous allons répondre à la question "pourquoi avez-MSIL à tous?" Pourquoi ne pas simplement avoir le compilateur C# écrire du code machine?
Parce qu'il est moins cher de faire de cette façon. Supposons que nous n'avons pas le faire de cette façon; supposons que chaque langue possède sa propre machine générateur de code. Vous avez vingt langues différentes: le C#, JScript .NET, Visual Basic, IronPython, F#... Et supposons que vous ayez dix différents processeurs. Combien de générateurs de code avez-vous écrire? 20 x 10 = 200 générateurs de code. C'est beaucoup de travail. Maintenant, supposons que vous souhaitez ajouter à un nouveau processeur. Vous devez écrire le générateur de code pour vingt fois, une pour chaque langue.
En outre, il est travail difficile et dangereux. Écriture efficace des générateurs de code pour les frites que vous n'êtes pas un expert, c'est un dur métier! Compilateur concepteurs sont des experts sur l'analyse sémantique de leur langue, et non pas sur l'efficacité de l'allocation des registres de nouveaux jeux de puces.
Maintenant, supposons que nous ne le CIL. Combien de CIL générateurs avez-vous écrire? Un par langue. Combien de compilateurs JIT avez-vous écrire? Un par processeur. Total: 20 + 10 = 30 générateurs de code. En outre, le langue-à-CIL generator est facile à écrire car CIL est un langage simple, et le CIL-de-machine-générateur de code est également facile à écrire car CIL est un langage simple. Nous débarrasser de toutes les subtilités du langage C# et VB et autres joyeusetés et "bas" tout en un langage simple qui est facile à écrire un jitter pour.
Avoir un langage intermédiaire abaisse le coût de production d'un nouveau compilateur de langage de façon spectaculaire. Il réduit également le coût de l'appui de une nouvelle puce de façon spectaculaire. Vous souhaitez soutenir une nouvelle puce, vous trouverez certains des experts sur cette puce et demandez-leur d'écrire un CIL de la gigue et vous avez terminé; vous, alors en charge toutes les langues sur votre carte à puce.
OK, nous avons donc établi pourquoi nous avons MSIL; car le fait d'avoir un langage intermédiaire permet de réduire les coûts. Pourquoi, alors, est la langue d'une pile "machine"?
Parce que la pile machines sont conceptuellement très simple pour le compilateur de langage écrivains à traiter. Les piles sont simples, faciles à comprendre mécanisme pour décrire les calculs. Pile machines sont aussi conceptuellement très facile pour le compilateur JIT écrivains à traiter. À l'aide d'une pile est une simplification de l'abstraction, et, par conséquent, encore une fois, il contribue à réduire nos coûts.
Vous demandez: "pourquoi une pile?" Pourquoi ne pas tout faire directement à partir de la mémoire? Eh bien, nous allons y réfléchir. Supposons que vous souhaitez générer du code CIL pour:
int x = A() + B() + C() + 10;
Supposons que nous avons la convention que "ajouter", "appel", "magasin", et ainsi de suite toujours prendre leurs arguments en dehors de la pile et de mettre leur résultat (si il y en a un) sur la pile. Pour générer du code CIL pour cette C# que nous venons de dire quelque chose comme:
load the address of x // The stack now contains address of x
call A() // The stack contains address of x and result of A()
call B() // Address of x, result of A(), result of B()
add // Address of x, result of A() + B()
call C() // Address of x, result of A() + B(), result of C()
add // Address of x, result of A() + B() + C()
load 10 // Address of x, result of A() + B() + C(), 10
add // Address of x, result of A() + B() + C() + 10
store in address // The result is now stored in x, and the stack is empty.
Supposons maintenant que nous l'avons fait sans une pile. Nous allons le faire à votre façon, où chaque opcode prend les adresses de ses opérandes et l'adresse à laquelle il stocke ses résultats:
Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...
Vous voyez comment ça se passe? Notre code devient énorme parce que nous avons explicitement attribuer la totalité de l'entreposage temporaire qui serait normalement par convention, il suffit d'aller sur la pile. Pire, nos opérateurs eux-mêmes sont tous énormes, car ils sont maintenant tous de prendre comme argument l'adresse qu'ils vont écrire leur résultat, et l'adresse de chaque opérande. Une "ajouter" de l'enseignement qui sait qu'il va prendre deux choses en dehors de la pile et de mettre une chose sur peut être un seul octet. Un complément d'instruction qui prend deux opérandes adresses et un résultat d'adresses va être énorme.
Nous utilisons en fonction de pile opcodes parce que les piles de résoudre le problème commun. À savoir: je veux allouer du stockage temporaire, l'utiliser très rapidement et ensuite se débarrasser de lui rapidement quand je suis fait. En faisant l'hypothèse que nous avons une cheminée à notre disposition, nous pouvons faire les opcodes très petite et le code très laconique.
Mise à JOUR: Quelques réflexions supplémentaires
D'ailleurs, cette idée d'une baisse drastique des coûts par (1) spécifiant une machine virtuelle, (2) l'écriture de compilateurs qui cible la VM de la langue, et (3) la rédaction des implémentations de la machine virtuelle sur une variété de matériel, n'est pas une idée nouvelle. Il n'est pas né avec MSIL, LLVM, le bytecode Java, ou toutes autres infrastructures modernes. La première mise en œuvre de cette stratégie, je suis conscient de est le code postal de la machine à partir de 1966.
La première que j'ai personnellement entendu parler de ce concept a été quand j'ai appris comment l'Infocom réalisateurs réussi à obtenir Zork cours d'exécution sur beaucoup de machines différentes, si bien. Ils ont spécifié une machine virtuelle appelée la Z-machine, puis mis à la Z-machine émulateurs pour tout le matériel qu'ils voulaient exécuter leurs jeux sur. Cela a eu l'ajout d'énormes bénéfices qu'ils pourraient mettre en œuvre une gestion de mémoire virtuelle sur les primitives 8-bits des systèmes; un jeu qui pourrait être plus grand que ne le fit dans la mémoire, car ils pourraient tout page le code à partir du disque quand ils en ont besoin et de les jeter quand ils avaient besoin de charger le nouveau code.