2012-08-17 17 views
25

Ist ein Zeiger nicht nur eine Adresse? Oder ich vermisse etwas?Warum unterscheidet sich die Größe eines Zeigers zu einer Funktion von der Größe eines Zeigers zu einer Elementfunktion?

ich mit mehreren Arten von Zeigern getestet:

  • Zeiger auf beliebige Variablen ist die gleiche (8B auf meiner Plattform)
  • Zeiger auf Funktionen haben die gleiche Größe, wie Zeiger auf Variablen (8B wieder)
  • Zeiger auf Funktionen mit unterschiedlichen Parametern - immer noch die gleichen (8B)

BUT Zeiger auf Elementfunktionen sind größer - 16B auf meiner Plattform.

Drei Dinge:

  1. Warum sind Zeiger auf Member-Funktionen größer? Was brauchen sie mehr Informationen?
  2. Soweit ich weiß, die Standard sagt nichts über die Größe eines Zeigers, außer dass void* der Lage sein muss, um jeden Zeigertyp „enthalten“. Mit anderen Worten, jeder Zeiger muss in der Lage sein, an void* geworfen zu werden, richtig? Wenn ja, warum ist sizeof(void*) 8, während sizeof ein Zeiger auf Member-Funktion ist 16?
  3. Gibt es andere Beispiele für Zeiger, die mit unterschiedlicher Größe sind (ich meine, für Standard Plattformen, nicht einige seltene und spezielle)?
+1

Im Wesentlichen erfordert der Standard nicht, dass Datenzeiger, Funktionszeiger und Elementfunktionszeiger alle die gleiche Größe haben. Wenn Sie wissen wollen, warum sie auf Ihrer Plattform nicht die gleiche Größe haben, müssen Sie die Betreuer Ihres C++ - Compilers fragen. http://www.parashift.com/c++-faq/cant-cvt-memfnptr-to-vovotrp.html http://www.parashift.com/c++faq/cant-cvt-fnptr-to-vovotrp.html – Cubic

+0

@KirilKirov kein Problem. Ich bin nicht immer sarkastisch :) –

Antwort

26

EDIT: Also ich bemerkte ich bin immer noch Stimmen später auf diesem Monat bekommen, auch wenn meine ursprüngliche Antwort ist schlecht und irreführend (Ich kann nicht einmal daran erinnern, was ich damals dachte, und es macht nicht viel Sinn!), also dachte ich, ich würde versuchen, die Situation zu klären, da die Leute immer noch durch die Suche hierher kommen müssen. nur

In der meisten normalen Situation, können Sie ziemlich viel denken an

struct A { int i; int foo() { return i; } }; 
A a; a.foo(); 

als

struct A { int i; }; 
int A_foo(A* this) { return this->i; }; 
A a; A_foo(&a); 

(Start wie C aussehen, nicht wahr?) Also würde man den Zeiger &A::foo würde denken ist der gleiche wie ein normaler Funktionszeiger. Aber es gibt ein paar Komplikationen: Mehrfachvererbung und virtuelle Funktionen.

So vorstellen, die wir haben:

struct A {int a;}; 
struct B {int b;}; 
struct C : A, B {int c;}; 

Es könnte so ausgelegt werden:

Multiple inheritance

Wie Sie sehen können, wenn Sie mit einem A* auf das Objekt verweisen soll oder a C*, zeigen Sie auf den Anfang, aber wenn Sie mit einer B* darauf zeigen möchten, müssen Sie irgendwo in der Mitte zeigen.Also, wenn C erbt Mitglied Funktion von B und Sie darauf zeigen möchten, dann rufen Sie die Funktion auf eine C*, muss es wissen, um die this Zeiger zu mischen. Diese Information muss irgendwo gespeichert werden. Also wird es mit dem Funktionszeiger in Verbindung gebracht.

Jetzt für jede Klasse, die virtual Funktionen hat, erstellt der Compiler eine Liste von ihnen als eine virtuelle Tabelle. Es fügt dann der Klasse einen zusätzlichen Zeiger auf diese Tabelle hinzu (vptr). Also für diese Klasse Struktur:

struct A 
{ 
    int a; 
    virtual void foo(){}; 
}; 
struct B : A 
{ 
    int b; 
    virtual void foo(){}; 
    virtual void bar(){}; 
}; 

Der Compiler könnte am Ende es so machen: enter image description here

