it-swarm.com.de

Was bedeutet "Zur Kompilierungszeit zugewiesener Speicher" wirklich?

In Programmiersprachen wie C und C++ wird häufig auf die statische und dynamische Speicherzuweisung verwiesen. Ich verstehe das Konzept, aber der Satz "Der gesamte Speicher wurde während der Kompilierungszeit zugewiesen (reserviert)" verwirrt mich immer.

Soweit ich weiß, konvertiert die Kompilierung C/C++ - Code auf hoher Ebene in Maschinensprache und gibt eine ausführbare Datei aus. Wie wird Speicher in einer kompilierten Datei "zugewiesen"? Wird der Arbeitsspeicher nicht immer im RAM mit allen virtuellen Speicherverwaltungselementen zugewiesen?

Ist Speicherzuordnung nicht per Definition ein Laufzeitkonzept?

Wenn ich in meinem C/C++ - Code eine statisch zugewiesene 1-KB-Variable erstelle, erhöht dies die Größe der ausführbaren Datei um den gleichen Betrag?

Dies ist eine der Seiten, auf denen der Ausdruck unter der Überschrift "Statische Zuordnung" verwendet wird.

Zurück zu den Grundlagen: Speicherzuordnung, einen Spaziergang durch den Verlauf

146
Talha Sayed

Zur Kompilierungszeit zugewiesener Speicher bedeutet, dass der Compiler zur Kompilierungszeit eine Auflösung vornimmt, bei der bestimmte Dinge in der Prozessspeicherzuordnung zugewiesen werden.

Betrachten Sie beispielsweise ein globales Array:

int array[100];

Der Compiler kennt zur Kompilierungszeit die Größe des Arrays und die Größe eines int, sodass er zur Kompilierungszeit die gesamte Größe des Arrays kennt. Außerdem hat eine globale Variable standardmäßig eine statische Speicherdauer: Sie wird im statischen Speicherbereich des Prozessspeicherbereichs (Abschnitt .data/.bss) zugewiesen. Angesichts dieser Informationen entscheidet der Compiler während der Kompilierung, unter welcher Adresse dieses statischen Speicherbereichs das Array sein soll.

Natürlich sind diese Speicheradressen virtuelle Adressen. Das Programm geht davon aus, dass es über einen eigenen Speicherplatz verfügt (z. B. von 0x00000000 bis 0xFFFFFFFF). Aus diesem Grund könnte der Compiler Annahmen wie "Okay, das Array befindet sich unter der Adresse 0x00A33211" treffen. Zur Laufzeit werden diese Adressen von MMU und OS. in Real-/Hardwareadressen übersetzt

Wertinitialisierte statische Speichersachen sind etwas unterschiedlich. Beispielsweise:

int array[] = { 1 , 2 , 3 , 4 };

In unserem ersten Beispiel hat der Compiler nur entschieden, wo das Array zugeordnet wird, und diese Informationen in der ausführbaren Datei gespeichert.
Bei wertinitialisierten Dingen fügt der Compiler auch den Anfangswert des Arrays in die ausführbare Datei ein und fügt Code hinzu, der dem Programmlader mitteilt, dass das Array nach der Arrayzuweisung beim Programmstart gefüllt werden soll mit diesen Werten.

Hier sind zwei Beispiele für die vom Compiler generierte Assembly (GCC4.8.1 mit x86-Ziel):

C++ Code:

int a[4];
int b[] = { 1 , 2 , 3 , 4 };

int main()
{}

Ausgabebaugruppe:

a:
    .zero   16
b:
    .long   1
    .long   2
    .long   3
    .long   4
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret

Wie Sie sehen, werden die Werte direkt in die Assembly eingefügt. Im Array a generiert der Compiler eine Nullinitialisierung von 16 Bytes, da der Standard festlegt, dass statisch gespeicherte Objekte standardmäßig auf Null initialisiert werden sollen:

8.5.9 (Initialisierer) [Anmerkung]:
Jedes Objekt mit statischer Speicherdauer wird beim Programmstart auf Null gesetzt, bevor eine andere Initialisierung stattfindet. In einigen Fällen erfolgt die zusätzliche Initialisierung später.

