Verwalten von C ++ in einem Puffer-Objekte, unter Berücksichtigung der Ausrichtung und Speicherlayout Annahmen

StackOverflow https://stackoverflow.com/questions/417446

Frage

Ich bin Speichern von Objekten in einem Puffer. Jetzt weiß ich, dass ich nicht Annahmen über das Speicherlayout des Objekts machen kann.

Wenn ich die Gesamtgröße des Objekts kennen, ist es acceptible einen Zeiger auf diesen Speicher zu erstellen und rufen Funktionen auf sie?

z. sage ich die folgende Klasse haben:

[int,int,int,int,char,padding*3bytes,unsigned short int*]

1) wenn ich weiß, diese Klasse von Größe zu sein, 24 und ich weiß, die Adresse, wo es beginnt im Speicher während es nicht sicher ist, das Speicherlayout anzunehmen, ist es acceptible dies auf einen Zeiger und Anruffunktionen auf diesem Objekt, das diese Elemente zugreifen zu werfen? (Hat C ++ von einiger Magie kennt die korrekte Position ein Mitglied?)

2) Ist dies nicht sicher / ok, gibt es eine andere Art und Weise andere als einen Konstruktor, der alle Argumente und zieht jedes Argument aus dem Puffer ein zu einer Zeit in Anspruch nimmt?

Edit: Changed Titel, um es angemessen zu dem, was ich frage

.
War es hilfreich?

Lösung

Sie können einen Konstruktor erstellen, die alle Mitglieder und ordnet sie nimmt, dann verwenden Platzierung neu.

class Foo
{
    int a;int b;int c;int d;char e;unsigned short int*f;
public:
    Foo(int A,int B,int C,int D,char E,unsigned short int*F) : a(A), b(B), c(C), d(D), e(E), f(F) {}
};

...
char *buf  = new char[sizeof(Foo)];   //pre-allocated buffer
Foo *f = new (buf) Foo(a,b,c,d,e,f);

Dies hat den Vorteil, dass auch die V-Tabelle korrekt generiert werden. Beachten Sie jedoch, wenn Sie dies für die Serialisierung verwenden, wird der unsigned short int-Zeiger nicht auf alles, was nützlich geht zu zeigen, wenn Sie es deserialisieren, es sei denn, Sie sind sehr vorsichtig irgendeine Art von Verfahren zu verwenden, Zeiger in Offsets zu konvertieren und dann wieder zurück .

Individual-Methoden auf einem this pointer statisch verknüpft werden und sind einfach ein direkter Aufruf die Funktion mit this der erste Parameter vor dem expliziten Parameter sein.

Das Mitglied Variablen aus dem this Offset mit Zeiger referenziert. Wenn ein Objekt wie folgt aufgebaut:

0: vtable
4: a
8: b
12: c
etc...

a wird durch dereferencing this + 4 bytes zugegriffen werden.

Andere Tipps

Im Grunde, was Sie vorschlagen, zu tun ist in einem Bündel von (hoffentlich nicht zufällig) Bytes gelesen wurden, um sie zu einem bekannten Objekt Gießen, und dann auf das Objekt eine Klasse Methode aufrufen. Es könnte tatsächlich funktionieren, weil dieser Bytes im „dieser“ Zeiger in dieser Klasse Methode, um am Ende gehen. Aber Sie nehmen eine echte Chance auf die Dinge nicht zu sein, wo der kompilierte Code es erwartet werden. Und im Gegensatz zu Java oder C #, gibt es keine echte „Laufzeit“ diese Art von Problemen zu fangen, so besten Sie einen Core Dump erhalten werden, und im schlimmsten Fall werden Sie Speicher beschädigt werden.

Es klingt wie Sie eine C ++ Version von Java Serialisierung / Deserialisierung wollen. Wahrscheinlich gibt es eine Bibliothek gibt, das zu tun.

Nicht-virtuelle Funktion Anrufe werden direkt wie eine C-Funktion verknüpft. Das Objekt (this) Zeiger wird als erstes Argument übergeben. Keine Kenntnisse des Objekts Layout erforderlich, um die Funktion aufzurufen.

Es klingt wie Sie nicht die Objekte selbst in einem Puffer speichern, sondern die Daten, aus denen sie bestehen sind.

