2010-04-01 6 views
13

Ich würde wirklich schätzen, wenn mir jemand sagen könnte, ob ich es gut verstehen:Sind die Referenzen dieser Objekte auf dem Stack oder auf dem Heap?

class X 
{ 
    A a1=new A(); // reference on the stack, object value on the heap 
    a1.VarA=5; // on the stack - value type 
    A a2=a1;  // reference on the stack, object value on the heap 
    a2.VarA=10; // on the stack - value type   
} 

beide auch a1 und a2 Referenzen auf dem Stapel sind, während ihre „Objekt“ Werte auf dem Heap sind. Aber was ist mit VarA Variable, es ist immer noch reiner Werttyp?

class A 
{ 
    int VarA; 
} 
+3

Da dieser Code nicht kompiliert ist es wirklich schwer zu beschreiben, wie die Laufzeit es behandelt. Sind alle diese Anweisungen in einem Methodenkörper enthalten? Sind diese Felddeklarationen oder lokalen Variablendeklarationen? –

Antwort

27

Sie Fragen über Implementierungsdetails, so die Antwort von der speziellen Implementierung ab. Lassen Sie uns eine Version des Programms berücksichtigen, die tatsächlich kompiliert:

class A { public int VarA; } 
class X 
{ 
    static void Main(string[] args) 
    { 
     A a1 = new A(); 
     a1.VarA = 5; 
     A a2 = a1; 
     a2.VarA = 10; 
    } 
} 

hier ist, was auf Microsofts geschieht 4.0 CLR, läuft C# 4.0, in Debug-Modus.

An diesem Punkt des Stapelrahmenzeiger in dem Register ebp kopiert worden:

Hier ordnen wir Heap-Speicher für das neue Objekt.

A a1 = new A(); 
mov   ecx,382518h 
call  FFE6FD30 

Das gibt einen Verweis auf ein Heap-Objekt in EAX zurück. Wir speichern die Referenz im Stack-Slot ebp-48, einem temporären Slot, der keinem Namen zugeordnet ist. Denken Sie daran, dass a1 noch nicht initialisiert wurde.

mov   dword ptr [ebp-48h],eax 

Nun nehmen wir die Referenz wir gerade auf dem Stapel gespeichert und kopieren Sie sich in ECX, die für den „diesen“ Zeiger auf den Aufruf den Ctor verwendet werden.

mov   ecx,dword ptr [ebp-48h] 

Jetzt rufen wir den Ctor.

Jetzt kopieren wir den im temporären Stack-Slot gespeicherten Verweis erneut in das Register eax.

mov   eax,dword ptr [ebp-48h] 

Und nun kopieren wir die Referenz in EAX in Stapel Schlitz EBP-40, die a1 ist.

mov   dword ptr [ebp-40h],eax 

Jetzt müssen wir a1 in EAX holen:

a1.VarA = 5; 
mov   eax,dword ptr [ebp-40h] 

Denken Sie daran, eax ist jetzt die Adresse der Halde zugewiesenen Daten für die Sache von a1 verwiesen. Das VarA Feld dieser Sache ist, vier Bytes in das Objekt, so speichern wir 5 in dem:

mov   dword ptr [eax+4],5 

Jetzt haben wir eine Kopie der Referenz in dem Stapel-Steckplatz für a1 in EAX machen, und kopieren Sie dann, dass in die Stack-Slot für a2, die ebp-44 ist.

A a2 = a1; 
mov   eax,dword ptr [ebp-40h] 
mov   dword ptr [ebp-44h],eax 

Und jetzt, wie Sie wieder erwarten würden wir a2 in EAX und dann die Referenz vier Bytes in Ehrerbietung 0x0A in die VarA zu schreiben:

a2.VarA = 10; 
mov   eax,dword ptr [ebp-44h] 
mov   dword ptr [eax+4],0Ah 

So ist die Antwort auf Ihre Frage ist, dass Verweise auf das Objekt sind im Stack an drei Stellen gespeichert: ebp-44, ebp-48 und ebp-40. Sie sind in Registern in eax und ecx gespeichert. Der Speicher des Objekts einschließlich seines Felds wird auf dem verwalteten Heap gespeichert. Das ist alles auf x86 im Debug-Build von Microsoft CLR v4.0. Wenn Sie wissen möchten, wie das Zeug auf dem Stack gespeichert, gehäuft und in einer anderen Konfiguration registriert wird, könnte es völlig anders aussehen. Referenzen könnten alle auf dem Heap oder alle in Registern gespeichert werden; es könnte überhaupt keinen Stapel geben. Es hängt völlig davon ab, wie sich die Autoren des JIT-Compilers für die Implementierung der IL-Semantik entschieden haben.

+0