Ich empfehle den Leuten immer, ihren Code zu zerlegen, um zu sehen, was der Compiler wirklich mit dem C++ - Code macht. Dies gilt von Speicherklassen/Dauer (wie diese Frage) bis hin zu erweiterten Compiler-Optimierungen. Sie könnten Ihren Compiler anweisen, die Assembly zu generieren, aber es gibt wunderbare Tools, um dies im Internet auf freundliche Weise zu tun. Mein Favorit ist GCC Explorer .

174
Manu343726

Zur Kompilierungszeit zugewiesener Speicher bedeutet einfach, dass zur Laufzeit keine weitere Zuordnung erfolgt - keine Aufrufe von malloc, new oder anderen dynamischen Zuordnungsmethoden. Sie haben eine feste Speicherauslastung, auch wenn Sie nicht die ganze Zeit über den gesamten Speicher benötigen.

Ist Speicherzuordnung nicht per Definition ein Laufzeitkonzept?

Der Speicher ist nicht in Benutzung vor der Laufzeit, sondern wird unmittelbar vor dem Start der Ausführung vom System zugewiesen.

Wenn ich in meinem C/C++ - Code eine statisch zugewiesene 1-KB-Variable erstelle, erhöht dies die Größe der ausführbaren Datei um den gleichen Betrag?

Durch das einfache Deklarieren der Statik wird die Größe Ihrer ausführbaren Datei nicht um mehr als einige Bytes erhöht. Wenn Sie ihn mit einem Anfangswert ungleich Null deklarieren, wird dieser Anfangswert beibehalten. Der Linker addiert diese 1 KB einfach zu dem Speicherbedarf, den der Loader des Systems unmittelbar vor der Ausführung für Sie erstellt.

26
mah

In der Kompilierzeit zugewiesener Speicher bedeutet, dass beim Laden des Programms ein Teil des Speichers sofort zugewiesen wird und die Größe und (relative) Position dieser Zuordnung zur Kompilierzeit bestimmt wird.

char a[32];
char b;
char c;

Diese 3 Variablen werden "zur Kompilierzeit zugewiesen". Dies bedeutet, dass der Compiler ihre Größe (die festgelegt ist) zur Kompilierzeit berechnet. Die Variable a ist ein Versatz im Speicher, der beispielsweise auf die Adresse 0 zeigt, b zeigt auf die Adresse 33 und c auf 34 (vorausgesetzt, keine Ausrichtungsoptimierung). Also, das Zuweisen von 1 KB statischer Daten erhöht nicht die Größe Ihres Codes, da es nur einen Versatz darin ändert. Der tatsächliche Speicherplatz wird zum Ladezeitpunkt zugewiesen.

Die tatsächliche Speicherzuweisung erfolgt immer zur Laufzeit, da der Kernel den Überblick behalten und seine internen Datenstrukturen aktualisieren muss (wie viel Speicher für jeden Prozess, jede Seite usw. zugewiesen wird). Der Unterschied besteht darin, dass der Compiler bereits die Größe der einzelnen Daten kennt, die Sie verwenden werden, und diese werden zugewiesen, sobald Ihr Programm ausgeführt wird.

Denken Sie auch daran, dass es sich um relative Adressen handelt. Die tatsächliche Adresse, an der sich die Variable befindet, ist unterschiedlich. Während des Ladevorgangs reserviert der Kernel etwas Speicher für den Prozess, zum Beispiel an der Adresse x, und alle fest codierten Adressen in der ausführbaren Datei werden um x Byte erhöht, so dass diese Variable a im Beispiel befindet sich unter der Adresse x, b unter der Adresse x+33 und so weiter.

23
fede1024

Das Hinzufügen von Variablen auf dem Stack, die N Bytes belegen, erhöht die Größe des Bins nicht (notwendigerweise) um N Bytes. Tatsächlich werden die meiste Zeit nur ein paar Bytes hinzugefügt.
Beginnen wir mit einem Beispiel, wie Sie Ihrem Code 1000 Zeichen hinzufügen wird die Größe des Fachs linear erhöhen.

Wenn die 1k eine Zeichenfolge mit tausend Zeichen ist, wird dies wie folgt deklariert

const char *c_string = "Here goes a thousand chars...999";//implicit \0 at end