So Mitglied Funktionszeiger auf eine virtuelle Funktion tatsächlich benötigt ein Index in der virtuellen Tabelle sein. So ein Mitgliedsfunktionszeiger benötigt tatsächlich 1) möglicherweise einen Funktionszeiger, 2) möglicherweise eine Anpassung des this Zeigers und 3) möglicherweise einen Vtable-Index. Um konsistent zu sein, muss jeder Mitgliedsfunktionszeiger zu all diesen fähig sein. Das ist also 8 Bytes für den Zeiger, 4 Bytes für die Anpassung, 4 Bytes für den Index, für 16 Bytes insgesamt.

Ich glaube, das ist etwas, das tatsächlich viel zwischen Compiler variiert, und es gibt viele mögliche Optimierungen. Wahrscheinlich implementiert es keiner so, wie ich es beschrieben habe.

Für ein Los des Details, siehe this (blättern Sie zu "Implementierungen von Member Function Pointers").

+0

Ich habe mir die Freiheit genommen, den Text aus dem Standard einzufügen, den ich zu suchen glaube. Ich hoffe, das ist in Ordnung. – jogojapan

+0

Beachten Sie, dass ein 'int *', soweit der Standard betroffen ist, kleiner sein kann als ein 'void *'; für einige sehr alte C-Implementierungen war es das. Und alles, was für ein 'void *' garantiert ist, sind Nichtmitgliedsdaten: Es gibt nichts, was zum Beispiel garantiert, dass ein Zeiger auf Mitgliedsdaten nicht größer als ein 'void *' ist (obwohl ich mir eine vernünftige Implementierung nicht vorstellen kann) es wäre). –

+1

Das: wird als etwas wie ein Zeiger auf ein Objekt implementiert ist offensichtlich nicht korrekt. Der Objektzeiger wird an dem Punkt bereitgestellt, an dem die Methode aufgerufen wird. Aber ich glaube, du hast deine Worte einfach falsch verstanden. Ich würde es einfach neu formulieren, um anzuzeigen, dass mehr Informationen erforderlich sind, um polymorphes Verhalten zu unterstützen. –

6

Grundsätzlich, weil sie polymorphes Verhalten unterstützen müssen. Sehen Sie eine schöne article von Raymond Chen.

0

Ich denke, es hat etwas mit this Zeiger zu tun ... Das heißt, jede Member-Funktion muss auch den Zeiger für die Klasse, in der sie sind. Der Zeiger macht dann die Funktion ein bisschen größer in der Größe.

+3

Nein, falsch vermutete mein Kommentar auf die Antwort von Dirk Siehe –

+0

Diese Antwort ist auch falsch alomg.. mit Dirk Holsopple's. –

+0

:) Ich dachte genauso, weggo, aber es ist nicht wahr, zum Beispiel, weil die Initialisierung eines solchen Pointers nichts mit irgendeinem Objekt zu tun hat, sondern nur mit der Klassendeklaration, das hat mir gezeigt, dass ich es bin falsch. –

2

Einige Erklärungen hier zu finden: The underlying representation of member function pointers

Obwohl Zeiger auf Elemente wie gewöhnliche Zeiger verhalten, hinter den Kulissen ihre Darstellung ganz anders. Tatsächlich besteht ein Zeiger auf Member normalerweise aus einer Struktur, die in bestimmten Fällen bis zu vier Felder enthält. Dies liegt daran, dass Zeiger auf Elemente nicht nur gewöhnliche Elementfunktionen unterstützen müssen, sondern auch virtuelle Elementfunktionen, Elementfunktionen von Objekten mit mehreren Basisklassen und Elementfunktionen von virtuellen Basisklassen. Somit kann die einfachste Elementfunktion als ein Satz von zwei Zeigern dargestellt werden: einer, der die physikalische Speicheradresse der Elementfunktion hält, und einen zweiten Zeiger, der den this-Zeiger enthält. In Fällen wie einer virtuellen Elementfunktion, Mehrfachvererbung und virtueller Vererbung muss der Zeiger zu Member jedoch zusätzliche Informationen speichern. Daher können Sie Zeiger auf normale Zeiger weder auf Zeiger triggern, noch können Sie sicher zwischen Zeigern auf Elemente unterschiedlichen Typs umwandeln. Blockquote

0

Einige der Hauptgründe für die Zeiger auf Elementfunktionen als {this, T (*f)()} vertreten, sind:

  • Es einfachere Implementierung in den Compiler hat als Zeiger auf Elementfunktionen als T (*f)()

  • Umsetzung

    Es beinhaltet keine Laufzeitcode-Generierung oder zusätzliche Buchführung

  • Es führt einigermaßen gut zu T (*f)() verglichen

  • Es ist nicht genug, um die Nachfrage von C++ Programmierer für die Größe von Zeigern auf Elementfunktionen sizeof(void*)

  • Runtime Codegenerierung gleich sein während der Ausführung ist de facto ein Tabu für C++ - Code

Verwandte Themen