Frage

Ich würde gerne wissen, warum die .o -Datei, die wir durch das Kompilieren einer .c -Datei erhalten, die "Hallo, Welt!" Kompiliert werden kann. Ist größer als eine Java .Class -Datei, die auch "Hallo, Welt!" Druckt?

War es hilfreich?

Lösung

Java verwendet Bytecode, um plattformunabhängig und "vorkompiliert" zu sein, aber Bytecode wird von Interpreter verwendet und wird als kompakt genug serviert. Es ist also nicht derselbe wie der Maschinencode, den Sie im kompilierten C -Programm sehen können. Schauen Sie sich einfach den vollen Prozess der Java -Zusammenstellung an:

Java program  
-> Bytecode   
  -> High-level Intermediate Representation (HIR)   
    -> Middle-level Intermediate Representation (MIR)   
      -> Low-level Intermediate Representation (LIR)  
        -> Register allocation
          -> EMIT (Machine Code)

Dies ist die Kette für Java -Programm zur Transformation von Maschinencode. Wie Sie sehen, ist Bytecode weit weg vom Maschinencode. Ich kann im Internet gute Dinge nicht finden, um Ihnen diese Straße auf dem realen Programm (ein Beispiel) zu zeigen, alles, was ich gefunden habe, ist diese Präsentation, Hier können Sie sehen, wie jeder Schritte die Codepräsentation ändert. Ich hoffe, es beantwortet Ihnen, wie und warum das C -Programm und Java -Bytecode zusammengestellt wurden.

AKTUALISIEREN:Alle Schritte, die nach "Bytecode" sind, werden von JVM in der Laufzeit abhängig von seiner Entscheidung, diesen Code zu kompilieren

Endlich ein gutes Beispiel gefunden, genommen von Linear Scan Register Allocation für den Java Hotspot ™ Client -Compiler (Übrigens gute Lektüre, um zu verstehen, was in JVM vor sich geht). Stellen Sie sich vor, wir haben ein Java -Programm:

public static void fibonacci() {
  int lo = 0;
  int hi = 1;
  while (hi < 10000) {
    hi = hi + lo;
    lo = hi - lo;
    print(lo);
  }
}

Dann ist sein Bytecode:

0:  iconst_0
1:  istore_0 // lo = 0
2:  iconst_1
3:  istore_1 // hi = 1
4:  iload_1
5:  sipush 10000
8:  if_icmpge 26 // while (hi < 10000)
11: iload_1
12: iload_0
13: iadd
14: istore_1 // hi = hi + lo
15: iload_1
16: iload_0
17: isub
18: istore_0 // lo = hi - lo
19: iload_0
20: invokestatic #12 // print(lo)
23: goto 4 // end of while-loop
26: return

Jeder Befehl dauert 1 Byte (JVM unterstützt 256 Befehle, hat jedoch tatsächlich weniger als diese Zahl) + Argumente. Zusammen braucht es 27 Bytes. Ich lasse alle Phasen weg und hier ist bereit, den Maschinencode auszuführen:

00000000: mov dword ptr [esp-3000h], eax
00000007: push ebp
00000008: mov ebp, esp
0000000a: sub esp, 18h
0000000d: mov esi, 1h
00000012: mov edi, 0h
00000017: nop
00000018: cmp esi, 2710h
0000001e: jge 00000049
00000024: add esi, edi
00000026: mov ebx, esi
00000028: sub ebx, edi
0000002a: mov dword ptr [esp], ebx
0000002d: mov dword ptr [ebp-8h], ebx
00000030: mov dword ptr [ebp-4h], esi
00000033: call 00a50d40
00000038: mov esi, dword ptr [ebp-4h]
0000003b: mov edi, dword ptr [ebp-8h]
0000003e: test dword ptr [370000h], eax
00000044: jmp 00000018
00000049: mov esp, ebp
0000004b: pop ebp
0000004c: test dword ptr [370000h], eax
00000052: ret

Es dauert 83 (52 in Hex + 1 -Byte) Bytes.

Ps. Ich berücksichtige nicht die Verknüpfung (wurde von anderen erwähnt) sowie CompiledC- und Bytecode -Dateiheader (wahrscheinlich sind sie auch anders; Ich weiß nicht, wie es mit C ist, aber in der Bytecode -Datei werden alle Zeichenfolgen verschoben Spezieller Header -Pool und im Programm wird seine "Position" in Header usw. verwendet.)

