Dieser hack wird verwendet um:
1) Gut getarnte Sicherheitslücken an verschiedensten Stellen einzubauen
2) Zu verhindern, dass es auf big endian Systemen läuft
3) Den Switch 32->64Bit zu erschweren
4) Undefined behaviour durch Type-Punning einzubauen
etc.
Also durchaus zahlreiche Anwendungsfälle. :)
Das ist eine Konstruktion, um eine Variable irgendeines Typs wie einen
16- oder 32-Bit-unsigned-Int beschreiben zu können.
Das setzt voraus, daß sizeof int < sizeof long ist, was z.B. beim AVR
der Fall ist.
Etwas "portabler" wird das, wenn statt "unsigned int" und "unsigned
long" die Typen "uint16_t" und "uin32_t" verwendet werden.
Das Konstrukt ist mit Vorsicht zu genießen, das hier beispielsweise geht
in die Hose:
1
charc;
2
3
WORD(c)=1234;
4
LONG(c)=0xdeadbeef;
Warum? Weil c nur Platz für ein einzelnes Byte zur Verfügung stellt, im
ersten Fall aber zwei und im zweiten sogar vier Bytes beschrieben
werden.
D.h. der Speicher ist dann korrumpiert.
Und je nach Prozessorarchitektur kann das auch zu lustigen
Alignment-Fehlern führen; viele Prozessoren können 16- bzw.
32-Bit-Zugriffe nur an ausgerichteten Adressen ausführen, und werfen
Exceptions, wenn sie unpassende ("misaligned") Adressen verwenden
sollen.
X kann ja auch char sein. Du kannst auf den Element von ein unsigned
char[] als unsigned int zugreifen.
Zum Beispiel bei Serielle empfangen weißt du das array[5] und array[6]
ein Länge ist mit 16 bit. Damit kannst du WORD(array[6]) nehmen und der
Compiler versucht den als unsigned int auszulesen. Ggf. ist das
schneller als shiften und ||.
Das macht was ganz dreckiges. Es castet einen Pointer auf einen anderen
Typ, d.h. der Compiler warnt nicht und konvertiert auch nicht.
Man kann sich damit z.B. die Hex-Entsprechung eines float Wertes
ansehen.
Rufus Τ. F. schrieb:> Das setzt voraus, daß sizeof int < sizeof long ist, was z.B. beim AVR> der Fall ist.
Nein. Es ist erstmal egal, wie der User WORD und LONG in seinem
Universum interpretiert. Und er kann mit plattformspezifischer Anpassung
dieser #defines den Code "Portieren" (wenn man von U.B. oder Endian oder
oder absieht.
A.S. schrieb:> Nein. Es ist erstmal egal, wie der User WORD und LONG in seinem> Universum interpretiert.
Nun, mit einem 32-Bit-Compiler machen beide Macros das gleiche.
Mit einem 16-Bit/8-Bit-Compiler aber nicht.
Und was bei einem 64-Bit-Compiler rauskommt, kann mal so, mal anders
aussehen.
Daß die Namen beknackt und irreführend sind (insbesondere "LONG", das
mit unsigned long gleichgesetzt wird), ist ein anderes Paar Schuhe.
Gerdi schrieb:> Hallöchen,>> was genau hat man mit diesen Zeilen bezweckt?> Springt mir nicht direkt ins Auge <^-^>..>>
1
>#defineWORD(X)(*((unsignedint*)&(X)))
2
>#defineLONG(X)(*((unsignedlong*)&(X)))
3
>
>> Außer das es im Endeffekt nur den Wert zurückliefert?
Mit diesen Zeilen spart man vermeintlich Schreibarbeit, um den Preis
dass der Code dafür schlechter wird (weil nicht offensichtlich ist was
passiert).
Wenn man hart auf eine Adresse im Speicher schreiben will, dann sollte
man das nicht in einem Makro verstecken. Sooooo oft wird man das nicht
brauchen, wenn man vernünftigen Code schreibt.
Dass die Namen der Makros nicht besonders intelligent gewählt sind, hat
Rufus ja schon richtig beschrieben.
DPA schrieb:> Dieser hack wird verwendet um:> 1) Gut getarnte Sicherheitslücken an verschiedensten Stellen einzubauen> 2) Zu verhindern, dass es auf big endian Systemen läuft> 3) Den Switch 32->64Bit zu erschweren> 4) Undefined behaviour durch Type-Punning einzubauen
5) Verletzung der C/C++ Strict Aliasing Regeln
6) Je nach Plattform unaligned Access
Rufus Τ. F. schrieb:> Daß die Namen beknackt und irreführend sind (insbesondere "LONG", das> mit unsigned long gleichgesetzt wird), ist ein anderes Paar Schuhe.
absolut beknackt
Gerdi schrieb:> #define WORD(X) (*((unsigned int *) &(X)))> #define LONG(X) (*((unsigned long *) &(X)))
wenn schon denn doch so
> #define UWORD(X) (*((unsigned int *) &(X)))> #define ULONG(X) (*((unsigned long *) &(X)))> #define WORD(X) (*((int *) &(X)))> #define LONG(X) (*((long *) &(X)))
aber wir hatten damals
zu DOS Zeiten
> #define (UWORD) (unsigned int)> #define (ULONG) (unsigned long)> #define (WORD) (int)> #define (LONG) (long)
geschrieben, wobei mir heute
uint8_t
uint16_t
uint32_t
uint64_t
int8_t
int16_t
int32_t
int64_t
besser gefällt
DPA schrieb:> Dieser hack wird verwendet um:> 1) Gut getarnte Sicherheitslücken an verschiedensten Stellen einzubauen
Sicherheitslücken auf nen 8 Bitter???
Nur als interessante Information, sowas wird in der Berechnung der
invertierten Wurzel im Quake-III-Quelltext verwendet.
https://en.wikipedia.org/wiki/Fast_inverse_square_root
Es musste sein, aber die wissen, dass das extrem unschön ist.
Trotzdem mag ich diesen Trick. :-)
Rufus Τ. F. schrieb:> un, mit einem 32-Bit-Compiler machen beide Macros das gleiche.
Genau deshalb solche Makros, mit denen man Plattform-abweichungen
behandelt.
Wenn Word 16 Bit sein sollen, dann halt short
loeti2 schrieb:> Mit std::bit_cast (since C++20)> https://en.cppreference.com/w/cpp/numeric/bit_cast
Danke. Also bis auf weiteres geht das ausschließlich mit memcpy.
Dussel schrieb:> Kommt das tatsächlich so oft vor, dass das im neuen Standard> neu eigeführt wird? Sowas habe ich noch nie gebraucht.
Man braucht es z.B. immer dann, wenn man "Daten unbestimmten Typs" hat,
die man je nach Kontext (datenabhängig) unterschiedlich interpretieren
muss. Ein Beispiel wären Interpreter, denn die bekommen nur einen Byte-
oder Word-Datenstrom und müssen daraus sämtliche Datentypen
regenerieren. Wie man das bei JIT-Compilern löst, habe ich mir noch
nicht näher angeschaut, aber das ist bestimmt auch spaßig.
Man kann alles auch in Software lösen (notfalls, indem man die gesamte
Arithmetik in Software nachbaut), aber das ist alles langsam - was man
gerade bei Interpretern nicht möchte. Deswegen gibt es auch Konstrukte
wie z.B. "computed goto", weil sich damit schneller und halbwegs gut
lesbarer Code für diese Fälle bauen lässt. An anderen Stellen sind das
furchtbar grausige Hacks...
S. R. schrieb:> Type-punning with a union type is considered undefined behavior> in C++ standards.
Darüber streiten sich die Gelehrten, die Formulierungen kann man so oder
so auslegen. Höchstwahrschinlich ist es implementation defined wie bei C
auch, das jeweilige ABI gibt vor wie das Speicherlayout einer Union
auszusehen hat. Bei C++ wurde nur vergessen die bei C nachträglich
eingefügte Fußnote mit dieser Klarstellung mit reinzuschreiben, das
Grundproblem ist aber exakt genauso gelagert.