Was ist der Unterschied zwischen nativem Code, Maschinencode und Assemblycode?


105

Ich bin verwirrt über Maschinencode und nativen Code im Kontext von .NET-Sprachen.

Was ist der Unterschied zwischen ihnen? Sind sie gleich?


3
Ich habe eine Frage zu dieser Frage. Fällt diese Frage unter die Anforderungen von StackOverflow? afaik ist es nicht, aber gleichzeitig ist diese Art von Frage sehr hilfreich / informativ. Angenommen, diese Art von Frage ist nicht erlaubt, wo sollten wir diese Art von Fragen stellen, wenn nicht hier?
Yousuf Azad

Antworten:


149

Die Begriffe sind in der Tat etwas verwirrend, da sie manchmal inkonsistent verwendet werden.

Maschinencode: Dies ist der am besten definierte. Es ist Code, der die Bytecode-Anweisungen verwendet, die Ihr Prozessor (das physische Metallstück, das die eigentliche Arbeit erledigt) versteht und direkt ausführt. Alle anderen Codes müssen übersetzt oder in Maschinencode umgewandelt werden, bevor Ihre Maschine sie ausführen kann.

Native Code: Dieser Begriff wird manchmal an Stellen verwendet, an denen Maschinencode (siehe oben) gemeint ist. Manchmal wird es jedoch auch verwendet, um nicht verwalteten Code zu bezeichnen (siehe unten).

Nicht verwalteter Code und verwalteter Code: Nicht verwalteter Code bezieht sich auf Code, der in einer Programmiersprache wie C oder C ++ geschrieben wurde und direkt in Maschinencode kompiliert wird . Es steht im Gegensatz zu verwaltetem Code , der in C #, VB.NET, Java oder ähnlichem geschrieben und in einer virtuellen Umgebung (wie .NET oder JavaVM) ausgeführt wird, die einen Prozessor in Software „simuliert“. Der Hauptunterschied besteht darin, dass verwalteter Code die Ressourcen (hauptsächlich die Speicherzuordnung) für Sie „verwaltet“, indem er die Speicherbereinigung einsetzt und Verweise auf Objekte undurchsichtig hält. Nicht verwalteter CodeDies ist die Art von Code, bei der Sie den Speicher manuell zuweisen und die Zuordnung aufheben müssen. Dies führt manchmal zu Speicherlecks (wenn Sie die Zuordnung vergessen) und manchmal zu Segmentierungsfehlern (wenn Sie die Zuordnung zu früh aufheben). Unmanaged auch bedeutet , in der Regel gibt es keine Laufzeitprüfungen für häufige Fehler wie Null-Pointer - Dereferenzierung oder Arraygrenzen Überlauf.

Genau genommen sind die meisten dynamisch typisierten Sprachen - wie Perl, Python, PHP und Ruby - ebenfalls verwalteter Code . Sie werden jedoch nicht allgemein als solche beschrieben, was zeigt, dass verwalteter Code tatsächlich ein Marketingbegriff für die wirklich großen, seriösen kommerziellen Programmierumgebungen (.NET und Java) ist.