Update2: Wahrscheinlich ist es wert, dass Java mit Stack (istore/iload -Befehl) arbeitet, obwohl der Maschinencode basierend auf X86 und den meisten anderen Plattform mit Registern funktioniert. Wie Sie sehen können, ist der Maschinencode "voll" von Registern ", was dem kompilierten Programm im Vergleich mit einfacheren Stack-basierten Bytecode eine zusätzliche Größe verleiht.

Andere Tipps

Die Hauptursache für die Größe in der Größe in diesem Fall ist Unterschied in den Dateiformaten. Für ein so kleines Programmformat des ELF (.o) Die Datei führt einen ernsthaften Aufwand in Bezug auf den Platz ein.

Zum Beispiel mein Beispiel .o Datei des Programms "Hallo, Welt" nimmt an 864 Bytes. Es besteht aus (untersucht mit readelf Befehl):

  • 52 Bytes des Dateiheaders
  • 440 Bytes der Abschnittsüberschriften (40 Bytes x 11 Abschnitte)
  • 81 Bytes von Abschnittsnamen
  • 160 Bytes Symboltabelle
  • 43 Bytes Code
  • 14 Bytes von Daten (Hello, world\n\0)
  • etc

.class Die Datei des ähnlichen Programms dauert nur 415 Bytes, Trotz der Tatsache, dass es mehr Symbolnamen enthält und diese Namen lang sind. Es besteht aus (untersucht mit Java -Klassenzuschauer):

  • 289 Bytes des konstanten Pools (enthält Konstanten, Symbolnamen usw.)
  • 94 Bytes der Methode Tabelle (Code)
  • 8 Bytes der Attributtabelle (Quelldateiname Referenz)
  • 24 Bytes von Header mit fester Größe

Siehe auch:

C-Programme, obwohl sie mit nativem Maschinencode zusammengestellt werden, der auf Ihrem Prozessor ausgeführt wird (natürlich über das Betriebssystem versandt), müssen tendenziell viel Einrichten und Zerreißen für das Betriebssystem benötigen und dynamisch verknüpft laden Bibliotheken wie die C -Bibliothek usw.

Java hingegen kompiliert nach Bytecode für eine virtuelle Plattform (im Grunde genommen ein simuliertes Computer-im-Computer), der speziell neben Java selbst entworfen wurde, so dass ein Großteil dieses Overheads (wenn es überhaupt erforderlich wäre Der Code und die VM-Schnittstelle sind gut definiert) können in die VM selbst verschoben werden, sodass der Programmcode schlank ist.

Es variiert jedoch von Compiler-zu-Compiler, und es gibt mehrere Optionen, um ihn zu reduzieren oder Code unterschiedlich zu erstellen, was unterschiedliche Auswirkungen hat.

All das heißt, es ist nicht wirklich so wichtig.

Kurz gesagt: Java -Programme werden mit dem Java -Byte -Code zusammengestellt, für den ein separater Interpreter (Java Virtual Machine) ausgeführt werden muss.

Es gibt keine 100% ige Garantie dafür, dass die vom C-Kompiler erstellte .o-Datei kleiner ist als die vom Java-Compiler erstellte .class-Datei. Es hängt alles von der Umsetzung des Compilers ab.

Einer der Hauptgründe für Unterschiede in den Größen von .o und .class Dateien ist, dass Java-Bytecodes etwas höher sind als Maschinenanweisungen. Natürlich nicht äußerst höher-es ist immer noch ziemlich niedriges Zeug-, aber das wird einen Unterschied machen, weil es effektiv dazu dient, das zu komprimieren ganz Programm. (Sowohl C- als auch Java -Code können dort einen Startcode haben.)

Ein weiterer Unterschied besteht darin, dass Java -Klassendateien häufig relativ kleine Funktionen der Funktionalität darstellen. Während es möglich ist, C -Objektdateien zu haben, die noch kleinere Teile abbilden, ist es häufig häufiger, mehr (verwandte) Funktionen in eine einzelne Datei zu stecken. Die Unterschiede in den Scoping-Regeln können auch dazu dienen, dies zu betonen (C hat wirklich nichts, was dem Umfang auf Modulebene entspricht, sondern stattdessen Dateiebene auf Dateiebene enthält; Javas Paketumfang funktioniert über mehrere Klassendateien hinweg). Sie erhalten eine bessere Metrik, wenn Sie die Größe eines ganzen Programms vergleichen.

