2014-09-03 12 views
6

Gibt es einen Unterschied zwischen den folgenden drei Umwandlungen zum Extrahieren von rohen Bytezeigern zur Verwendung in Zeigerarithmetik? (Unter der Annahme einer Plattform, wo char 1 Byte ist.)reinterpret_cast <char*> (p) oder static_cast <char*> ((void *) p)) für byteweise Zeigerdifferenz, was ist besser?

  1. static_cast<char*>((void*)ptr))
  2. reinterpret_cast<char*>(ptr)
  3. (aktualisiert) oder: static_cast<char*>(static_cast<void*>(ptr))

Was sollte ich bevorzugen?

mehr Details ...

Da Zeiger auf zwei Mitgliedsobjekte in einer Klasse, würde Ich mag ein von einem zum anderen versetzt berechnen, so dass ich angesichts der Adresse eines Mitglieds rekonstruieren ein Offset und die Adresse des anderen Mitglieds.

// assumed data layout: 
struct C { 
    // ... 
    A a; 
    // ... 
    B b; 
} 

Der Code, den ich im Moment verwenden ist entlang der Linien von:

void approach1(A *pa, B *pb) 
{ 
    // compute offset: 
    std::ptrdiff_t offset = static_cast<char*>((void*)pa) - static_cast<char*>((void*)pb); 
    // then in some other function... 
    // given offset and ptr to b, compute ptr to a: 
    A *a = static_cast<A*>((void*)(static_cast<char*>((void*)pb) + offset)); 
} 

main() 
{ 
    C c; 
    approach1(&c.a, &c.b); 
} 

Ich möchte wissen, ob die folgende ist besser (oder schlechter):

void approach2(A *pa, B *pb) 
{ 
    std::ptrdiff_t offset = reinterpret_cast<char*>(pa) - reinterpret_cast<char*>(pb); 
    // ... 
    A *a = reinterpret_cast<A*>(reinterpret_cast<char*>(pb) + offset); 
} 

Sind die beiden Methoden völlig gleichwertig? Sind sie genauso tragbar?

Mein Eindruck ist, dass approach1() ist tragbarer, weil "static_casting a pointer to and from void* preserves the address", während reinterpret_cast<> weniger garantiert (siehe angenommene Antwort bei Link).

Ich würde gerne wissen, was der sauberste Weg ist, dies zu tun ist.

Update: Erklärung des Zwecks

Eine Reihe von Menschen hat gefragt, was ist der Zweck, diese Offsets zu berechnen. Der Zweck besteht darin, eine Meta-Klasse-Tabelle mit Instanz-Offsets zu erstellen. Dies wird von einem Laufzeit-Reflektionsmechanismus zur automatischen GUI-Erstellung und Persistenz verwendet (die Offsets werden nicht serialisiert, sondern nur zum Durchlaufen der Struktur verwendet). Der Code ist seit über 15 Jahren in Produktion. Für die Zwecke dieser Frage möchte ich nur die portabelste Art der Berechnung der Zeigeroffsets wissen. Ich habe nicht die Absicht, große Änderungen an der Funktionsweise des Metaklassensystems vorzunehmen. Darüber hinaus bin ich im Allgemeinen auch an der besten Möglichkeit interessiert, dies zu tun, da ich andere Anwendungen im Sinn habe (z. B. Differenzzeiger für einen Code für gemeinsam genutzten Speicher).

HINWEIS: Ich kann nicht offsetof() verwenden, weil in meinem eigentlichen Code nur ich die Zeiger auf Instanzen haben a und b, ich habe nicht unbedingt den Typ des enthaltenen Objekts c oder andere statische Informationen offsetof() zu verwenden. Alles was ich annehmen kann ist, dass a und b Mitglieder desselben Objekts sind.

+0

C++ 11 oder nicht? einige Besonderheiten von reinterpret_cast haben IIRC geändert. Abgesehen davon: Warum willst du das machen? – stijn

+0

Dies könnte ein wenig helfen http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used – displayname

+0

@stijn C++ 03 oder früher bevorzugt.Antworten, die die Unterschiede zwischen verschiedenen C++ - Versionen erklären, würden mir gefallen, damit ich es verstehen kann. Ich werde die Frage mit einer Beschreibung des Zwecks aktualisieren. –

Antwort

0

Ich empfehle nicht, Offset-Abstände zwischen Klassen-Adressen zu berechnen. Entweder fügt der Compiler Padding-Daten ein, oder selbst wenn er funktioniert, funktioniert er nur für den spezifischen Compiler, der auf diesem spezifischen Host läuft.Es gibt eine Vielzahl von Fehlerquellen bei der Anwendung dieser Praxis. Zum Beispiel, wenn Sie mit dem berühmten Virtual tables and memory layout in multiple virtual inheritance umgehen müssen? Dies wird Ihre Lösung völlig unbrauchbar machen.

Also zurück zu den Wurzeln: Warum versuchst du das zu tun? Vielleicht gibt es eine bessere Lösung.

EDIT/Update

Dank uns den Grund zu erklären. Es ist ein sehr interessanter Ansatz, den ich bisher nicht gesehen habe. Ich habe heute etwas gelernt.

Dennoch bleibe ich bei meinem Punkt, dass es eine viel einfachere Art der Handhabung geben sollte. Und genau wie ein Beweiskonzept habe ich eine kleine Anwendung geschrieben, nur um zu sehen, welche Ihrer Methoden funktioniert. Für mich arbeiten beide nicht.

Die Anwendung ist ein hier leicht eine Ihrer Methoden erweitert, ist es:

#include <iostream> 
#include <stdio.h> 
#include <string> 

struct A 
{ 
    A(const std::string& pa) : a(pa) {printf("CTR: A address: %p\n", this) ;} 
    std::string a; 
}; 

struct B 
{ 
    B(const std::string& pb) : b(pb) {printf("CTR: B address: %p\n", this) ;} 
    std::string b; 
}; 

// assumed data layout: 
struct C { 

    C() : a("astring"), b("bstring") {} 
    // ... 
    A a; 
    // ... 
    B b; 
}; 

void approach1(A *pa, B *pb) 
{ 

    printf("approach1: A address: %p B address: %p\n", pa, pb); 
    // compute offset: 
    std::ptrdiff_t offset = static_cast<char*>((void*)pb) - static_cast<char*>((void*)pa); 
    // then in some other function... 
    // given offset and ptr to b, compute ptr to a: 
    A *a = static_cast<A*>((void*)(static_cast<char*>((void*)pb) + offset)); 
    printf("approach1: a address: %p \n", a); 

    std::cout << "approach1: A->a=" << a->a << std::endl; 
} 


void approach2(A *pa, B *pb) 
{ 
    printf("approach2: A address: %p B address: %p\n", pa, pb); 

    std::ptrdiff_t offset = reinterpret_cast<char*>(pb) - reinterpret_cast<char*>(pa); 

    A *a = reinterpret_cast<A*>(reinterpret_cast<char*>(pb) + offset); 
    printf("approach2: a address: %p \n", a); 
    std::cout << "approach2: A->a=" << a->a << std::endl; 
} 

main() 
{ 
    C c; 
    std::cout << c.a.a << std::endl; 

    approach1(&c.a, &c.b); 
    approach2(&c.a, &c.b); 
} 

Der Ausgang der es auf meinem Computer (uname -a Linux flood 3.13.0-33-generiC#58-Ubuntu SMP Tue Jul 29 16:45:05 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux) mit meinem Compiler (g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2) ist:

CTR: A address: 0x7fff249f0900 
CTR: B address: 0x7fff249f0908 
astring 
approach1: A address: 0x7fff249f0900 B address: 0x7fff249f0908 
approach1: a address: 0x7fff249f0910 
approach1: A->a=<GARBAGE> 
approach2: a address: 0x7fff249f0910 

wo <GARBAGE> wie erwartet enthält ... Müll.

Bitte sehen Sie unter: http://ideone.com/U8ahAL

+2

Ich habe die Frage mit einer Beschreibung des Zwecks aktualisiert. Leider hast du meine Frage nicht beantwortet, mir einfach gesagt, dass ich es falsch mache - das wäre in einem Kommentar besser gemacht worden. Ich bin mir der Gefahren bewusst. –

+0

meine schlechte, sollte pa-pb nicht pb-pa sein, das läuft: http://ideone.com/R7aODT –

+0

@fritzone Ich denke, der Code sollte funktionieren, es ist nur, dass die Logik 'pb - pa 'als @ falsch ist RossBencina sagt. Repariere die Logik und deine Beispiele zeigen, dass es gut funktioniert. – Mine

6

Diese beide zum gleichen Ergebnis führen werden, so dass der Unterschied meist semantisch ist, und reinterpret_cast hat genau die Bedeutung der Operation, die Sie wollen, sowie die Tatsache, dass nur ein Guss stattdessen erforderlich ist von zwei (und je weniger Besetzung Sie in Ihrem Code haben, desto besser).

reinterpret_cast

5.2.10/7: ein Objektzeiger umgewandelt explizit auf einen Objektzeiger eines anderen Typs ist. Wenn ein Prvalue v von Objektzeigertyp in den Objektzeiger Typ "Zeiger auf CV T" konvertiert wird, ist das Ergebnis static_cast < cv T *> (static_cast < cv void *> (v)).

So sei denn, ein exotique Zufalls niedrigen Niveau unterschiedliches Verhalten erscheint auf einem mittleren Alter Plattform, sollten Sie auf jeden Fall mit gehen:

reinterpret_cast<char*>(ptr); 

Im Allgemeinen.

Das sagte, warum verwenden Sie uintptr_t in Ihrem Fall nicht? es ist noch apropriate, brauchen Sie keine Zeiger:

void approach3(A *pa, B *pb) 
{ 
    std::ptrdiff_t offset = reinterpret_cast<std::uintptr_t>(pa) - reinterpret_cast<std::uintptr_t>(pb); 
    // ... 
    A *a = reinterpret_cast<A*>(reinterpret_cast<std::uintptr_t>(pb) + offset); 
} 

Weitere Informationen finden Sie unter:

http://en.cppreference.com/w/cpp/language/reinterpret_cast

+0

in Bezug auf 'uintptr_t': Ich würde sagen, dass der Unterschied zwischen zwei Zeigern am besten auf den Zeigern, nicht auf ganzen Zahlen berechnet wird. –

+3

@RossBencina ich würde das genaue Gegenteil sagen :) Sie berechnen einen Offset nicht eine Anzahl von Elementen, und deshalb werfen Sie in erster Linie auf 'char *', weil 'char' ein Byte groß ist.Aber Sie wollen nicht die Anzahl der Zeichen zwischen Ihren Zeigern, Sie wollen den Byte-Offset, der natürlicher durch Zahlen als durch Zeiger dargestellt wird. Zeigerdifferenz bedeutet die Anzahl der Elemente zwischen ihnen. – Drax

+0

wenn ich 'char *' durch 'byte_t' ersetzt habe, wo' sizeof (byte_t) == 1' wärst du glücklicher? Siehe auch den Kommentar von Peter Schneider zu der Frage nach reinterpret_cast, die Bitmuster nicht garantiert - ich denke, das bricht Ihren Ansatz von "uintptr_t". –

Verwandte Themen