Quelle est la différence entre le code natif, le code machine et le code assembleur?

106

Je suis confus à propos du code machine et du code natif dans le contexte des langages .NET.

Quelle est la différence entre eux? Sont-ils les mêmes?

samaladeepak
la source
3
J'ai une question concernant cette question. Cette question relève-t-elle des exigences de StackOverflow? afaik ce n'est pas, mais en même temps ce genre de question est très utile / informatif. En supposant que ce type de question n'est pas autorisé, où devrions-nous poser ce type de questions sinon ici?
Yousuf Azad

Réponses:

150

Les termes sont en effet un peu déroutants, car ils sont parfois utilisés de manière incohérente.

Code machine: c'est le plus bien défini. C'est un code qui utilise les instructions de code octet que votre processeur (le morceau de métal physique qui fait le travail réel) comprend et exécute directement. Tout autre code doit être traduit ou transformé en code machine avant que votre machine puisse l'exécuter.

Code natif: ce terme est parfois utilisé dans les endroits où le code machine (voir ci-dessus) est utilisé . Cependant, il est également parfois utilisé pour désigner du code non managé (voir ci-dessous).

Code non managé et code managé: unmanaged code fait référence à un code écrit dans un langage de programmation tel que C ou C ++, qui est compilé directement dans le code de la machine . Il contraste avec le code managé , qui est écrit en C #, VB.NET, Java ou similaire, et exécuté dans un environnement virtuel (tel que .NET ou JavaVM) qui «simule» en quelque sorte un processeur dans un logiciel. La principale différence est que le code managé «gère» les ressources (principalement l'allocation de mémoire) pour vous en utilisant le ramasse-miettes et en gardant les références aux objets opaques. Code non géréest le type de code qui vous oblige à allouer et désallouer manuellement de la mémoire, provoquant parfois des fuites de mémoire (lorsque vous oubliez de désallouer) et parfois des erreurs de segmentation (lorsque vous désallouez trop tôt). Unmanaged implique également généralement qu'il n'y a pas de vérifications au moment de l'exécution pour les erreurs courantes telles que le déréférencement du pointeur nul ou le dépassement des limites du tableau.

À proprement parler, la plupart des langages typés dynamiquement - tels que Perl, Python, PHP et Ruby - sont également du code géré . Cependant, ils ne sont pas généralement décrits comme tels, ce qui montre que le code managé est en fait un terme marketing pour désigner les environnements de programmation commerciaux très importants et sérieux (.NET et Java).

Code d'assemblage: ce terme fait généralement référence au type de code source que les gens écrivent lorsqu'ils veulent vraiment écrire du code octet. Un assembleur est un programme qui transforme ce code source en véritable byte-code. Ce n'est pas un compilateur car la transformation est 1 vers 1. Cependant, le terme est ambigu quant au type d'octet-code utilisé: il peut être géré ou non. S'il n'est pas géré, l'octet-code résultant est le code machine . S'il est géré, il en résulte le code d'octet utilisé en arrière-plan par un environnement virtuel tel que .NET. Le code managé (par exemple C #, Java) est compilé dans ce langage spécial d'octet-code, qui dans le cas de .NET est appelé Common Intermediate Language (CIL) et en Java est appelé Java byte-code. Il est généralement peu nécessaire que le programmeur commun accède à ce code ou écrive directement dans ce langage, mais lorsque les gens le font, ils y font souvent référence en tant que code d'assemblage car ils utilisent un assembleur pour le transformer en byte-code.

Timwi
la source
C ++ peut compiler en code machine, mais il est très souvent compilé dans d'autres formats comme exe qui s'exécutera avec un système d'exploitation.
Gordon Gustafson
Il existe des langages qui prennent en charge le garbage collection et les références opaques qui se compilent généralement en code machine. Les implémentations les plus sérieuses de Common Lisp le font. Ce que vous dites peut être vrai des langages pris en charge par Microsoft, mais il y a plus de langages compilés que ceux pris en charge par Visual Studio.
David Thornley
3
@CrazyJugglerDrummer: Le code contenu dans les fichiers EXE générés par les compilateurs C ++ est toujours du code machine. @David Thornley: J'ai mentionné beaucoup plus de langues que celles-là, mais je ne voulais pas compliquer les choses en mentionnant chaque bizarrerie obscure.
Timwi
Certains compilateurs, beaucoup, vont en fait compiler à partir de C / C ++ ou d'autres langages vers le langage assembleur puis appeler l'assembleur et l'assembleur le transforme en fichiers objets qui sont principalement du code machine mais ont besoin de quelques touches avant de pouvoir entrer en mémoire sur le processeur puis l'éditeur de liens relie tout cela à la version de code machine du programme. Le point étant C / C ++, etc., ne compile souvent pas directement en code machine, il est invisible pour l'utilisateur qui effectue deux ou trois étapes en cours de route. TCC, par exemple, est une exception à cela, il va directement au code machine.
old_timer
Cela ressemble à de la pinaillerie, mais tous les assembleurs ne traduisent pas 1-1 en opcodes. En fait, de nombreux assembleurs modernes prennent en charge les constructions d'abstraction comme les classes. Exemple: TASM, l'assembleur de Borland. en.wikipedia.org/wiki/TASM
Prime
45