und wenn Sie dann vim your_compiled_bin wären, könnten Sie diese Zeichenfolge tatsächlich irgendwo in der Tonne sehen. In diesem Fall ja: Die ausführbare Datei wird 1 KB größer, da sie die Zeichenfolge vollständig enthält.
Wenn Sie jedoch ein Array von ints, chars oder longs auf dem Stapel zuweisen und es in einer Schleife zuweisen, etwas in dieser Richtung

int big_arr[1000];
for (int i=0;i<1000;++i) big_arr[i] = some_computation_func(i);

dann nein: es erhöht nicht den Behälter ... um 1000*sizeof(int)
Die Zuweisung zur Kompilierungszeit bedeutet, was Sie inzwischen verstanden haben (basierend auf Ihren Kommentaren): Die kompilierte Bin enthält Informationen, die das System benötigt, um zu wissen, wie viel Speicher welche Funktion/welcher Block benötigt, wenn sie ausgeführt wird zusammen mit Informationen zur Stapelgröße, die Ihre Anwendung benötigt. Das teilt das System zu, wenn es Ihren Bin ausführt und Ihr Programm zu einem Prozess wird (nun, die Ausführung Ihres Bin ist der Prozess, der ... nun, Sie verstehen, was ich sage).
Natürlich male ich hier nicht das ganze Bild: Der Behälter enthält Informationen darüber, wie viel Stapel der Behälter tatsächlich benötigt. Auf der Grundlage dieser Informationen (unter anderem) reserviert das System einen Teil des Speichers, den so genannten Stack, über den das Programm frei regieren kann. Der Stapelspeicher wird weiterhin vom System zugewiesen, wenn der Prozess (das Ergebnis Ihrer Bin-Ausführung) gestartet wird. Der Prozess verwaltet dann den Stapelspeicher für Sie. Wenn eine Funktion oder eine Schleife (ein beliebiger Blocktyp) aufgerufen/ausgeführt wird, werden die für diesen Block lokalen Variablen in den Stapel verschoben und entfernt (der Stapelspeicher ist "freigegeben" so zu speak) zur Verwendung durch andere Funktionen/Bausteine. Wenn Sie also int some_array[100] Deklarieren, werden dem Bin nur einige Bytes zusätzlicher Informationen hinzugefügt, die dem System mitteilen, dass die Funktion X 100*sizeof(int) + zusätzlichen Platz für die Buchhaltung benötigt.

17

Auf vielen Plattformen werden alle globalen oder statischen Zuordnungen in jedem Modul vom Compiler in drei oder weniger konsolidierten Zuordnungen zusammengefasst (eine für nicht initialisierte Daten (häufig als "bss" bezeichnet), eine für initialisierte beschreibbare Daten (häufig als "data" bezeichnet). ) und eine für konstante Daten ("const")) sowie alle globalen oder statischen Zuordnungen jedes Typs innerhalb eines Programms werden vom Linker zu einer globalen für jeden Typ konsolidiert. Angenommen, int ist vier Byte groß, dann weist ein Modul die folgenden statischen Zuordnungen auf:

int a;
const int b[6] = {1,2,3,4,5,6};
char c[200];
const int d = 23;
int e[4] = {1,2,3,4};
int f;

es würde dem Linker mitteilen, dass es 208 Bytes für bss, 16 Bytes für "data" und 28 Bytes für "const" benötigte. Ferner würde jeder Verweis auf eine Variable durch eine Bereichsauswahl und einen Versatz ersetzt, so dass a, b, c, d und e durch bss + 0, const + 0, bss + 4, const + 24, Daten ersetzt würden +0 oder bss + 204.

Wenn ein Programm verknüpft ist, werden alle BSS-Bereiche aller Module miteinander verkettet. Ebenso die Bereiche data und const. Für jedes Modul wird die Adresse aller bss-relativen Variablen um die Größe der bss-Bereiche aller vorhergehenden Module erhöht (ebenfalls mit data und const). Wenn der Linker fertig ist, hat jedes Programm eine BSS-Zuordnung, eine Datenzuordnung und eine CONST-Zuordnung.

