2014-09-11 7 views
51

Ich fand, dass MSVC und GCC-Compiler mindestens ein Byte pro Klasseninstanz zuweisen, selbst wenn die Klasse ein Prädikat ohne Membervariablen (oder nur mit statischen Membervariablen) ist. Der folgende Code veranschaulicht den Punkt.Warum belegen C++ - Klassen ohne Membervariablen Platz?

#include <iostream> 

class A 
{ 
public: 
    bool operator()(int x) const 
    { 
     return x>0; 
    } 
}; 

class B 
{ 
public: 
    static int v; 
    static bool check(int x) 
    { 
     return x>0; 
    } 
}; 

int B::v = 0; 

void test() 
{ 
    A a; 
    B b; 
    std::cout << "sizeof(A)=" << sizeof(A) << "\n" 
      << "sizeof(a)=" << sizeof(a) << "\n" 
      << "sizeof(B)=" << sizeof(B) << "\n" 
      << "sizeof(b)=" << sizeof(b) << "\n"; 
} 

int main() 
{ 
    test(); 
    return 0; 
} 

Ausgang:

sizeof(A)=1 
sizeof(a)=1 
sizeof(B)=1 
sizeof(b)=1 

Meine Frage ist, warum Compiler braucht es? Der einzige Grund, den ich finden kann, ist sicherzustellen, dass alle Mitglieds-Var-Zeiger unterschiedlich sind, so dass wir zwischen zwei Mitgliedern vom Typ A oder B unterscheiden können, indem wir Zeiger auf sie vergleichen. Aber die Kosten dafür sind ziemlich streng im Umgang mit kleinen Behältern. In Anbetracht der möglichen Datenausrichtung können wir bis zu 16 Bytes pro Klasse ohne vars (?!) Erhalten. Angenommen, wir haben einen benutzerdefinierten Container, der normalerweise einige int-Werte enthält. Betrachten Sie dann ein Array solcher Container (mit etwa 1000000 Mitgliedern). Der Overhead wird 16 * 1000000 sein! Ein typischer Fall, in dem dies passieren kann, ist eine Containerklasse mit einem Vergleichsprädikat, das in einer Membervariablen gespeichert ist. Bedenkt man außerdem, dass eine Klasseninstanz immer etwas Platz einnehmen sollte, welche Art von Overhead sollte man beim Aufruf von A() (value) erwarten?

+7

Nur zur Bestätigung Ihres Verdachts: * Sofern es sich nicht um ein Bitfeld handelt (9.6), muss ein am weitesten abgeleitete Objekt eine Größe ungleich Null haben und ein oder mehrere Speicherbytes belegen. Unterobjekte der Basisklasse dürfen keine Größe haben. * – chris

+3

FYI: Unterobjekte der Größe 0 * sind erlaubt. Wenn Sie also von einer solchen leeren Klasse ableiten und ein weiteres Mitglied der Größe x hinzufügen, ist es wahrscheinlich, dass die Größe Ihres abgeleiteten Typs ebenfalls x ist. Dies wird als "leere Basisklassenoptimierung" bezeichnet. – sellibitze

+8

Ich glaube, dass sich dort mehrere Fragen überschneiden. Es hat keinen Sinn, eine große Anzahl von Klassen ohne Mitglieder in einem Container zu "speichern". Da es keine Daten gibt, gibt es keinen Unterschied zwischen ihnen. Die Tatsache, dass Klassen ohne Member in C++ eine Größe ungleich Null haben, bedeutet jedoch nicht, dass Klassen, die über Member verfügen, unnötigen Overhead haben würden. Das Speicherausrichtungsproblem ist jedoch ein unabhängiges Problem und ist nicht auf C++ beschränkt. – AlefSin

Antwort

74

Es ist notwendig, eine Invariante aus dem C++ - Standard zu erfüllen: Jedes C++ - Objekt des gleichen Typs muss eine eindeutige Adresse haben, um identifizierbar zu sein.

Wenn Objekte keinen Platz beanspruchten, würden Elemente in einem Array die gleiche Adresse haben.

+1

BTW, ich denke, wenn Sie eine lokale Variable mit einer solchen leeren 'struct' (oder' class') haben, könnte es oft optimiert werden, um überhaupt keinen Platz auf dem Aufruf-Stack zu nehmen. –

+1

Ich frage mich: Da es keine zugänglichen Daten gibt, könnte eine konforme Implementierung ein Array von leeren Klassen an einer sehr hohen Adresse "zuweisen", die niemals wirklich nutzbar wäre? Zum Beispiel einer dieser "unmöglichen" Zeigerwerte auf x86_64? –

+0

Arrays mit keinen Werten belegen genau null Bytes, und C++ hat kein Problem damit umzugehen. Warum ist es wichtig, Instanzen durch Zeiger zu unterscheiden, wenn sie per Definition identisch sind und nicht in jedem Ausdruck nach Wert verwendet werden können? – bkxp

13

Alle vollständigen Objekte müssen eine eindeutige Adresse haben; Sie müssen also mindestens ein Byte Speicher belegen - das Byte an ihrer Adresse.

Ein typischer Fall, in dem es vorkommen kann, ist eine Containerklasse mit einem Vergleichsprädikat, das in einer Membervariablen gespeichert ist.