Assembler-Code: Dieser Begriff bezieht sich im Allgemeinen auf die Art von Quellcode, den Benutzer schreiben, wenn sie wirklich Bytecode schreiben möchten. Ein Assembler ist ein Programm, das diesen Quellcode in echten Bytecode umwandelt. Es ist kein Compiler, da die Transformation 1 zu 1 erfolgt. Der Begriff ist jedoch nicht eindeutig, welche Art von Bytecode verwendet wird: Er kann verwaltet oder nicht verwaltet werden. Wenn es nicht verwaltet wird, ist der resultierende Bytecode Maschinencode . Wenn es verwaltet wird, wird der Bytecode hinter den Kulissen von einer virtuellen Umgebung wie .NET verwendet. Verwalteter Code (z. B. C #, Java) wird in diese spezielle Bytecode-Sprache kompiliert, die im Fall von .NET als Common Intermediate Language (CIL) und in Java als Java-Bytecode bezeichnet wird. Normalerweise muss der normale Programmierer kaum auf diesen Code zugreifen oder direkt in diese Sprache schreiben. Wenn dies jedoch der Fall ist, wird er häufig als Assembler-Code bezeichnet, da er einen Assembler verwendet , um ihn in Byte-Code umzuwandeln.


C ++ kann in Maschinencode kompiliert werden, wird jedoch sehr oft in andere Formate wie exe kompiliert, die mit einem Betriebssystem ausgeführt werden.
Gordon Gustafson

Es gibt Sprachen, die Garbage Collection und undurchsichtige Referenzen unterstützen, die normalerweise mit Maschinencode kompiliert werden. Die ernsthaftesten Implementierungen von Common Lisp tun dies. Was Sie sagen, gilt möglicherweise für von Microsoft unterstützte Sprachen, aber es gibt mehr kompilierte Sprachen als von Visual Studio unterstützt werden.
David Thornley

3
@CrazyJugglerDrummer: Der Code in EXE-Dateien, die von C ++ - Compilern generiert werden, ist immer noch Maschinencode. @ David Thornley: Ich erwähnte deutlich mehr Sprachen als nur diese, aber ich wollte die Sache nicht komplizieren, indem ich jede obskure Kuriosität erwähnte.
Timwi

Einige Compiler, viele, kompilieren tatsächlich von C / C ++ oder anderen Sprachen in Assemblersprache und rufen dann den Assembler auf. Der Assembler wandelt ihn in Objektdateien um, die hauptsächlich Maschinencode sind, aber einige Berührungen benötigen, bevor sie dann auf dem Prozessor gespeichert werden können Der Linker verknüpft alles mit der Maschinencode-Version des Programms. Der Punkt C / C ++ usw. wird häufig nicht direkt zu Maschinencode kompiliert, der für den Benutzer unsichtbar ist. Er führt zwei oder drei Schritte auf dem Weg aus. Eine Ausnahme bildet beispielsweise TCC, das direkt zum Maschinencode wechselt.
old_timer

Dies fühlt sich wie Nitpicking an, aber nicht alle Assembler übersetzen 1-1 in Opcodes. Tatsächlich unterstützen viele moderne Assembler Abstraktionskonstrukte wie Klassen. Beispiel: TASM, Borlands Assembler. en.wikipedia.org/wiki/TASM
Prime

45

Was Sie sehen, wenn Sie Debug + Windows + Disassembly beim Debuggen eines C # -Programms verwenden, ist eine gute Anleitung für diese Begriffe. Hier ist eine kommentierte Version davon, wenn ich ein in C # geschriebenes 'Hallo Welt'-Programm in der Release-Konfiguration mit aktivierter JIT-Optimierung kompiliere:

        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

Klicken Sie mit der rechten Maustaste auf das Fenster und aktivieren Sie das Kontrollkästchen "Codebytes anzeigen", um eine ähnliche Anzeige zu erhalten.

Die linke Spalte ist die Maschinencode-Adresse. Sein Wert wird vom Debugger gefälscht, der Code befindet sich tatsächlich woanders. Dies kann jedoch überall sein, abhängig vom vom JIT-Compiler ausgewählten Speicherort. Daher beginnt der Debugger zu Beginn der Methode mit der Nummerierung der Adressen von 0.

Die zweite Spalte ist der Maschinencode . Die tatsächlichen Einsen und Nullen, die die CPU ausführt. Maschinencode wird wie hier üblicherweise hexadezimal angezeigt. Beispielhaft ist vielleicht, dass 0x8B den MOV-Befehl auswählt, die zusätzlichen Bytes sind da, um der CPU genau mitzuteilen, was verschoben werden muss. Beachten Sie auch die beiden Varianten des CALL-Befehls: 0xE8 ist der direkte Aufruf, 0xFF ist der indirekte Aufrufbefehl.

Die dritte Spalte ist der Assemblycode . Assembly ist eine einfache Sprache, die das Schreiben von Maschinencode erleichtert. Es ist vergleichbar mit C #, das zu IL kompiliert wird. Der zum Übersetzen von Assembler-Code verwendete Compiler wird als "Assembler" bezeichnet. Sie haben wahrscheinlich den Microsoft-Assembler auf Ihrem Computer. Der ausführbare Name lautet ml.exe und ml64.exe für die 64-Bit-Version. Es werden zwei gängige Versionen von Assemblersprachen verwendet. Das, was Sie sehen, wird von Intel und AMD verwendet. In der Open Source-Welt ist die Montage in der AT & T-Notation üblich. Die Sprachsyntax hängt stark von der Art der CPU ab, für die geschrieben wurde. Die Assemblersprache für einen PowerPC ist sehr unterschiedlich.

Okay, das behandelt zwei der Begriffe in Ihrer Frage. "Native Code" ist ein unscharfer Begriff. Er wird nicht selten verwendet, um Code in einer nicht verwalteten Sprache zu beschreiben. Es ist vielleicht lehrreich zu sehen, welche Art von Maschinencode von einem C-Compiler generiert wird. Dies ist die 'Hallo Welt'-Version in 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   

Ich habe es nicht kommentiert, hauptsächlich, weil es dem vom C # -Programm generierten Maschinencode so ähnlich ist . Der Funktionsaufruf printf () unterscheidet sich erheblich vom Aufruf Console.WriteLine (), aber alles andere ist ungefähr gleich. Beachten Sie auch, dass der Debugger jetzt die reale Maschinencode-Adresse generiert und dass Symbole etwas intelligenter sind. Ein Nebeneffekt beim Generieren von Debug-Informationen nach dem Generieren von Maschinencode, wie dies bei nicht verwalteten Compilern häufig der Fall ist. Ich sollte auch erwähnen, dass ich einige Optionen zur Optimierung des Maschinencodes deaktiviert habe, damit der Maschinencode ähnlich aussieht. C / C ++ - Compiler haben viel mehr Zeit, um Code zu optimieren. Das Ergebnis ist oft schwer zu interpretieren. Und sehr schwer zu debuggen.

Der entscheidende Punkt hierbei ist, dass es nur sehr wenige Unterschiede zwischen Maschinencode gibt, der vom JIT-Compiler aus einer verwalteten Sprache generiert wird, und Maschinencode, der vom nativen Code-Compiler generiert wird. Dies ist der Hauptgrund, warum die C # -Sprache mit einem nativen Code-Compiler konkurrieren kann. Der einzige wirkliche Unterschied zwischen ihnen sind die Support-Funktionsaufrufe. Viele davon sind in der CLR implementiert. Und das dreht sich hauptsächlich um den Müllsammler.


6

Native Code und Maschinencode sind dasselbe - die tatsächlichen Bytes, die die CPU ausführt.

Assembler-Code hat zwei Bedeutungen: Eine ist der Maschinencode, der in eine besser lesbare Form übersetzt wurde (wobei die Bytes für die Anweisungen in kurze wortähnliche Mnemoniken wie "JMP" übersetzt werden (die an eine andere Stelle im Code "springen"). Die andere ist der IL-Bytecode (Anweisungsbytes, die Compiler wie C # oder VB generieren, die schließlich in Maschinencode übersetzt werden, aber noch nicht), der in einer DLL oder EXE lebt.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.