Wenn ein Programm geladen wird, geschieht abhängig von der Plattform im Allgemeinen eines von vier Dingen:

  1. Die ausführbare Datei gibt an, wie viele Bytes für jede Art von Daten benötigt werden, und - für den initialisierten Datenbereich, in dem der ursprüngliche Inhalt gefunden werden kann. Es wird auch eine Liste aller Anweisungen enthalten, die eine BS-, Daten- oder Konstantenadresse verwenden. Das Betriebssystem oder der Loader weist jedem Bereich die entsprechende Menge an Speicherplatz zu und fügt dann die Startadresse dieses Bereichs zu jedem Befehl hinzu, der diesen benötigt.

  2. Das Betriebssystem weist einen Teil des Speichers zu, der alle drei Arten von Daten enthält, und weist der Anwendung einen Zeiger auf diesen Teil des Speichers zu. Jeder Code, der statische oder globale Daten verwendet, dereferenziert diese relativ zu diesem Zeiger (in vielen Fällen wird der Zeiger in einem Register für die Lebensdauer einer Anwendung gespeichert).

  3. Das Betriebssystem weist der Anwendung zunächst keinen Speicher zu, mit Ausnahme des Binärcodes. Als Erstes fordert die Anwendung jedoch eine geeignete Zuweisung vom Betriebssystem an, die sie jedoch nicht in einem Register führen wird.

  4. Das Betriebssystem weist der Anwendung zunächst keinen Speicherplatz zu, die Anwendung fordert jedoch beim Start eine geeignete Zuweisung an (siehe oben). Die Anwendung enthält eine Liste von Anweisungen mit Adressen, die aktualisiert werden müssen, um anzuzeigen, wo der Speicher zugewiesen wurde (wie beim ersten Stil). Statt jedoch die Anwendung vom Betriebssystem-Ladeprogramm zu patchen, enthält die Anwendung genügend Code, um sich selbst zu patchen .

Alle vier Ansätze haben Vor- und Nachteile. In jedem Fall konsolidiert der Compiler jedoch eine beliebige Anzahl statischer Variablen in einer festgelegten kleinen Anzahl von Speicheranforderungen, und der Linker konsolidiert alle diese Variablen in einer kleinen Anzahl von konsolidierten Zuordnungen. Obwohl eine Anwendung einen Teil des Arbeitsspeichers vom Betriebssystem oder vom Loader erhalten muss, sind es der Compiler und der Linker, die dafür verantwortlich sind, allen einzelnen Variablen, die sie benötigen, einzelne Teile dieses großen Teils zuzuweisen.

15
supercat

Der Kern Ihrer Frage ist folgender: "Wie wird Speicher in einer kompilierten Datei" zugewiesen "? Wird der Speicher nicht immer im RAM mit dem gesamten virtuellen Speicherverwaltungsmaterial zugewiesen? Ist das nicht Speicher?" Zuweisung per definitionem ein Laufzeitkonzept? "

Ich denke, das Problem ist, dass es zwei verschiedene Konzepte bei der Speicherzuweisung gibt. Grundsätzlich ist die Speicherzuweisung der Vorgang, bei dem wir sagen, "dieser Datenposten wird in diesem bestimmten Speicherblock gespeichert". In einem modernen Computersystem umfasst dies einen zweistufigen Prozess:

  • Einige Systeme werden verwendet, um die virtuelle Adresse zu bestimmen, unter der der Artikel gespeichert wird
  • Die virtuelle Adresse wird einer physischen Adresse zugeordnet