In diesem Fall können Sie die leere Basisklasse Optimierung verwenden: eine Basis subobject die gleiche Adresse wie das komplette Objekt haben dürfen, dass es Teil, so dass keine Speicherung in Anspruch nehmen können. Sie können also das Prädikat einer Klasse als (möglicherweise private) Basisklasse und nicht als Mitglied hinzufügen. Es ist etwas fummeliger als ein Mitglied, aber sollte den Overhead beseitigen.

Welche Art von Overhead sollte erwartet werden, wenn A() (Wert) aufgerufen wird?

Der einzige Overhead im Vergleich zum Aufruf einer Nicht-Member-Funktion wird das zusätzliche this Argument übergeben. Wenn die Funktion inline ist, sollte dies eliminiert werden (wie dies im Allgemeinen der Fall ist, wenn eine Mitgliedsfunktion aufgerufen wird, die nicht auf Mitgliedsvariablen zugreift).

+1

Es ist erwähnenswert, dass der Overhead des Übergebens von "this" im Allgemeinen klein ist, aber größer wird, wenn die Anzahl von Argumenten die ABI-Beschränkung für das Weiterreichen des Stapels aufgrund des unnötigen "this" überschreitet. Wenn die Elementfunktion jedoch "Inline" ist, kann dies optimiert werden. –

+0

Danke für die Erklärung. Der Vererbungstrick ist ziemlich interessant, obwohl es eine Adapterklasse erfordern würde, um die Infusion eines fremden Operators() in der Containerklasse zu vermeiden. – bkxp

+0

@bkxp: Wenn Sie private Vererbung verwenden, dann haben Sie _technisch_ immer noch den 'operator()', aber es ist privat, so dass niemand sonst es verwenden kann. Ich würde erwarten, dass praktisch jeder Container dies mit den Allokatoren macht. –

26

Im Grunde ist es ein Wechselspiel zwischen zwei Anforderungen:

  • Zwei verschiedene Objekte des gleichen Typs an einer anderen Adressen sein muss.
  • In Arrays ist möglicherweise kein Abstand zwischen Objekten vorhanden.

Beachten Sie, dass die erste Bedingung allein nicht ungleich Null Größe erfordert: Gegeben

struct empty {}; 
struct foo { empty a, b; }; 

die die erste Anforderung leicht mit einer Null-Größe a gefolgt von einem einzelnen Füllbyte erfüllt werden könnte, um erzwingen Sie eine andere Adresse, gefolgt von einer Nullgröße b. Angesichts der Tatsache

empty array[2]; 

, die nicht mehr funktioniert, weil eine Polsterung zwischen den verschiedenen Objekten empty[0] und empty[1] würde nicht erlaubt sein.

+0

Ich versuche immer noch, die Fälle herauszufinden, in denen dies absolut notwendig wäre. Einer dieser Fälle ist die Vermeidung einer Division durch Null bei der Berechnung von N/Größe von (A). – bkxp

+0

@bkxp: 'std :: sort (& array [0], & array [was auch immer seine Größe ist]);' – PlasmaHH

+0

@PlasmaHH: Wenn man ein Array von identischen Objekten sortiert, ist das kein Op, und mit begin = end, any Der Aufruf von 'std :: sort' ist ebenfalls ein No-Op, diese spezielle Zeile würde sicherlich nicht brechen. – celtschk

1

Es gibt bereits ausgezeichnete Antworten, die die Hauptfrage beantworten. Ich möchte auf die von Ihnen geäußerten Bedenken eingehen:

Aber die Kosten dafür sind bei kleinen Behältern sehr hoch. In Anbetracht der möglichen Datenausrichtung können wir bis zu 16 Bytes pro Klasse ohne vars (?!) Erhalten. Angenommen, wir haben einen benutzerdefinierten Container, der normalerweise einige int-Werte enthält. Betrachten Sie dann ein Array solcher Container (mit etwa 1000000 Mitgliedern). Der Overhead wird 16 * 1000000 sein! Ein typischer Fall, in dem dies passieren kann, ist eine Containerklasse mit einem Vergleichsprädikat, das in einer Membervariablen gespeichert ist.

Vermeidung der Kosten für A

halten Wenn alle Instanzen eines Behälters hängen vom Typ A, dann gibt es keine Notwendigkeit, Fälle von A im Behälter zu halten. Der Overhead, der mit der Größe A ungleich Null verknüpft ist, kann vermieden werden, indem einfach eine Instanz von A auf dem Stapel erstellt wird, wenn dies erforderlich ist.

nicht die Kosten der Holding zu vermeiden, in der Lage, A

Sie gezwungen werden kann, einen Zeiger auf A in jedem Fall des Behälters zu halten, wenn A von polymorphen zu erwarten ist. Für einen solchen Behälter steigen die Kosten jedes Behälters um die Größe eines Zeigers. Ob es Membervariablen in der Basisklasse A gibt oder nicht, spielt für die Größe des Containers keine Rolle.

Auswirkungen der sizeof A

In jedem Fall sollte die Größe einer leeren Klasse auf den Speicherbedarf des Behälters keinen Einfluss haben.

+0

Weniger Speicherbedarf für Container vs. weniger Zeit beim Erstellen temporärer Objekte. Ich denke, Sie müssen entscheiden, welche Abwägung für Ihren Anwendungsfall besser ist. –

Verwandte Themen