Ich bin auf das Makro unten gestoßen
%Vor%Ich bin irgendwie nicht in der Lage, dies zu verdauen, weil ich in C ++, wenn ich versuche, einen Null-Zeiger zu verwerfen, ein unerwartetes Verhalten erwarte ... aber wie kommt es, dass es eine Adresse haben kann? Was bedeutet die Adresse von Null?
Für den Zweck des Makros:
Es nimmt an, dass ein Objekt vom Typ TYPE
bei Adresse 0 ist und gibt die Adresse des Elements zurück, das effektiv der Offset des Elements in der Struktur ist.
This Antwort erklärt, warum dies ein undefiniertes Verhalten ist. Ich denke, das ist das wichtigste Zitat:
Wenn
E1
den Typ "Zeiger auf Klasse X" hat, wird der AusdruckE1->E2
in die äquivalente Form(*(E1)).E2
umgewandelt;*(E1)
wird dazu führen undefiniertes Verhalten mit einer strengen Interpretation, und.E2
konvertiert es zu einem rvalue, was es zu undefiniertem Verhalten für die Schwachen macht Interpretation.
was hier der Fall ist. Obwohl andere denken, dass dies gültig ist. Es ist wichtig zu beachten, dass dies jedoch das korrekte Ergebnis für viele Compiler erzeugt.
ist einer ziemlich allgemeinen Definition des Standard-Makros offsetof()
sehr ähnlich, das in <stddef.h>
(in C) oder <cstddef>
(in C ++) definiert ist.
0
ist eine Nullzeigerkonstante . Wenn Sie es in TYPE *
umsetzen, erhalten Sie einen Nullzeiger vom Typ TYPE *
. Beachten Sie, dass die Sprache nicht garantiert (oder sogar impliziert), dass ein Nullzeiger den Wert 0 hat, obwohl dies sehr häufig der Fall ist.
So (TYPE *)0
ist theoretisch die Adresse eines Objekts vom Typ TYPE
, die sich an der Adresse befindet, auf die der Nullzeiger zeigt, und ((TYPE *)0)->ELEMENT))
ist das ELEMENT
Mitglied dieses Objekts.
Der Operator &
nimmt die Adresse dieses Elements ELEMENT
, und der Cast konvertiert diese Adresse in size_t
.
Nun wenn ein Nullzeiger auf Adresse 0 zeigt, dann beginnt das (nicht existierende) Objekt vom Typ TYPE
Objekt an Adresse 0 und die Adresse des ELEMENT
Mitglieds davon Das Objekt befindet sich an einer Adresse, die um eine bestimmte Anzahl von Bytes von Adresse 0 versetzt ist. Unter der Annahme, dass sich die implementierungsdefinierte Konvertierung von TYPE *
nach size_t
direkt verhält (etwas anderes, das nicht von der Sprache garantiert wird), ist das Ergebnis Der gesamte Ausdruck wird der Offset des Elements ELEMENT
in einem Objekt vom Typ TYPE
sein.
All dies hängt von mehreren nicht definierten oder nicht spezifizierten Verhaltensweisen ab. Bei den meisten modernen Systemen ist der Nullzeiger als ein Zeiger auf die Adresse 0 implementiert, Adressen (Zeigerwerte) sind so dargestellt, als ob sie Ganzzahlen wären, die den Index eines bestimmten Bytes innerhalb eines monolithischen Adressraumes spezifizieren, und von einem Zeiger in eine ganze Zahl umgewandelt werden von der gleichen Größe neu interpretiert nur die Bits. Auf einem System mit solchen Merkmalen funktioniert das Makro OFFSETOF
wahrscheinlich, und die Implementierung kann eine ähnliche Definition für das standardmäßige Makro offsetof
verwenden. (Code, der Teil der Implementierung ist, kann das implementierungsdefinierte oder undefinierte Verhalten nutzen; er muss nicht portierbar sein.)
Auf Systemen, die diese Eigenschaften nicht aufweisen, funktioniert dieses Makro OFFSETOF
möglicherweise nicht - und die Implementierung muss eine andere Methode zum Implementieren von offsetof
verwenden. Deshalb ist offsetof
Teil der Standardbibliothek; Es kann nicht portabel implementiert werden, aber es kann immer in einige Weise für jedes System implementiert werden. Und einige Implementierungen verwenden Compiler-Magie, wie gcc __builtin_offsetof
.
In der Praxis macht es nicht viel Sinn, Ihr eigenes Makro OFFSETOF
so zu definieren, da jede konforme C- oder C ++ - Implementierung ein funktionierendes offsetof
-Makro in seiner Standardbibliothek zur Verfügung stellt.
Dies ist kein Dereferenzieren eines Zeigers, sondern gibt den Offset des Elements in der Struktur zurück.
zum Beispiel für
%Vor% Der Aufruf von OFFSETOF(someStruct, b)
wird 1 zurückgeben (vorausgesetzt, es ist gepackt usw.).
Dies ist dasselbe wie:
%Vor% außer dass mit OFFSETOF
keine Dummy-Variable erstellt werden muss.
Dies ist erforderlich, wenn Sie aus irgendeinem Grund einen Offset des Members class / struct / union finden müssen.
** Bearbeiten **
An alle eiligen Downvoters, die denken, dass "der Standard dies nicht zulässt" - bitte lesen Sie den Standard noch einmal. Das Verhalten ist in diesem Fall sehr gut definiert.
** Noch eine Bearbeitung **
Ich glaube, keiner der Downvoters hat gemerkt, dass der erste Parameter type ist. Ich bin mir sicher, dass Sie Ihren Fehler verstehen werden, wenn Sie ein wenig mehr als eine halbe Sekunde zum Downvote denken. Wenn nicht - na ja, es wird nicht der erste sein, dass ein Haufen unwissender Downvoter eine richtige Antwort unterdrückt.
Die Dereferenzierung eines Null-Zeigers (wie dieses Makro funktioniert) ist ein undefiniertes Verhalten. Es ist nicht erlaubt, ein solches Makro zu schreiben und zu verwenden, es sei denn, das Implementation gibt Ihnen eine besondere, zusätzliche Garantie.
Die C-Standardbibliothek definiert ein Makro offsetof
; viele Implementierungen
benutze etwas ähnliches. Die Implementierung kann es tun, weil
es weiß, was der Compiler in diesem Fall tatsächlich erzeugt und ob
Es wird Probleme verursachen oder nicht. Die Implementierung des Standards
Bibliothek kann viele Dinge verwenden, die Sie nicht können.
Der Zweck von OFFSETOF
besteht darin, den Abstand zwischen der Adresse eines Mitglieds und der Adresse des zugehörigen Aggregats zurückzugeben.
Wenn der Compiler das Objektlayout nicht in Abhängigkeit von seiner Platzierung ändert, ist dieser "Abstand" konstant und daher ist die Adresse, von der Sie ausgehen, irrelevant. 0, in diesem Fall ist es nur eine Adresse wie jede andere.
Nach C ++ - Standard ist der Zugriff auf eine ungültige Adresse "undefiniertes Verhalten", aber:
Wenn das Teil einer Compiler-Support-Bibliothek ist (das ist der eigentliche Code von "OFFSETOF" in der CRT, der mit VS2003 kommt!), ist das vielleicht nicht so "undefiniert" (für einen bekannten Compiler und eine Plattform) Verhalten ist dem Entwickler der Support-Bibliothek bekannt: Natürlich muss dies als "plattformspezifischer Code" betrachtet werden, aber unterschiedliche Plattformen werden wahrscheinlich unterschiedliche Bibliotheksversionen haben.
In jedem Fall "handeln" Sie nicht mit dem Element (es wird also kein "Zugriff" ausgeführt), indem Sie einfach eine einfache Zeigerarithmetik ausführen. Thnk als eine allgemeine Darstellung wie " Wenn es ein Objekt an der Stelle 0 gibt, wird sein angenommenes Element ELEMENT an der Stelle 6 beginnen. Daher ist 6 der Versatz ". Die Tatsache, dass es kein echtes solches Objekt gibt, ist irrelevant.
Übrigens, dieses Makro scheitert (mit einem Segmentierungsfehler!), wenn das ELEMENT von TYPE mittels einer virtuellen -Basis vererbt wird, um die Platzierung der virtuellen Basis zu ermitteln Sie müssen auf einige Laufzeitinformationen zugreifen - normalerweise Teil einer Objekt-V-Tabelle -, deren Position nicht erkannt werden kann, da die Objektadresse keine "echte" Adresse ist. Das ist der warum der Standard sagt cautelativ, dass "Dereferenzierung eines ungültigen Zeigers ist undefiniertes Verhalten".
TO DOWNVOTERS:
Ich biete plattformspezifische Informationen für eine Plattform spezifisch an. Vor dem Downvote stellen Sie bitte eine Demonstration vor, dass das, was ich sagte, falsch ist.
Das ist ein höllisches Makro und stapelt unbestimmtes Verhalten ...
Was es versucht: den Offset eines struct
Mitglieds zu bekommen.
Wie es versucht wird:
&
) size_t
Es gibt zwei Probleme:
size_t
sollte nicht durchgeführt werden (das Problem ist, dass ein Zeiger nicht garantiert passt) Wie es gemacht werden könnte:
Im Code:
%Vor%Allerdings benötigt es ein Objekt, daher ist es möglicherweise nicht für Ihre Zwecke geeignet.
So sollte es gemacht werden :
%Vor%Aber es würde wenig Sinn machen ... richtig?
Für die Neugierigen kann die Definition etwas wie: __builtin_offsetof(st, m)
(von Wikipedia ) sein. Einige Compiler implementieren es mit Null-Dereferenzierungen, aber sie sind die Compiler und wissen daher, dass sie diesen Fall sicher behandeln. das ist nicht portierbar ... und muss nicht sein, seit man den Compiler gewechselt hat, man wechselt auch die C-Bibliotheksimplementierung.
A. Die Aktion ist gültig, es wird keine Ausnahme ausgelöst, weil Sie nicht versuchen, auf den Speicher zuzugreifen, auf den der Zeiger zeigt.
B. Null-Zeiger - es ist im Grunde ein normaler Zeiger, der sagt, dass das Objekt in Adresse 0 sitzt (Adresse 0 ist definitionsgemäß eine ungültige Adresse für reale Objekte), aber der Zeiger ist selbst gültig.
Also ist dieses Makro gemeint: wenn ein Objekt vom Typ TYPE in Adresse 0 beginnt, wo wird sein ELEMENT gespeichert? Mit anderen Worten, was ist der Offset von ELEMENT zum Start des TYPE-Objekts.
littleadv hatte die Absicht des Konstrukts genau richtig. Erklären Sie ein wenig: Sie werfen einen Struct-Pointer, der auf die Adresse 0x0 zeigt, und die Dereferenzierung seiner Elemente. Die Adresse, auf die Sie zeigen, ist jetzt bei 0x0 +, was auch immer das Element hat. Jetzt werfen Sie diesen Wert auf eine Größe_t und erhalten den Offset des Elements.
Ich bin mir nicht sicher, wie tragbar dieses Konstrukt ist.