Operator new[] in Assembler c++

Gast #3139642
Lesenswert?

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
Gast #3139675
Lesenswert?

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.
#3139676
Lesenswert?

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)
(Firma: Basta) #3139696
Lesenswert?

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:
>
>
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
7
>
Gast #3139698
Lesenswert?

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
Gast #3139699
Lesenswert?

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.
Gast #3139722
Lesenswert?

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?
(Firma: Basta) #3139733
Lesenswert?

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.
#3139739
Lesenswert?

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.
#3139762
Lesenswert?

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.
Gast #3139790
Lesenswert?

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.
#3139791
Lesenswert?

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.
Gast #3139799
Lesenswert?

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 :)
(Firma: Basta) #3139876
Lesenswert?

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
> auseinandergesetzt

Andre Kurth schrieb:
> Frage: Warum ist denn der Compiler in dem fall scheiße?^^

Kannst du dir die Frage selbst beantworten?

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren