[C++] Datentyp für große Bitfields

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

[C++] Datentyp für große Bitfields

Beitrag von Schrompf »

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?
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
antisteo
Establishment
Beiträge: 928
Registriert: 15.10.2010, 09:26
Wohnort: Dresdem

Re: [C++] Datentyp für große Bitfields

Beitrag von antisteo »

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.
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von starcow »

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?
Interessante Frage, die mich in ganz ähnlicher Weise gerade auch beschäftigt.
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:
Schrompf hat geschrieben: 14.10.2024, 22:25 Soweit ich weiß, ist Cast von und zu CharPointer immer ok.
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!
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.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
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

Beitrag von Schrompf »

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
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von starcow »

Schrompf hat geschrieben: 17.10.2024, 14:37 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
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
It 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.
K. N. King hat in seinem Buch "C Programming - A Moddern Approach (Second Edition) ein Kapitel S. 519
Using Unions to Provide Multiple Views of Data
Indem er genau solche Beispiele auch zeigt. Er schreibt lediglich:
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.
Wäre ich bereits auf die entsprechende Stelle im Standard gestossen, hätte ich sie natürlich jetzt aufgeführt! :-)
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.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
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

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von starcow »

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.
... a process sometimes called ‘‘type punning’’)
Es können aber sogenannte trap representations enstehen, die dann UB sind (Kapitel 6.2.6).
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...)
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von dot »

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.
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:
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.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von dot »

starcow hat geschrieben: 17.10.2024, 15:36 Es ist in C grundsätzlich erlaubt und wird (manchmal) unter dem Begriff "type punning" referenziert.
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.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von dot »

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?
Schwer zu sagen, müsste man mehr über den konkreten Anwendungsfall wissen…
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
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

Beitrag von Schrompf »

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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von dot »

Na dann vermutlich einfach __m128i und per _mm_set_epi16() bzw. _mm_extract_epi16() drauf zugreifen?
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
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

Beitrag von Schrompf »

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.
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von starcow »

dot hat geschrieben: 18.10.2024, 23:27
starcow hat geschrieben: 17.10.2024, 15:36 Es ist in C grundsätzlich erlaubt und wird (manchmal) unter dem Begriff "type punning" referenziert.
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.
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
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.
Was wären denn eigentlich gute Anwendungsfälle für ein Union, wenn man nur das lesen darf, was man zuletzt geschrieben hat?
Gibts da etwas?
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
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

Beitrag von Schrompf »

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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
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

Beitrag von Krishty »

starcow hat geschrieben: 24.10.2024, 19:41Was wären denn eigentlich gute Anwendungsfälle für ein Union, wenn man nur das lesen darf, was man zuletzt geschrieben hat?
Gibts da etwas?
Alle Variant-Typen (std::variant & Co.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Datentyp für große Bitfields

Beitrag von dot »

starcow hat geschrieben: 24.10.2024, 19:41
dot hat geschrieben: 18.10.2024, 23:27
starcow hat geschrieben: 17.10.2024, 15:36 Es ist in C grundsätzlich erlaubt und wird (manchmal) unter dem Begriff "type punning" referenziert.
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.
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
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.
populär heißt nicht automatisch gut ;)

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().
starcow hat geschrieben: 24.10.2024, 19:41 Was wären denn eigentlich gute Anwendungsfälle für ein Union, wenn man nur das lesen darf, was man zuletzt geschrieben hat?
Gibts da etwas?
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.
Antworten