Es hängt auch davon ab, wie die Autoren des C# -Compilers entschieden haben, die C# -Semantik zu implementieren.Die lokalen Variablen ('a1' und' a2') könnten als Felder in einem verwalteten Typ implementiert werden, wobei in jedem Stapelrahmen nur eine einzige Referenz verbleibt. Ich merke, dass das in einem Kommentar zu deinem Post auf Gedanken von Großmüttern und Eierlutschen hinweist, aber ich dachte, ich würde es trotzdem erwähnen :) –

+0

@Jon: In der Tat. Es gibt sehr wenige Fehler, die wir während der IL-Erzeugungsphase des Compilers erzeugen; einer von ihnen ist "zu viele Einheimische" - ich erinnere mich nicht, was das Limit ist, aber es ist etwas wie Sie können nicht mehr als 32K oder 64K Einheimische oder Provisorien in einer Methode. (Offensichtlich hat echter Code dieses Problem nicht, aber maschinell erzeugter Code könnte es sein.) Ich habe oft gedacht, dass wir in solchen Fällen lieber einen Fehler erzeugen sollten, als einfach, sie zu Feldern hochzuziehen. Aber es ist zu dunkel, um die Kosten für das Schreiben und Testen des Codes zu rechtfertigen. –

10

Genau genommen ist es implementierungsabhängig. Normalerweise sollte ein .NET Entwickler sich nicht um diese Dinge kümmern. Soweit ich weiß, werden Variablen von Werttypen in der .NET-Implementierung von Microsoft im Stapel gespeichert (wenn sie in einer Methode deklariert sind), und Daten von Objekten vom Referenztyp werden auf einem verwalteten Heap zugeordnet. Denken Sie jedoch daran, wenn ein Wertetyp ein Feld einer Klasse ist, werden die Klassendaten selbst auf einem Heap gespeichert (einschließlich aller Wertetypfelder). Daher nicht semantische (Werttypen vs Referenztypen) mit Zuordnungsregeln. Diese Dinge können korreliert sein oder nicht.

2

Ich glaube, Sie könnten ein kleines Missverständnis haben haben ...

Allgemeinen Referenztypen auf dem Heap gehen und Werttypen/Einheimische Ich glaube, (kann falsch sein) auf den Stapel gehen. Die Beispiele A1.VarA und A2.VarA beziehen sich jedoch auf ein Feld eines Referenztyps, das zusammen mit dem Objekt auf dem Heap gespeichert wird.

+0

Ja, aber der Wert dieses Feldes ist int, daher Werttyp, oder? – Petr

+0

@Petr, alle Felder sind im Referenztyp A enthalten, der sich auf dem Heap befindet. –

2

In diesem Fall wäre a1.VarA auf dem Heap als Platz dafür wäre zugewiesen worden, wenn Sie A a1 = new A() getan haben.

Wenn Sie das tun nur int i = 5; in einer Funktion, die auf den Stapel gehen, aber wie Sie explizit angegeben a1 auf dem Heap zugewiesen wurde, um dann alle Werttypen mit ihm verbunden wird auf dem Heap platziert werden

0

lesen Jeff Richters CLR via C# für ein vollständiges Verständnis dieses Themas.

0

Erinnern Sie sich an das Lesen in C# in der Tiefe: - Nur lokale Variablen (die deklarierte interne Methode) und Methodenparameter leben im Stapel.Instanzvariable wie varA im obigen Fall befindet sich auf Heap.

+3

Beachten Sie, dass lokale Variablen, die abgeschlossene Locals einer Lambda oder anonymen Methode sind nicht in der Microsoft-Implementierung von C# auf dem Stapel gespeichert sind. Dasselbe gilt für lokale Variablen, die sich in einem Iteratorblock befinden. –

2
class X 
{ 
    A a1=new A(); // reference on the stack, object value on the heap 
    a1.VarA=5; // on the Heap- value type (Since it is inside a reference type) 
    A a2=a1;  // reference on the stack, object value on the heap 
    a2.VarA=10; // on the Heap - value type (Since it is inside a reference type) 
} 
0

Ich bin neu in C# auch. Deine Frage ist sehr wichtig, ich habe auch darüber nachgedacht. Die gesamte Dokumentation sagte, die Werte gehen Stack und Referenzen geht Heap, aber wie die Jungs oben gesagt, ist es nur für den Code innerhalb der Methoden. Auf der Stufe des Lernens erkenne ich, dass alle Programmcodes innerhalb einer Methode beginnen, die zu einer Instanz gehören, die zu Heap gehören. So konzeptionell ist der Stapel nicht gleichbedeutend mit Heap, so wie die gesamte Dokumentation Menschen verwirrt. Der Stack-Mechanismus wird nur in einer Methode gefunden ...