Ich bin verwirrt über Maschinencode und nativen Code im Kontext von .NET-Sprachen.
Was ist der Unterschied zwischen ihnen? Sind sie gleich?
Ich bin verwirrt über Maschinencode und nativen Code im Kontext von .NET-Sprachen.
Was ist der Unterschied zwischen ihnen? Sind sie gleich?
Antworten:
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.
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.
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.
In .NET enthalten Assemblys MS Intermediate Language- Code (MSIL, manchmal CIL).
Es ist wie ein Maschinencode auf hoher Ebene.
Beim Laden wird MSIL vom JIT-Compiler in nativen Code (Intel x86- oder x64-Maschinencode) kompiliert.