In Bezug auf "verknüpfte" Größen sind Java -ausführbare JAR -Dateien in der Regel kleiner (für eine bestimmte Funktionalität), da sie komprimiert werden. Es ist relativ selten, C -Programme in komprimierter Form zu liefern. (Es gibt auch Unterschiede in der Größe der Standardbibliothek, aber sie können genauso gut eine Wäsche sein, da C -Programme auf andere Bibliotheken als die vorhandene LIBC zählen können, und Java -Programme haben Zugang zu einer riesigen Standardbibliothek. Auswahl, wer den Vorteil hat ist unangenehm.)

Dann gibt es auch die Frage, dass Informationen debuggen. Wenn Sie ein C -Programm mit Debugging auf IO kompilieren, erhalten Sie viele Informationen zu Typen in der Standardbibliothek enthalten, nur weil es etwas zu umständlich ist, um es herauszufiltern. Der Java -Code verfügt nur über Debugging -Informationen über den tatsächlichen kompilierten Code, da er auf relevante Informationen in der Objektdatei zählen kann. Ändert dies die tatsächliche Größe des Codes? Nein. Aber es kann einen großen Einfluss auf die Dateigrößen haben.

Insgesamt würde es vermuten, dass es schwierig ist, die Größen von C- und Java -Programmen zu vergleichen. Oder besser gesagt, Sie können sie vergleichen und leicht nicht viel Nützliches lernen.

Die meisten (bis zu 90% für einfache Funktionen) einer Elfenformat .o Datei ist Junk. Für ein .o Datei mit einem einzelnen leeren Funktionskörper, Sie können einen Größenumbruch erwarten wie:

  • 1% Code
  • 9% Symbol und Verlagerungstabelle (wesentlich für die Verknüpfung)
  • 90% Header Overhead, nutzlose Version/Anbieter Notizen, die vom Compiler und/oder dem Assembler usw. gespeichert sind, etc.

Wenn Sie die reale Größe des kompilierten C -Codes sehen möchten, verwenden Sie die size Befehl.

Eine Klassendatei ist Java -Byte -Code.

Es ist höchstwahrscheinlich kleiner, da C/C ++ - Bibliotheken und Betriebssystembibliotheken mit dem Objektcode verknüpft sind, den der C ++ - Compiler erstellt, um schließlich eine ausführbare Binärdatei zu erstellen.

Einfach ausgedrückt ist es, als würde man den Java -Byte -Code mit einem von einem C -Compiler erzeugten Objektcode vergleichen, bevor er verknüpft ist, um ein Binärdatum zu erstellen. Der Unterschied ist die Tatsache, dass ein JVM den Java -Byte -Code interpretiert, um das zu tun, was das Programm tun soll, während C Informationen aus dem Betriebssystem benötigt, da das Betriebssystem als Dolmetscher fungiert.

Auch in C wird jedes Symbol (Funktionen usw.) in einer externen Bibliothek in einem der Objektdateien importiert. Wenn Sie es in mehreren Objektdateien verwenden, wird es weiterhin nur einmal importiert. Es gibt zwei Möglichkeiten, wie diese "Import" passieren kann. Bei statischer Verknüpfung wird der tatsächliche Code für eine Funktion in die ausführbare Datei kopiert. Dies erhöht die Dateigröße, hat jedoch den Vorteil, dass keine externen Bibliotheken (.dll/.so -Dateien) erforderlich sind. Bei der Dynamikverknüpfung ist dies nicht vorhanden, aber infolgedessen erfordert Ihr Programm zusätzliche Bibliotheken, die ausgeführt werden müssen.

In Java ist alles sozusagen dynamisch "verknüpft".

Java wird in eine maschinenunabhängige Sprache zusammengestellt. Dies bedeutet, dass es nach dem Zusammenot von der Java Virtual Machine (JVM) zur Laufzeit übersetzt wird. C wird in Maschinenanweisungen zusammengestellt und ist daher das gesamte Binärdatum für das Programm auf der Zielmaschine.

Da Java mit einer maschinenunabhängigen Sprache zusammengestellt ist, werden die spezifischen Details für eine bestimmte Maschine von der JVM behandelt. (IE C hat maschinell spezifische Overhead)

So denke ich sowieso darüber nach :-)

Einige potenzielle Gründe:

  • Die Java -Klassendatei enthält überhaupt keinen Initialisierungscode. Es hat nur Ihre einzige Klasse und eine Funktion - sehr klein. Im Vergleich dazu hat das C-Programm einen gewissen Grad an statisch verknüpften Initialisierungscode und möglicherweise DLL-Thunks.
  • Das C -Programm kann auch Abschnitte haben, die an Seitengrenzen ausgerichtet sind. Dies würde der Programmgröße mindestens 4 KB verleihen, um sicherzustellen, dass das Codesegment an einer Seitengrenze beginnt.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top