Ce que vous voyez lorsque vous utilisez Debug + Windows + Disassembly lors du débogage d'un programme C # est un bon guide pour ces termes. Voici une version annotée de celui-ci lorsque je compile un programme `` hello world '' écrit en C # dans la configuration Release avec l'optimisation JIT activée:

        static void Main(string[] args) {
            Console.WriteLine("Hello world");
00000000 55                push        ebp                           ; save stack frame pointer
00000001 8B EC             mov         ebp,esp                       ; setup current frame
00000003 E8 30 BE 03 6F    call        6F03BE38                      ; Console.Out property getter
00000008 8B C8             mov         ecx,eax                       ; setup "this"
0000000a 8B 15 88 20 BD 02 mov         edx,dword ptr ds:[02BD2088h]  ; arg = "Hello world"
00000010 8B 01             mov         eax,dword ptr [ecx]           ; TextWriter reference
00000012 FF 90 D8 00 00 00 call        dword ptr [eax+000000D8h]     ; TextWriter.WriteLine()
00000018 5D                pop         ebp                           ; restore stack frame pointer
        }
00000019 C3                ret                                       ; done, return

Cliquez avec le bouton droit sur la fenêtre et cochez "Afficher les octets de code" pour obtenir un affichage similaire.

La colonne de gauche correspond à l'adresse du code machine. Sa valeur est truquée par le débogueur, le code est en fait situé ailleurs. Mais cela peut être n'importe où, en fonction de l'emplacement sélectionné par le compilateur JIT, de sorte que le débogueur commence simplement à numéroter les adresses à partir de 0 au début de la méthode.

La deuxième colonne est le code machine . Les 1 et 0 réels exécutés par la CPU. Le code machine, comme ici, est généralement affiché en hexadécimal. Peut-être que 0x8B sélectionne l'instruction MOV, les octets supplémentaires sont là pour indiquer exactement au CPU ce qui doit être déplacé. Notez également les deux saveurs de l'instruction CALL, 0xE8 est l'appel direct, 0xFF est l'instruction d'appel indirect.

La troisième colonne est le code d'assemblage . L'assembly est un langage simple, conçu pour faciliter l'écriture de code machine. Il se compare à C # en cours de compilation en IL. Le compilateur utilisé pour traduire le code d'assembly est appelé un «assembleur». Vous avez probablement l'assembleur Microsoft sur votre machine, son nom d'exécutable est ml.exe, ml64.exe pour la version 64 bits. Il existe deux versions courantes des langages d'assemblage en cours d'utilisation. Celui que vous voyez est celui qu'utilisent Intel et AMD. Dans le monde open source, l'assemblage dans la notation AT&T est courant. La syntaxe du langage dépend fortement du type de CPU pour lequel il a été écrit, le langage d'assemblage d'un PowerPC est très différent.

D'accord, cela aborde deux des termes de votre question. «Code natif» est un terme flou, il n'est pas rarement utilisé pour décrire du code dans un langage non managé. Il est peut-être instructif de voir quel type de code machine est généré par un compilateur C. Voici la version «bonjour le monde» en C:

int _tmain(int argc, _TCHAR* argv[])
{
00401010 55               push        ebp  
00401011 8B EC            mov         ebp,esp 
    printf("Hello world");
00401013 68 6C 6C 45 00   push        offset ___xt_z+128h (456C6Ch) 
00401018 E8 13 00 00 00   call        printf (401030h) 
0040101D 83 C4 04         add         esp,4 
    return 0;
00401020 33 C0            xor         eax,eax 
}
00401022 5D               pop         ebp  
00401023 C3               ret   

Je ne l'ai pas annoté, principalement parce qu'il est tellement similaire au code machine généré par le programme C #. L'appel de la fonction printf () est assez différent de l'appel de Console.WriteLine () mais tout le reste est à peu près identique. Notez également que le débogueur génère maintenant l'adresse de code machine réelle et qu'il est un peu plus intelligent sur les symboles. Un effet secondaire de la génération d'informations de débogage après la génération de code machine comme le font souvent les compilateurs non gérés. Je dois également mentionner que j'ai désactivé quelques options d'optimisation du code machine pour rendre le code machine similaire. Les compilateurs C / C ++ ont beaucoup plus de temps disponible pour optimiser le code, le résultat est souvent difficile à interpréter. Et très difficile à déboguer.

Le point clé ici est qu'il existe très peu de différences entre le code machine généré à partir d'un langage managé par le compilateur JIT et le code machine généré par un compilateur de code natif. C'est la principale raison pour laquelle le langage C # peut être compétitif avec un compilateur de code natif. La seule vraie différence entre eux réside dans les appels de fonction de support. Beaucoup d'entre eux sont mis en œuvre dans le CLR. Et cela tourne principalement autour du ramasse-miettes.

Hans Passant
la source
6

Le code natif et le code machine sont la même chose - les octets réels exécutés par le CPU.

Le code d'assemblage a deux significations: l'une est le code machine traduit dans une forme plus lisible par l'homme (avec les octets des instructions traduits en de courts mnémoniques de type mot comme "JMP" (qui "saute" à un autre endroit du code). L'autre est le bytecode IL (octets d'instructions générés par des compilateurs comme C # ou VB, qui finiront par être traduits en code machine, mais ne le sont pas encore) qui se trouvent dans une DLL ou un EXE.

cHao
la source
2

Dans .NET, les assemblys contiennent du code MS Intermediate Language (MSIL, parfois CIL).
C'est comme un code machine «de haut niveau».

Une fois chargé, MSIL est compilé par le compilateur JIT en code natif (code machine Intel x86 ou x64).

Henk Holterman
la source