Der letztgenannte Vorgang ist eine reine Laufzeit, der erstgenannte Vorgang kann jedoch zur Kompilierungszeit ausgeführt werden, wenn die Daten eine bekannte Größe haben und eine feste Anzahl von ihnen erforderlich ist. Im Grunde funktioniert es so:

  • Der Compiler sieht eine Quelldatei mit einer Zeile, die ungefähr so ​​aussieht:

    int c;
    
  • Es erzeugt eine Ausgabe für den Assembler, die ihn anweist, Speicher für die Variable 'c' zu reservieren. Das könnte so aussehen:

    global _c
    section .bss
    _c: resb 4
    
  • Wenn der Assembler ausgeführt wird, speichert er einen Zähler, der die Offsets jedes Elements ab dem Beginn eines Speicher- "Segments" (oder "Abschnitts") verfolgt. Dies ist wie die Teile einer sehr großen 'Struktur', die alles in der gesamten Datei enthält und der zu diesem Zeitpunkt kein tatsächlicher Speicher zugewiesen ist und die sich an einem beliebigen Ort befinden könnte. In einer Tabelle wird angegeben, dass _c Einen bestimmten Versatz hat (z. B. 510 Bytes vom Beginn des Segments) und dann seinen Zähler um 4 inkrementiert, sodass die nächste solche Variable bei (z. B.) 514 Bytes liegt. Für jeden Code, der die Adresse von _c Benötigt, wird nur 510 in die Ausgabedatei eingefügt und ein Hinweis hinzugefügt, dass die Ausgabe die Adresse des Segments benötigt, das _c Enthält, das später hinzugefügt wird.

  • Der Linker nimmt alle Ausgabedateien des Assemblers und untersucht sie. Es bestimmt eine Adresse für jedes Segment, so dass sie sich nicht überlappen, und fügt die erforderlichen Offsets hinzu, damit die Anweisungen weiterhin auf die richtigen Datenelemente verweisen. Bei nicht initialisiertem Speicher wie dem, der von c belegt wird (dem Assembler wurde mitgeteilt, dass der Speicher nicht initialisiert werden würde, weil der Compiler ihn in das Segment '.bss' gestellt hat, das ein Name ist, der für nicht initialisiert reserviert ist Memory) enthält ein Header-Feld in seiner Ausgabe, das dem Betriebssystem mitteilt, wie viel reserviert werden muss. Es kann verschoben werden (und ist es normalerweise), ist jedoch normalerweise so konzipiert, dass es an einer bestimmten Speicheradresse effizienter geladen wird, und das Betriebssystem versucht, es an dieser Adresse zu laden. Zum jetzigen Zeitpunkt haben wir eine ziemlich gute Vorstellung davon, welche virtuelle Adresse von c verwendet wird.

  • Die physikalische Adresse wird erst dann ermittelt, wenn das Programm ausgeführt wird. Aus Sicht des Programmierers ist die physikalische Adresse jedoch irrelevant - wir werden nie herausfinden, was es ist, da das Betriebssystem normalerweise niemandem sagt, dass es sich häufig ändern kann (auch während das Programm ausgeführt wird), und a Hauptzweck des Betriebssystems ist es, dies sowieso zu abstrahieren.

13
Jules

Eine ausführbare Datei beschreibt, welcher Speicherplatz für statische Variablen zugewiesen werden soll. Diese Zuordnung wird vom System vorgenommen, wenn Sie die ausführbare Datei ausführen. Ihre statische 1-KB-Variable erhöht die Größe der ausführbaren Datei also nicht um 1 KB:

static char[1024];

Es sei denn, Sie geben einen Initialisierer an:

static char[1024] = { 1, 2, 3, 4, ... };

Zusätzlich zur Maschinensprache (d. H. CPU-Anweisungen) enthält eine ausführbare Datei eine Beschreibung des erforderlichen Speicherlayouts.

9
meaning-matters

Der Speicher kann auf verschiedene Arten zugewiesen werden:

  • im Anwendungsheap (der gesamte Heap wird Ihrer App vom Betriebssystem beim Programmstart zugewiesen)
  • im Betriebssystem-Heap (damit man immer mehr greifen kann)
  • im Müllsammler kontrollierter Haufen (wie oben)
  • auf Stapel (so dass ein Stapelüberlauf auftreten kann)
  • reserviert im Code/Daten-Segment Ihrer Binärdatei (ausführbar)
  • an einem entfernten Ort (Datei, Netzwerk - und Sie erhalten ein Handle, kein Zeiger auf diesen Speicher)

Nun ist Ihre Frage, was "Speicher zur Kompilierungszeit reserviert" ist. Auf jeden Fall handelt es sich nur um eine falsch formulierte Redewendung, die sich entweder auf die Zuweisung von Binärsegmenten oder Stapeln oder sogar auf eine Heap-Zuweisung beziehen soll. In diesem Fall wird die Zuweisung jedoch durch einen unsichtbaren Konstruktoraufruf vor den Augen des Programmierers verborgen. Oder wahrscheinlich die Person, die gesagt hat, dass nur gesagt werden soll, dass der Speicher nicht auf Heap reserviert ist, aber nichts über Stapel- oder Segmentzuweisungen wusste (oder nicht auf diese Art von Details eingehen wollte).

In den meisten Fällen möchte die Person jedoch nur sagen, dass die zugewiesene Speichermenge ist zur Kompilierungszeit bekannt.

Die Binärgröße ändert sich nur, wenn der Speicher im Code- oder Datensegment Ihrer App reserviert ist.

