Hallo,
ich bin auf der Suche nach einer Erklärung zu einem Assembler-Code wenn
ich ein Array oder eine Variable mit new Anlege. Bsp:
int *iVariable = new int[10];
sprich ich will ein Array mit 10 Elementen anlegen.
Assemblercode:
1
00CB14FE push 28h
2
00CB1500 call operator new[]
3
00CB1505 add esp, 4
4
00CB1508 mov dword ptr[ebp-110h], eax
5
00CB140E mov eax, dword ptr[ebp-110h]
6
00CB1514 mov dword ptr [iVariable], eax
So jetzt meine Fragen:
Wie wird wo welcher Speicher angelegt? (Größe? Heap? Stack?)
Was passiert bei Zeile 3 esp, 4?
Warum wird (Zeile 4 & 5) zuerst das Register in einen Pointer kopiert
und sofort danach der Pointer wieder in das Register zurückkopiert?
Ich weiß Assembler wird heute so gut wie nie benutzt aber wir sollen das
mal gesehen haben und verstehen was da passiert. Nach etlichen Stunden
der google/wikipedia/usw. Suche probier ich mein Glück mal hier in dem
Forum.
Ich hoffe ihr könnt mir dabei helfen.
MfG und vielen Dank im Vorraus
Andre Kurth schrieb:> Was passiert bei Zeile 3 esp, 4?
also wenn du schon asm lernen willst dann musst du schon etwas selber
dafür tun.
add esp, 4
was ist daran so schwer zu verstehen? auf Wert vom register esp werden 4
aufaddiert.
In C werden üblicherweise Werte, beim Aufruf einer Funktion, auf den
Stack abgelegt. Dann erfolgt der eigentliche Aufruf der Funktion.
Der Befehl: push 28h entspricht der Zahl 40 (10+4 Bytes).
Die eigentliche Speicherreservierung erfolgt in der Funktion "new".
Ein Zeiger auf den so reservierten Speicherbereich wird in irgendeinem
Register, wahrscheinlich eax, zurückgeliefert.
Also wie genau der Speicher reserviert wird, ist aus den Codestücken
nicht zu ersehen. Da müsstest Du dem call folgen.
Das hat alles mit den Aufrufkonventionen zu tun.
Wenn die Funktion aufgerufen wird, dann schreibst du in C++
int *iVariable = new int[10];
irgendwie müssen die 10 an die Funktion übergeben werden und irgendwie
muss die Funktion ein Ergebnis liefern, welches nachher in die Variable
bugsiert wird.
All das sind Schritte um die du dich auf C++ Ebene nicht kümmern musst.
Aber irgendwo, irgendwann von irgendeinem Code müssen sie durchgeführt
werden. Argumente wandern ja nicht irgendwie magisch zu den Funktionen
und Returnwerte von Funktionen landen auch nicht irgendwie magisch in
den Speicherzellen, die eine Variable repräsentieren (und die lokale
Variable muss auch irgendwann mal von irgendeinem Code angelegt worden
sein)
Die Fragestellung ist unstrukturiert, und die Überschrift verwirrend.
Grundsätzlich geht es um x86 Assembler. C++ ist die kompilierte Sprache.
Du betrachtest also vom C++-Compiler generierten Binärcode. Dieser wird
von vielen Faktoren bestimmt (Wahl des Compilers, Direktiven,
Optimierungsgrad, ...).
Genug gemeckert, jetzt zur Sache. esp ist der Stackpointer. Lokale
Variablen legen C-Compiler gerne auf dem Stack an. ebp ist das
Base-Register. Es dient den Compilern typischerweise als Backup für den
Stackpointer des höheren Kontexts. Für weitere Aussagen ist dein Auszug
zu begrenzt.
Andre Kurth schrieb:> Assemblercode:>>
hallo,
ich hätte meine Frage präziser stellen sollen:
Warum werden auf das Register ebp diese 4 byte dazuaddiert?
Wird dem Register dadurch gesagt das durch den operator new[] neuer
Speicher angelegt worden ist?
Ich hab mich schon ziemlich intensiv mit dem thema assembler
auseinandergesetzt, allerdings ist das mit dem Operator new[] noch nicht
ganz klar.
Vor allen dingen die letzten 3 Befehlszeilen des Assemblercodes sind für
mich noch rätselhaft.
MfG
Die dritte Zeile ist nötig, weil in der ersten Zeile ein Push steht.
Push "Zahl" legt die übergebene Zahl auf den Stapel ab. Dabei wird der
Stapelzeiger (esp) um 4 (4 Bytes) verringert.
Um den Stapel zu bereinigen, gibt es nach dem Call zwei Möglichkeiten:
1. Pop ebx
2. add esp,4
Die erste Möglichkeit zerstört aber das angegebene Register, welches
dann, im obigen Falle, mit 40 geladen würde.
Also wenn ich das jetzt richtig verstanden habe wird erst der Speicher
auf dem Stack angelegt (40 byte) und der Stackpointer wird um 4
verringert, anschließend wird durch den Aufruf des Operators new dieser
Speicher in dem new angelegt und darauffolgend der Stackspeicher um 4
erhöht damit der Stack wieder leer is und der Speicher nur noch im new
vorhanden ist?
28h ist der Parameter für die operator_new[] - Funktion. Jetzt müsste
man die Calling-Convention wissen, also ob die Funktion ihr Argument
selbst vom Stack holt, oder dort belässt. Ich schätze mal zweites. Das
heisst, mit dem add-Befehl entfernt der Caller den Parameter wieder vom
Stack.
Du hast nicht gepostet, wie die operator_new[]-Funktion aussieht. Ich
bezweifle, dass dort 40 byte auf dem Stack reserviert werden. Dann
könnte der Caller auch nicht einfach mit einem add-Befehl den alten
Parameter vom Stack nehmen. Außerdem wird im Anschluss das Ergebnis der
operator_new[]-Funktion im Speicher abgelegt. Ich vermute, die Funktion
gibt einen Zeiger zu einem neu allokierten Speicherbereich zurück.
Andre Kurth schrieb:> Also wenn ich das jetzt richtig verstanden habe wird erst der Speicher> auf dem Stack angelegt (40 byte) und der Stackpointer wird um 4> verringert, anschließend wird durch den Aufruf des Operators new dieser> Speicher in dem new angelegt und darauffolgend der Stackspeicher um 4> erhöht damit der Stack wieder leer is und der Speicher nur noch im new> vorhanden ist?
Die 4 haben im eigentlichen Sinne mit dem new nicht das geringste zu
tun. Es ist schlicht und ergreifend eine Aufrufkonvention.
* Der Aufrufer legt seine Argumente auf den Stack
* Der Aufgerufene arbeitet mit diesen Werten, ändert daran aber nichts.
Insbesondere hinterlässt er den Stack beim Return so, wie er ihn
vorgefunden hat
* Der Aufrufer ist dafür zuständig, den Stack wieder aufzuräumen.
Der Aufrufer hat mit dem push 4 Bytes auf den Stack gelegt und er ist
auch dafür zuständig, dass diese 4 Bytes wieder aus dem Stack
verschwinden.
Andre Kurth schrieb:> Also wenn ich das jetzt richtig verstanden habe wird erst der Speicher> auf dem Stack angelegt (40 byte)
C++ schreibt zwar nicht vor, wo ein bestimmter Speicher zu erzeugen ist,
weil C++ kein KOnzept vopn 'Stack' oder 'Heap' hat, aber du kannst
deinen A. darauf verwetten, dass ein new sicher nicht Speicher auf dem
Stack reserviert.
> erhöht damit der Stack wieder leer is und der Speicher nur noch im new> vorhanden ist?
Der 'Speicher' ist sowieso immer vorhanden. Der verschwindet ja nicht
irgendwie magisch oder wird aus der dünnen Luft herbeigezaubert.
Aber es muss auch eine Verwaltung geben bzw. eine Identifikation,
welcher Speicher denn eigentlich reserviert ist und welcher nicht. new
kümmert sich um diese Dinge und liefert eine Adresse (eine Zahl), die
zum Speicher führt, der in der benötigten Größe reserviert wurde.
Und um das auch gleich abzustellen:
new ist kein Assembler-Konstrukt! Da steckt genauso eine Funktion und
damit genauso Code dahinter, den mal irgendwer geschrieben hat.
Wahrscheinlich ist new selber in C++ geschrieben bzw. greift auf andere
Funktionen zurück, die in C++ bzw. in C geschrieben wurden.
Kan asta schrieb:> Auch ohne -OX würde ich soetwas nicht erwarten.
Erwarten würde ich es auch nicht.
Es ist aber auch nichts, was mir jetzt groß Kopfzerbrechen machen würde.
Okay das hört sich schonmal verständlich an.
Danke für die Infos
Frage: Warum ist denn der Compiler in dem fall scheiße?^^
btw. benutze Microsoft Visual Studio 2010
Mfg
Manche Compiler legen die Ergebnisse auch auf dem Stapel ab.
Bei Klein-Weich wird hierzu oft ebp-relativ gearbeitet.
Die Zeile 4 und 5 bewirken, dass sowohl Register- als auch
Stapelrückgabewerte möglich sind.
Wahrscheinlich ist der Optimizer nicht aktiv.
Interessant sind also nur die Zeilen 1,2,3 und 6.
1. Größe des gewünschten Bereiches 10 Variablen a 4 Bytes = 40d/28h
werden an Funktion (call new[]) übergeben.
2. Die eigentliche Funktion zur Speicherreservierung aufrufen.
3. Stapel bereinigen.
6. Zeiger auf reservierten Speicherbereich in "iVariable" ablegen.
Andre Kurth schrieb:> Frage: Warum ist denn der Compiler in dem fall scheiße?^^
Vergiss es.
Aus den 3 Zeilen Code kann man das nicht ableiten.
Für diese 'leichte' Ineffizienz mag es Gründe geben, die wir nicht
wissen. Zb. wie der Debugger an den Returnwert einer Funktion rankommt,
wenn der nicht (wie andere Variablen) im Speicher gespeichert wird,
sondern nur in einem Register temporär gehalten wird.
Zb. wie das Expression-Parsing funktioniert
zb. wer die Entscheidung getroffen hat, welche Dinge gleich optimiert
werden und welche Dinge auf spätere Optimizer-Stufen verschoben werden.
Ein Compiler ist nun mal kein triviales Programm. Speziell C++ Code ist
unoptimiert ein Graus. Aber wenn der Optimizer loslegen darf, dann holt
er da schon einiges raus.
amateur schrieb:> Manche Compiler legen die Ergebnisse auch auf dem Stapel ab.>> Bei Klein-Weich wird hierzu oft ebp-relativ gearbeitet.>> Die Zeile 4 und 5 bewirken, dass sowohl Register- als auch>> Stapelrückgabewerte möglich sind.>> Wahrscheinlich ist der Optimizer nicht aktiv.>>>> Interessant sind also nur die Zeilen 1,2,3 und 6.>>>> 1. Größe des gewünschten Bereiches 10 Variablen a 4 Bytes = 40d/28h>> werden an Funktion (call new[]) übergeben.>> 2. Die eigentliche Funktion zur Speicherreservierung aufrufen.>> 3. Stapel bereinigen.>> 6. Zeiger auf reservierten Speicherbereich in "iVariable" ablegen.
Super danke dir!
Jetzt wird das alles klarer :)
Andre Kurth schrieb:> 00CB1508 mov dword ptr[ebp-110h], eax> 00CB140E mov eax, dword ptr[ebp-110h]Andre Kurth schrieb:> Ich hab mich schon ziemlich intensiv mit dem thema assembler> auseinandergesetztAndre Kurth schrieb:> Frage: Warum ist denn der Compiler in dem fall scheiße?^^
Kannst du dir die Frage selbst beantworten?