[C++] Datentyp für große Bitfields
- Schrompf
- Moderator
- Beiträge: 5074
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
[C++] Datentyp für große Bitfields
Moin!
Ich brauch ein großes Bitfield. Da will ich mal als uint16_t reingreifen, evtl. mal mit uint64_t, und sicher auch mal mit der ganzen Macht von memset() oder AVX _m256. Welchen Datentyp nehme ich da als Basis, damit der Compiler die Fresse hält und ich nicht irgendwann in ein UndefinedBehaviour latsche?
Soweit ich weiß, ist Cast von und zu CharPointer immer ok. Aber gibt's noch was Anderes? Ein Char-Pointer aliased doch auch mit ALLEM, und es wär doch echt doof, wenn dann jeder beliebige Ref-Funktionsparameter für den Compiler nach Aliasing aussieht. Oder mach ich mir da zuviele Gedanken?
Ich brauch ein großes Bitfield. Da will ich mal als uint16_t reingreifen, evtl. mal mit uint64_t, und sicher auch mal mit der ganzen Macht von memset() oder AVX _m256. Welchen Datentyp nehme ich da als Basis, damit der Compiler die Fresse hält und ich nicht irgendwann in ein UndefinedBehaviour latsche?
Soweit ich weiß, ist Cast von und zu CharPointer immer ok. Aber gibt's noch was Anderes? Ein Char-Pointer aliased doch auch mit ALLEM, und es wär doch echt doof, wenn dann jeder beliebige Ref-Funktionsparameter für den Compiler nach Aliasing aussieht. Oder mach ich mir da zuviele Gedanken?
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Re: [C++] Datentyp für große Bitfields
ich bin auf uint64. Für FastPFOR und Arithmetische Kodierung beides ideal, weil man da die wenigsten Overlaps zu Nachbarfeldern hat (halb so viele wie bei uint32)
http://fedoraproject.org/ <-- freies Betriebssystem
http://launix.de <-- kompetente Firma
In allen Posts ist das imo und das afaik inbegriffen.
http://launix.de <-- kompetente Firma
In allen Posts ist das imo und das afaik inbegriffen.
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Interessante Frage, die mich in ganz ähnlicher Weise gerade auch beschäftigt.Schrompf hat geschrieben: ↑14.10.2024, 22:25 Welchen Datentyp nehme ich da als Basis, damit der Compiler die Fresse hält und ich nicht irgendwann in ein UndefinedBehaviour latsche?
Soweit ich weiß, ist Cast von und zu CharPointer immer ok. Aber gibt's noch was Anderes? Ein Char-Pointer aliased doch auch mit ALLEM, und es wär doch echt doof, wenn dann jeder beliebige Ref-Funktionsparameter für den Compiler nach Aliasing aussieht. Oder mach ich mir da zuviele Gedanken?
Die Kernfrage scheint mir zu sein, wann man guten Gewissens von einem Pointer-Typ in einen anderen Pointer-Typ casten darf, ohne dabei laut Standard UB auszulösen.
Bislang dachte ich, dass dies alleine vom Alignment abhängig sei - und solange dies den Anforderungen des Typs entspräche, sei alles "gut".
Deshalb hatte ich auch jeweils meine Buffer einfach mittels _Alignas entsprechend den Typ-Anforderungen ausgerichtet.
Von der ARM Architektur weiss ich, dass ein "Alignment-missmatch" zu einer nicht unerheblichen Performance-Strafe führt.
Abgesehen davon ist es aber der Hardware "egal" - was aber ja leider nicht bedeuten würde, dass es dann laut C(++) Standard auch tatsächlich legal ist.
Der Standard sagt hier leider klar: UB.
Übrigens scheint das Brechen des Alignment der x86/x64 Architektur weitestgehend egal zu sein (hörensagen - ohne Gewär).
https://hackaday.com/2022/05/10/data-al ... -the-ugly/
Seit gestern bin ich dabei mich in den C11 ISO Standard einzulesen (mit dem Ziel ihn systematisch durchzuarbeiten).
Und seit gestern kann ich mit Sicherheit sagen: Ein richtiges Alignment alleine reicht leider nicht aus, um aus der "UB-Zone" zu kommen.
Das Ganze ist (nach meinen Erfahrung gestern) auch nicht ganz trivial zu durchschauen und damit die Frage konkret zu beantworten.
Es gibt nicht die eine Stelle im Standard, die hier Klarheit bringen würde (oder ich habe sie noch nicht gefunden).
Es müssen an verschiedenen Stellen spezifische Definitionen miteinbezogen werden, die sich in verschiedenen Kapiteln befinden.
Ich kann dir aber soweit versichern:
Ja, das trifft genau zu: C11 Standard 6.5 Expressions §7 (s. 77)
Du könntest dir überlegen, ob dir ein union behilflich sein könnte?
Damit kann man in bestimmten Situationen die "Pointer-Cast-Problematik" umgehen.
Von der Idee her so, oder so ähnlich:
Code: Alles auswählen
union
{
unsigned char u8[8];
unsigned short u16[4];
unsigned int u32[2];
unsigned long long u64[1];
} u = { 0 };
unsigned char * p1 = u.u8; // Ok!
unsigned short * p2 = u.u16; // Ok!
unsigned int * p3 = u.u32; // Ok!
unsigned long long * p4 = u.u64; // Ok!
C11 und C18 unterscheiden sich jedoch nur marginal - und diese Abweichungen betreffen (nach meinen Recherchen) in erster Linie die stdlib.
Meine Vermutung: Wenn in einigen Monaten der neue C23 Standard erscheint, sollte der C18 Standard wiederum entsprechend günstiger zu haben sein.
- Schrompf
- Moderator
- Beiträge: 5074
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Dafür überhaupt Geld zu verlangen ist eine Sauerei. Ich finde es bemerkenswert, dass Du das trotzdem tust.
Ne union ist aber auch keine Lösung: wenn ich mich recht erinnere, darf man in ner Union immer nur auf den Teil zugreifen, denn man zuletzt geschrieben hat. Alles andere ist ebenfalls UB
Ne union ist aber auch keine Lösung: wenn ich mich recht erinnere, darf man in ner Union immer nur auf den Teil zugreifen, denn man zuletzt geschrieben hat. Alles andere ist ebenfalls UB
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Das scheint mir ein verbreitetes Missverständnis zu sein (und auch einer der Gründe, wieso ich den Standard durcharbeiten will).
K&R (Second Edition ANSI) sagt bei 6.8 auf S. 148 dazu
K. N. King hat in seinem Buch "C Programming - A Moddern Approach (Second Edition) ein Kapitel S. 519It is the programmer's responsibility to keep track of which type is currently stored in a union; the results are implementation-dependent if smothing is sored as one type and extractd as another.
Indem er genau solche Beispiele auch zeigt. Er schreibt lediglich:Using Unions to Provide Multiple Views of Data
Wäre ich bereits auf die entsprechende Stelle im Standard gestossen, hätte ich sie natürlich jetzt aufgeführt! :-)Be careful when using unions to provide mutliple views of data. Data that is valid in its original formats may be invalid when viewed as a different type, causing unexpected problems.
Laut C11 grundsätzlich erlaubt: C++ ISO Standard 6.5.2.3 Structure and union members (S. 83), Abschnitt 3 - Fussnote 95)
(Ich kann mir übrigens nicht vorstellen, dass sich C und C++ in diesem Aspekt unterscheiden) Danke Krishty :-(
Klar muss jedoch sein, dass das Ganze Implementationsabhängig sein "muss" - alleine aufgrund der LE/BE Thematik.
Aber vielleicht weiss es ja jemand aus dem Stehgreif?
Zuletzt geändert von starcow am 17.10.2024, 18:02, insgesamt 10-mal geändert.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
In C++ ist es definitiv UB, es sei denn man greift auf das erste Member der Union zu und es hat in allen Varianten den selben Typ. Also bei
union U {
struct A {
int type;
...
} a;
struct B {
int type;
...
} b;
} u;
ist es erlaubt, u.b.type zu lesen obwohl u.a zuletzt geschrieben wurde weil beide am Anfang sind und den selben Typ haben. Das musste zugelassen werden, weil sonst die Berkeley Sockets kaputtgehen (sockaddr_in & Co.). Alles andere, ab dem zweiten Member, ist UB (nicht IB).
Ob C sich dort unterscheidet weiß ich aus dem Stehgreif nicht; ich meine mich zu erinnern dass schon. Clang und GCC tolerieren das üblicherweise weil es in der Linux-Welt so stark verbreitet ist.
union U {
struct A {
int type;
...
} a;
struct B {
int type;
...
} b;
} u;
ist es erlaubt, u.b.type zu lesen obwohl u.a zuletzt geschrieben wurde weil beide am Anfang sind und den selben Typ haben. Das musste zugelassen werden, weil sonst die Berkeley Sockets kaputtgehen (sockaddr_in & Co.). Alles andere, ab dem zweiten Member, ist UB (nicht IB).
Ob C sich dort unterscheidet weiß ich aus dem Stehgreif nicht; ich meine mich zu erinnern dass schon. Clang und GCC tolerieren das üblicherweise weil es in der Linux-Welt so stark verbreitet ist.
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Ach Herr Jeeh! ^^
Danke Krishty! :-)
Edit:
Ich habs gefunden:
§6.5.2.3 Structure and union members (S. 83), Abschnitt 3 - Fussnote 95)
Es ist in C grundsätzlich erlaubt und wird (manchmal) unter dem Begriff "type punning" referenziert.
Beispiel: Du speicherst dir präzise eine u32 Zahl, die dann als float Repräsentation ungültig ist.
(Ist das in IEEE 754 überhaupt denkbar? Vielleicht ein NaN mittels 0xFFFFFFFF? Ich komm jetzt irgendwie auf kein Beispiel...)
Danke Krishty! :-)
Edit:
Ich habs gefunden:
§6.5.2.3 Structure and union members (S. 83), Abschnitt 3 - Fussnote 95)
Es ist in C grundsätzlich erlaubt und wird (manchmal) unter dem Begriff "type punning" referenziert.
Es können aber sogenannte trap representations enstehen, die dann UB sind (Kapitel 6.2.6).... a process sometimes called ‘‘type punning’’)
Beispiel: Du speicherst dir präzise eine u32 Zahl, die dann als float Repräsentation ungültig ist.
(Ist das in IEEE 754 überhaupt denkbar? Vielleicht ein NaN mittels 0xFFFFFFFF? Ich komm jetzt irgendwie auf kein Beispiel...)
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Es ist absolut unnötig als Privatperson den ISO Standard zu kaufen. Die entsprechenden Drafts sind frei verfügbar, eine aktuelle Liste findet sich hier:starcow hat geschrieben: ↑17.10.2024, 14:15 P. S.: Den C11 Standard hatte ich gewählt, weil er für rund 80.- zu haben war. Der aktuelle C18 Standard hätte rund 240.- gekostet.
C11 und C18 unterscheiden sich jedoch nur marginal - und diese Abweichungen betreffen (nach meinen Recherchen) in erster Linie die stdlib.
Meine Vermutung: Wenn in einigen Monaten der neue C23 Standard erscheint, sollte der C18 Standard wiederum entsprechend günstiger zu haben sein.
C: https://en.cppreference.com/w/c/links#C ... references
C++: https://en.cppreference.com/w/cpp/links ... and_drafts
Den ISO Standard brauchst du wenn du eine C oder C++ Implementierung als Standardkonform verkaufen willst oder sowas, wo du dann wirklich sichergehen willst, dass alles auf Punkt und Strich mit dem offiziellen ISO Dokument übereinstimmt.
Zuletzt geändert von dot am 18.10.2024, 23:29, insgesamt 1-mal geändert.
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Es ist in C technically erlaubt, effektiv aber ziemlich nutzlos. Was passiert ist völlig implementation-defined, am Ende bekommst du bestenfalls das selbe Ergebnis wie mit memcpy() oder std::bit_cast(). Und in C++ ist es ausnahmslos UB.
Zuletzt geändert von dot am 18.10.2024, 23:29, insgesamt 1-mal geändert.
- Schrompf
- Moderator
- Beiträge: 5074
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Konkret ist es so:
* Lesen in uint16, weil ich 16²-Blöcke vorauswähle
* Schreiben als uint16, weil ich mit SSE2 16 Byte lese und jeweils in ein Bit konvertiere
* Lesen und Schreiben als __m128i, weil ich gelegentlich paar Kilobyte davon maximal schnell verUNDen muss.
Weggefallen, aber evtl. irgendwann noch notwendig:
* Lesen und Schreiben als uint64, weil das der schnellste portable Zugriff für das Massen-UND ist.
* Lesen in uint16, weil ich 16²-Blöcke vorauswähle
* Schreiben als uint16, weil ich mit SSE2 16 Byte lese und jeweils in ein Bit konvertiere
* Lesen und Schreiben als __m128i, weil ich gelegentlich paar Kilobyte davon maximal schnell verUNDen muss.
Weggefallen, aber evtl. irgendwann noch notwendig:
* Lesen und Schreiben als uint64, weil das der schnellste portable Zugriff für das Massen-UND ist.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Na dann vermutlich einfach __m128i und per _mm_set_epi16() bzw. _mm_extract_epi16() drauf zugreifen?
- Schrompf
- Moderator
- Beiträge: 5074
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Nun so wäre der Datentyp konsistent, ja. Hm. Und ich merke schon, es gibt keine Methode, dem Compiler den Mund zu verbieten.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Das scheint mir bisschen krass formuliert. Nach Allem was ich so lese, ist es wohl ziemlich populär und beliebt in C (Kernelentwicklung als Beispiel).
gcc manual: https://gcc.gnu.org/onlinedocs/gcc/Opti ... pe-punning
Was wären denn eigentlich gute Anwendungsfälle für ein Union, wenn man nur das lesen darf, was man zuletzt geschrieben hat?The practice of reading from a different union member than the one most recently written to (called “type-punning”) is common. Even with -fstrict-aliasing, type-punning is allowed, provided the memory is accessed through the union type.
Gibts da etwas?
- Schrompf
- Moderator
- Beiträge: 5074
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Klar, all die Anwendungen, wo Du dynamisch je nach "Art" irgendnen Payload unterbringst. Nachrichten ausm Netzwerk zum Beispiel. Oder Elemente einer GUI.
Du vergleichst aber immer noch C (Deine Welt) mit C++ (was dot schrieb). Das sind zwei verschiedene Welten, und in diesem Fall unterscheiden sie sich drastisch.
Ein Leut im C++-Discord meinte, ich solle doch einfach bit_cast benutzen. Ich denke, das ist trotzdem UB, sobald ich den Pointer dann dereferenziere. Aber xq hat heute mal verschiedene Wege im Godbolt durchgespielt, und bei *keinem* davon hat der UB-Sanatizer angeschlagen. Echt komisch.
Du vergleichst aber immer noch C (Deine Welt) mit C++ (was dot schrieb). Das sind zwei verschiedene Welten, und in diesem Fall unterscheiden sie sich drastisch.
Ein Leut im C++-Discord meinte, ich solle doch einfach bit_cast benutzen. Ich denke, das ist trotzdem UB, sobald ich den Pointer dann dereferenziere. Aber xq hat heute mal verschiedene Wege im Godbolt durchgespielt, und bei *keinem* davon hat der UB-Sanatizer angeschlagen. Echt komisch.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
Alle Variant-Typen (std::variant & Co.)
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: [C++] Datentyp für große Bitfields
populär heißt nicht automatisch gut ;)starcow hat geschrieben: ↑24.10.2024, 19:41Das scheint mir bisschen krass formuliert. Nach Allem was ich so lese, ist es wohl ziemlich populär und beliebt in C (Kernelentwicklung als Beispiel).
gcc manual: https://gcc.gnu.org/onlinedocs/gcc/Opti ... pe-punningThe practice of reading from a different union member than the one most recently written to (called “type-punning”) is common. Even with -fstrict-aliasing, type-punning is allowed, provided the memory is accessed through the union type.
Wie gesagt, die Methode hat keinen Vorteil gegenüber einem memcpy() oder std::bit_cast() (vielleicht mir der einen Ausnahme dass memcpy() in einem unoptimierten Build tatsächlich zu einem Funktionsaufruf wird). Du bekommst bestenfalls das selbe Ergebnis wie mit memcpy() oder std::bit_cast().
unions sind gedacht für wann immer du einen Wert aus einer bekannten Menge an potentiellen Typen speichern musst, wo der konkrete Typ erst zur Laufzeit bekannt is, das selbe Objekt im Laufe seines Lebens potentiell Werte von verschiedenen Typen annehmen muss und du die minimal notwendige Menge an Speicher dafür reservieren willst. Das ist der eigentliche Zweck einer union. Type-punning is nur ein Hack der sich unglücklicherweise weit verbreitet hat, dafür waren unions nie wirklich gedacht. C hat nur irgendwann mal (iirc in C99) die Hoffnung aufgegeben, dass die Leute das lernen. C++ hat ein sehr viel komplexeres Objektmodell und kann es sich nicht wirklich leisten, die Hoffnung aufzugeben.