5
exebook

Du hast recht. Der Speicher wird tatsächlich zum Ladezeitpunkt zugewiesen (ausgelagert), d. H. Wenn die ausführbare Datei in den (virtuellen) Speicher gebracht wird. In diesem Moment kann auch der Speicher initialisiert werden. Der Compiler erstellt nur eine Speicherzuordnung. [Übrigens werden Stapel- und Heapspeicher auch zum Ladezeitpunkt zugewiesen!]

4
Yves Daoust

Ich möchte diese Konzepte anhand weniger Diagramme erläutern.

Dies ist wahr, dass Speicher zur Kompilierungszeit sicher nicht zugewiesen werden kann. Aber was passiert dann eigentlich beim Kompilieren.

Hier kommt die Erklärung. Angenommen, ein Programm hat zum Beispiel vier Variablen x, y, z und k. Jetzt wird zur Kompilierungszeit einfach eine Speicherkarte erstellt, in der die Position dieser Variablen in Bezug zueinander ermittelt wird. Dieses Diagramm wird es besser veranschaulichen.

Nun stellen Sie sich vor, es läuft kein Programm im Speicher. Dies zeige ich durch ein großes leeres Rechteck.

empty field

Als nächstes wird die erste Instanz dieses Programms ausgeführt. Sie können es wie folgt visualisieren. Dies ist die Zeit, zu der tatsächlich Speicher zugewiesen wird.

first instance

Wenn die zweite Instanz dieses Programms ausgeführt wird, sieht der Speicher folgendermaßen aus.

second instance

Und der dritte ..

third instance

Und so weiter und so fort.

Ich hoffe, dass diese Visualisierung dieses Konzept gut erklärt.

2
user3258051

Wenn Sie die Assembly-Programmierung lernen, werden Sie feststellen, dass Sie Segmente für die Daten, den Stapel und den Code usw. ausschneiden müssen. Im Datensegment befinden sich Ihre Zeichenfolgen und Zahlen. Im Codesegment befindet sich Ihr Code. Diese Segmente sind in das ausführbare Programm integriert. Natürlich ist auch die Stapelgröße wichtig ... Sie möchten kein Stapelüberlauf!

Wenn Ihr Datensegment also 500 Byte umfasst, verfügt Ihr Programm über einen Bereich von 500 Byte. Wenn Sie das Datensegment in 1500 Bytes ändern, wird das Programm 1000 Bytes größer. Die Daten werden zum eigentlichen Programm zusammengefügt.

Dies ist, was passiert, wenn Sie übergeordnete Sprachen kompilieren. Der tatsächliche Datenbereich wird zugewiesen, wenn er in ein ausführbares Programm kompiliert wird, wodurch die Größe des Programms erhöht wird. Das Programm kann auch im laufenden Betrieb Speicher anfordern, und dies ist ein dynamischer Speicher. Sie können Speicher vom RAM anfordern und die CPU gibt ihn Ihnen zur Verwendung, Sie können ihn loslassen und Ihr Garbage Collector gibt ihn an die CPU zurück. Es kann sogar sein Bei Bedarf von einem guten Speichermanager auf eine Festplatte ausgetauscht Diese Funktionen bieten Ihnen Hochsprachen.

2
Engineer

Ich denke, Sie müssen einen Schritt zurücktreten. Zur Kompilierungszeit zugewiesener Speicher .... Was kann das bedeuten? Kann es bedeuten, dass Speicher auf Chips, die noch nicht hergestellt wurden, für Computer, die noch nicht entworfen wurden, irgendwie reserviert wird? Nein, Zeitreise, keine Compiler, die das Universum manipulieren können.

Es muss also bedeuten, dass der Compiler Anweisungen generiert, um diesen Speicher zur Laufzeit zuzuweisen. Wenn Sie es jedoch aus dem richtigen Winkel betrachten, generiert der Compiler alle Anweisungen. Woran kann das liegen? Der Unterschied besteht darin, dass der Compiler entscheidet und Ihr Code zur Laufzeit seine Entscheidungen nicht ändern oder modifizieren kann. Wenn zur Kompilierungszeit 50 Bytes benötigt wurden, können Sie zur Laufzeit nicht entscheiden, ob 60 Bytes zugewiesen werden sollen - diese Entscheidung wurde bereits getroffen.

2
jmoreno