Wenn diese Daten im Speicher in der Reihenfolge ist die Felder innerhalb der Klasse definiert sind (mit dem richtigen Polsterung für die Plattform) und Ihr Typ ist ein f"

  • Anruf Standardinitialisierung für die Klasse "Foo" (tut nichts in diesem Fall)
  • Push-Argument Wert 42 auf den Stapel
  • Push-Zeiger auf Objekt "f" auf den Stapel
  • Anruf Foo_i_DoSomething@4 funktioniert (eigentlicher Name ist in der Regel komplexe)
  • load Rückgabewert 0 in Gesamtaddiereinheit
  • zurück zum Anrufer
  • Funktion Foo_i_DoSomething@4 (an anderer Stelle in dem Codesegment befindet)
    1. load "x" Wert aus dem Stapel (Aufschieben von Anrufern)
    2. mit 2 multiplizieren
    3. load "this" Zeiger vom Stapel (Aufschieben von Anrufern)
    4. berechnen Offset des Feldes "a" innerhalb eines Foo Objekt
    5. berechnet add Offset this Zeiger, der in Schritt 3
    6. geladen
    7. Speicher Produkt, berechnet in Schritt 2, um in Schritt berechnete Offset 5
    8. load "x" Wert aus dem Stapel, wieder
    9. load "this" Zeiger vom Stapel, wieder
    10. berechnen Offset des Feldes "a" innerhalb eines Foo Objekt, wieder
    11. berechnet add Offset this Zeiger in Schritt 8
    12. geladen
    13. load "a" gespeicherte Wert Offset
    14. "a" Wert, geladen int Schritt 12 auf "x" Wert geladen in Schritt 7
    15. fügen
    16. load "this" Zeiger vom Stapel, wieder
    17. berechnen Offset des Feldes "b" innerhalb eines Foo Objekt
    18. fügen Offset berechnet this pointer, geladen in Schritt 14
    19. Speicher sum, berechnet in Schritt 13, in Schritt berechnete Offset 16
    20. zurück zum Anrufer
  • Mit anderen Worten, es wäre mehr oder weniger der gleiche Code sein, wie wenn Sie diese geschrieben hatten (Besonderheiten, wie Name der DoSomething Funktion und Methode der this Zeiger vorbei bis zu den Compiler):

    class Foo{
        int a;
        int b;
    
        friend void Foo_DoSomething(Foo *f, int x);
    };
    
    void Foo_DoSomething(Foo *f, int x){
        f->a = x * 2;
        f->b = x + f->a;
    }
    
    int main(){
        Foo f;
        Foo_DoSomething(&f, 42);
        return 0;
    }
    
    1. Ein Objekt mit POD-Typ, in diesem Fall ist bereits (Unabhängig davon, ob Sie neu aufrufen. Die Zuweisung der erforderlichen Speicher genügt schon), und Sie können die Mitglieder darauf zugreifen, darunter eine Funktion auf, dass Aufruf Objekt. Aber das funktioniert nur, wenn Sie genau die erforderliche Ausrichtung von T wissen, und die Größe von T (der Puffer kann nicht kleiner sein als es), und die Ausrichtung aller Mitglieder von T. Selbst für einen Pod-Typen, der Compiler dürfen Füllbytes zwischen den Mitgliedern stellen, wenn sie will. Für einen nicht-POD-Typen können Sie das gleiche Glück haben, wenn Ihr Typ keine virtuellen Funktionen oder Basisklassen haben, keinen benutzerdefinierten Konstruktor (natürlich) und das gilt für die Basis und all nicht-statische Elemente zu.

    2. Für alle anderen Typen, alle Wetten ausgeschaltet sind. Sie haben Werte mit einem POD zuerst lesen, und dann einen nicht-POD-Typen mit diesen Daten initialisieren.

      

    Ich bin Speichern von Objekten in einem Puffer. ... Wenn ich die Gesamtgröße des Objekts weiß, ist es akzeptabel, einen Zeiger auf diesen Speicher und rufen Funktionen auf, es zu schaffen?

    Dies ist insofern akzeptabel, dass Casts mit akzeptabel ist:

    #include <iostream>
    
    namespace {
        class A {
            int i;
            int j;
        public:
            int value()
            {
                return i + j;
            }
        };
    }
    
    int main()
    {
        char buffer[] = { 1, 2 };
        std::cout << reinterpret_cast<A*>(buffer)->value() << '\n';
    }
    

    Casting ein Objekt zu etwas wie rohem Speicher und wieder zurück ist eigentlich ziemlich häufig, vor allem in der C-Welt. Wenn Sie eine Klassenhierarchie verwenden, obwohl, würde es mehr Sinn macht Zeiger auf Member-Funktionen zu verwenden.

      

    sage ich die folgende Klasse: ...

         

    , wenn ich diese Klasse weiß von der Größe 24 zu sein, und ich weiß, die Adresse, wo es beginnt im Speicher ...

    Dies ist, wo es schwierig wird. Die Größe eines Objekts enthält die Größe seiner Datenelemente (und alle Datenelemente von allen Basisklassen) sowie jede Polsterung sowie alle Funktionszeiger oder implementierungsabhängigen Informationen, minus alles von bestimmten Größe Optimierungen (leere Basisklasse Optimierung) gespeichert. Wenn die resultierende Zahl 0 Byte ist, dann wird das Objekt benötigt mindestens ein Byte in den Speicher zu übernehmen. Diese Dinge sind eine Kombination aus Sprachproblemen und gemeinsamen Anforderungen, die die meisten CPUs haben in Bezug auf Speicherzugriffe. Versuch richtig, um Dinge zu arbeiten, kann eine echte Schmerzen sein.

    Wenn Sie nur ein Objekt und gegossen zu und von rohen Speicher zuweisen können Sie diese Probleme ignorieren. Aber wenn Sie ein Objekt der Einbauten auf einen Puffer von einer Art kopieren, dann hinten sie ihren Kopf ziemlich schnell. Der Code stützt sich oben auf einigen allgemeinen Regeln über die Ausrichtung (dh passiere ich diese Klasse A wissen, die gleiche Ausrichtung Einschränkungen wie Ints haben, und somit kann das Array sicher zu einem A gegossen werden, aber ich konnte nicht unbedingt gewährleistet, die Gleiches gilt, wenn ich wurden Teile des Arrays A ist und Teile zu anderen Klassen mit anderen Datenelemente).

    Gießen

    Ach ja, und wenn Objekte Kopieren Sie müssen sicherstellen, dass Sie Umgang mit Zeigern richtig.

    Sie auch interessiert sein an Dinge wie Googles Protocol Buffers oder Facebook Thrift .


    Ja sind diese Probleme schwierig. Und, ja, einige Programmiersprachen sie unter den Teppich kehren. Aber es gibt eine ganze Menge Sachen bekommen unter dem Teppich gekehrt :

      

    In Suns HotSpot JVM, Objektspeicher wird auf die nächste Grenze 64-Bit ausgerichtet ist. Hinzu kommt, hat jedes Objekt einen 2-Wort-Header im Speicher. Die Wortgröße der JVM ist in der Regel der nativen Zeigergröße der Plattform. (Ein Objekt, bestehend aus nur einem 32-Bit-int und eine 64-Bit-Doppel - 96 Datenbits - erfordert) zwei Worte für das Objekt-Header, ein Wort für die INT, zwei Wörter für die Doppel. Das 5 Wörter: 160 Bit. Aufgrund der Ausrichtung dieses Objekt 192 Bit Speicher belegt.

    Dies liegt daran, Sun auf eine relativ einfache Taktik setzt für Speicherausrichtungsprobleme (auf einem imaginären Prozessor, ein char kann an jeder Speicherstelle, einen int an einem beliebigen Ort existieren gelassen werden, die durch 4 teilbar ist, und eine Doppel möglicherweise muß nur auf Speicherplatz zugewiesen werden, die durch 32 teilbar sind -. aber die restriktivste auch erfüllt Ausrichtungsanforderung jede andere Ausrichtung Anforderung, also alles Sonne ausrichtet nach dem restriktivsten Standort)

    Eine andere Taktik für Speicherausrichtung etwas von diesem Raum zurückzufordern kann.

    1. Wenn die Klasse keine virtuellen Funktionen enthält (und damit Klasseninstanzen haben keine vptr), und wenn Sie machen richtig Annahmen über die Art und Weise, in der die Klasse Mitgliedsdaten im Speicher angelegt, dann zu tun, was Sie vorschlagen funktionieren könnte (aber vielleicht nicht tragbar sein).
    2. Ja, ein andere Art und Weise (mehr idiomatische aber nicht viel sicherer ... Sie müssen noch wissen, wie die Klasse seine Daten legt) würde den sogenannten „Placement Operator new“ und ein Default-Konstruktor zu verwenden sein.

    Das hängt davon ab, was Sie unter „sicher“. Jedes Mal, wenn Ihr eine Speicheradresse in einen Punkt auf diese Weise Sie unter Umgehung der Art Sicherheitsmerkmale durch den Compiler zur Verfügung gestellt und unter die Verantwortung für sich. Wenn, wie Chris schon sagt, Sie eine falsche Annahme über die Speicher-Layout machen, oder Compiler Implementierungsdetails, dann werden Sie zu unerwarteten Ergebnissen und lose Portabilität erhalten.

    Da Sie besorgt über die „Sicherheit“ dieser Programmierstil sind, ist es wahrscheinlich lohnt sich, portable und typsichere Methoden wie vorbestehende Bibliotheken zu untersuchen, oder einen Konstruktor oder Zuweisungsoperator für den Zweck zu schreiben.

    Lizenziert unter: CC-BY-SA mit Zuschreibung
    Nicht verbunden mit StackOverflow
    scroll top