-Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4!

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

-Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4!

Beitrag von DerAlbi »

Hallo Leute,
Wiedereinmal ein tolles C++-Problem mit Templates!

Und zwar geht es um das Zuweisen von Bitfield-Members.
Beispiel-Bitfeld:

Code: Alles auswählen

struct SBitfield
{
	unsigned int a : 4;
	unsigned int b : 4;
	unsigned int   : 24;
};
static_assert(sizeof(SBitfield) == 4 ,"Heidewitzka!");

unsigned int MegaZahl = 5;
//...
SBitfield Bitfield;
Bitfield.a = MegaZahl; //  <---- [-Wconversion]
(void)Bitfield.a;
Der GCC wirft hier:
conversion to 'unsigned char:4' from 'unsigned int' may alter its value [-Wconversion]
Ok.
Es hilft nicht:

Code: Alles auswählen

unsigned int MegaZahl:4 = 5; //Geht nicht O_o
unsigned char MegaZahl = 5; //Selbe Fehlermeldung.. (ja, mit 'unsigned int')
Nun habe ich mir ein Hilfstemplate geschrieben:

Code: Alles auswählen

template<unsigned int N, typename T> union NBitValue
{
	static_assert((sizeof(T)*8) > N, "Komische Bitanzahl");
private:
	const T Data;
public:
	struct
	{
		const T Value : N;
		const T : (sizeof(T)*8-N);
	};
	NBitValue(const T& _Data): Data(_Data) {}
};
Damit kann ich jetzt schreiben:

Code: Alles auswählen

Bitfield.b = NBitValue<4, unsigned int>(MegaZahl).Value;
Und alles ist in Butter, weil Value die gleiche Bitanzahl hat usw.

Nun die große Frage!
Wieso kann der Compiler den Typ T nicht deduzieren(, deducten, deductziieren.. whatever).
Es ist mMn klar, dass der Konstruktor von NBitValue<4>(MegaZahl) mit dem Typ T=unsigned int aufgerufen wird.
Dennoch werde ich gezwungen den Typen immer hinzuschreiben...
Wieso?

Ich hätte gern einfach nur:

Code: Alles auswählen

Bitfield.b = NBitValue<4>(MegaZahl).Value;
Und falls noch jemand weiß, wie man auf das .Value verzichten kann.. immer raus damit!
:-)
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von dot »

Mal rein prinzipiell: Wieso muss es ein Bitfield sein?
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von DerAlbi »

Hardwarenahes Zeugs. Ist halt so.
Da gibts (MemoryMapped)Register, die irgendwas hardwaremäßiges konfigurieren.. aber ein Register konfiguriert nicht nur einen Aspekt der Peripherie, sondern in dem Register sind gleich diverse Bitfelder untergebracht. z.B. kann eine PLL ein einen Multiplikations- und Divisionsfaktor haben, damit man Grundtakt * 4/3 einstellen kann. Der Multiplikator und Divisor sind aber in einem einzigen Registe - der Multiplikator istvllt 4Bits breit, der Divisor evtl nur 3Bit usw... Um da geschmeidig drauf zugreifen zu können nutzt man z.B. Bitfields, die so ein Register abbilden.
Da muss man nicht immer so sinnlose Bitmaskiererei schreiben. Das ist nicht nur hässlich, sondern auch Copy&Paste-Fehleranfällig.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von dot »

Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von Krishty »

Ich widerspreche vielem, was Alexandrescu sagt (er gibt ja eine Minute ins Video zu, dass er keine Ahnung von LLVMs Code Generation hat). Ich würde auch kein 200-Zeilen-Template anlegen, um Bits in einem Register zu setzen. Aber prinzipiell hat er völlig recht damit, dass Bitfields maximal Pain in the Ass sind. Mach ’ne Inline-Funktion draus, dann ist das nicht mehr Copy-Paste-Fehleranfällig.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von DerAlbi »

Ich hab das schon als Inlinefunktion gemacht.. das löst das jetzige Problem nicht. In gewissen Situation haben Bitfields ihre Daseinsberechtigung, da bin ich stark dafür, unabhäängig davon, was andere da sagen. Ich vermute, dass andere Leute in iherer Hardwareumgebung nur selten mit in Hardware implementierten Bitfeldern zu tun haben. Ich weiß nicht, warum man das dann nicht auch tatsächlich in Software abbilden sollen können dürfen könnte.
Das finde ich das gleiche Niveau, wie wenn jemand sagt, dass man goto nicht verwenden darf.

Prinzipiell könnte man die Warnung [-Wconversion] auch einfach abschalten, aber ich finde die Warnung gar nicht so dumm. So entscheidet man sich immer explizit, Bits wegzuwerfen und die Typgröße zu verringern.

Aber da ihr nun alle auf den Bitfields rumhackt und es nicht um das eigentliche Problem geht, habe ich mitlerweile eine Lösung gefunden :-)
Grundproblem war/ist, das Template-Parameter-Deduction nur für Funktionsaufrufe, nicht aber für Klassen-Templates funktioniert :!: [/u]

Die Lösung sieht nun so aus:

Code: Alles auswählen

template<unsigned int N, typename T> union NBitValueUnion
{
	static_assert((sizeof(T)*8) > N, "Komische Bitanzahl");
private:
	const T Data;
public:
	struct
	{
		const T Value : N;
		const T : (sizeof(T)*8-N);
	};
	NBitValueUnion(const T& _Data): Data(_Data) {}
};
template<unsigned int N, typename T> const NBitValueUnion<N, T> NBitValue(const T& _Data)
{ return NBitValueUnion<N, T>(_Data); }
Damit kann ich jetzt tatsächlich gemütlich:

Code: Alles auswählen

Bitfield.b = NBitValue<4>(MegaZahl).Value;
schreiben und alles ist gut. Um das .Value komme ich leider nicht drum rum.
Ich habe auch

Code: Alles auswählen

template<unsigned int N, typename T> const auto NBitValue2(const T& _Data) ->decltype(NBitValueUnion<N, T>::Value)
{ return NBitValueUnion<N, T>(_Data).Value; }
probiert. Leider wird im auto die Typbreite irgnoriert [könnte man fast als compiler bug sehen], sodass [-Wconversion] wieder aufploppt.

Das Gute ist jetzt, dass man zur Laufzeit nun checken könnte, ob bei der Zuweisung Informationen verloren gehen (Bitfield-Überlauf). Der Compiler macht das für Compile-Time-Konstanten auch schon.. zum debuggen nicht schlecht. Ich weiß, das ginge mit der üblichen Bitschieberei auch.

Und hier muss ich nochmal widersprechen: eine Inline-Funktion ist anfällig für Fehler. Gehen wir nochmal vom Beispiel-Bitfeld aus.

Code: Alles auswählen

union SBitfield
{
        unsigned int Data;
        struct
        {
                unsigned int a : 4;
                unsigned int b : 4;
                unsigned int   : 24;
        };
};
Angenommen du hättest diese Definition nicht direkt vor Augen, würde es dir mitten im Code auffallen, dass du mit

Code: Alles auswählen

Bitfield.Data = SetBitField<2 /*Start*/, 4 /*Width*/>(Bitfield.Data, NewContent);
keines der in Hardware real existierenden Bitfelder triffst? :o
Nein, man übersieht es. Der Code compiliert und er läuft auch. Nur die Hardware verhält sich dann dumm.
Mit

Code: Alles auswählen

Bitfield.a = NewContent; 
//bzw
Bitfield.a = NBitValue<4>(NewContent);
ist hingegen absolut sichergestellt, dass man keinen Fehler macht.
Und der Assembler-Code ist der selbe.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von Krishty »

Ich meinte eher einen Wrapper um das Setzen des Registers, statt überall herumzuschreien, dass wir ein Bitfield haben:

  struct TimingRegister { // Wenn es nur an einer Adresse gemappt sein kann, kann man das struct auch weglassen
    uint32_t packed;
  };

  void setFrequency(TimingRegister & r, unsigned int numerator, unsigned int denumerator) {
    assert(isWithin(1, numerator, 15) && isWithin(1, denumerator 7));
    r.packed = (r.packed & 0xFF01FFFF) | (numerator << 24) | (denumerator << 21); //
wenn man hier zu viele Fehler machen kann, halt doch was mit Templates
  }

  setFrequency(circuitA.timer, 11, 4);


… aber YMMV.
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: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von dot »

DerAlbi hat geschrieben:Ich hab das schon als Inlinefunktion gemacht.. das löst das jetzige Problem nicht. In gewissen Situation haben Bitfields ihre Daseinsberechtigung, da bin ich stark dafür, unabhäängig davon, was andere da sagen. Ich vermute, dass andere Leute in iherer Hardwareumgebung nur selten mit in Hardware implementierten Bitfeldern zu tun haben. Ich weiß nicht, warum man das dann nicht auch tatsächlich in Software abbilden sollen können dürfen könnte.
Das finde ich das gleiche Niveau, wie wenn jemand sagt, dass man goto nicht verwenden darf.
Vergiss Bitfields. Gerade für sowas wie Memory-Mapped-IO und Kommunikation mit Hardware sind Bitfields völlig unbrauchbar, denn was man dafür unbedingt haben muss ist exakte Kotrolle darüber, welche Bits wie gemapped werden. Und genau die hat man mit Bitfields eben gerade nicht, da kannst du auch noch so stark "dafür sein":
ISO/IEC 14882:2014 §9.6/1 hat geschrieben:Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined. Bit-fields are packed into some addressable allocation unit.
Mit anderen Worten: C++ gibt dir keinerlei Garantien darüber welche Bits eines Bitfield wo genau im Layout eines Objektes zu finden sind. Zwei aufeinanderfolgende Bitfields müssen nichtmal in anliegenden Bytes stehen. Ein Compiler dürfte dir Bitfields völlig durcheinander quer über alle Bytes eines Objektes verteilen wenn ihm danach ist und wäre immer noch standardkonform. Mit anderen Worten: Die einzige Anwendung, die Bitfields eine potentielle Daseinsberechtigung verleihen könnte ist genau die eine Anwenung für die Bitfields absolut gar nicht verwendbar sind. Das wirklich einzige, das der C++ Standard für Bitfields verlagt, ist, dass jede C++ Implementierung dokumentieren muss, wie Bitfields umgesetzt werden. Damit ist sichergestellt, dass es zwar irgendein definiertes und dokumentiertes Verhalten gibt, wie dieses Verhalten aussieht, ist aber jedem Compiler selbst überlassen. Jeglicher Code, der von einem bestimmten Speicherlayout von Bitfields abhängt, ist also automatisch völlig unportabel.

Vergiss Bitfields. Über goto können wir gerne reden, aber Bitfields sind in C++ wirklich völlig frei von jeglichem Nutzen.

Deine Hardware Register haben eine ganz bestimmte Adresse und ganz bestimmte Größe. Allein diesen doch fundamentalen Umstand kannst du mit Bitfields rein prinzipiell niemals explizit modellieren. Besser einfach fixed-width Integer und ein paar inline Funktionen zum lesen/schreiben der entsprechenden Bits verwenden...
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von Krishty »

dot hat geschrieben:Mit anderen Worten: C++ gibt dir keinerlei Garantien darüber welche Bits eines Bitfield wo genau im Layout eines Objektes zu finden sind. Zwei aufeinanderfolgende Bitfields müssen nichtmal in anliegenden Bytes stehen. Ein Compiler dürfte dir Bitfields völlig durcheinander quer über alle Bytes eines Objektes verteilen wenn ihm danach ist und wäre immer noch standardkonform.
C++ garantiert, dass sich der Compiler konsistent verhält und dir in der Dokumentation Auskunft darüber erteilt. Ich würde sagen, dass viel zu viel Code überall auf der Welt von Bitfields abhängt, als dass GCC oder Clang oder gar Visual C++ ihr Verhalten irgendwann mal ändern werden. Theoretisch hast du da völlig recht, aber praktisch, naja, wird Albi für immer auf GCC bleiben.

Gewiss ist nur: Portabel ist es nicht, weil ein anderer Compiler schon jetzt anderes Layout ausspuckt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von DerAlbi »

Ganz langsam.
Schauen wir uns nochmal Krishtys verlinktes Beispiel an und dann noch das, was im Sinne der Hardwareimplementierung meiner Realität entspricht.
Der Katastrophenheinz ausm Link:

Code: Alles auswählen

struct bitfield
{
    unsigned a : 3;
    char b;
    unsigned c : 5;
    int d;
}bit;
Man beachte die Tatsache, dass es sich überhaupt nicht um ein reines Bitfield handelt. Es ist vielmehr eine Struktur mit wild aneinander gereihten Members, worin der Compiler sogar noch Padding-Bytes reinschmeißen sollte. Dass das im übermaß Implementation-Defined ist, ist mir und jedem anderen hier vollkommen klar. Dass eine solche Struktur kein MemoryMappedIO beschreibt, auch.
Schauen wir uns nochmal eine richtige Register-Deklaration an, wie ich sie für die Hardware schreibe:

Code: Alles auswählen

struct AlbisBitfield
{
	unsigned int A : 3;
	unsigned int   : 5; //reserved
	unsigned int B : 12;
	unsigned int   : 4;
	unsigned int C : 7;
	unsigned int D : 1;
};
Man beachtet die wohl überlegte Festlegung der Registergröße auf unsigned int. Man beachte ebenfalls, das die Summe der verwendeten bits exakt sizeof(unsigned int)*8 ist.
Und nun zeige mir jemand bitte irgend einen Compiler, der bei sizeof(AlbisBitfield) irgendwas anderes ausspuckt als sizeof(unsigned int) und dann reden wir nochmal, wie ultra massiv überungeeignet Bitfields sind und wie katastrophal wenig Kontrolle ich über die habe. Ich hab schon bisschen mit den Online-Compiler-Webseiten rumgespielt. Möchte jetzt aber nicht spoilern ^_^.
FunFact: die größte Inkompatibilität die ich über architekturübergreifenden Code mit Bitfields bisher hatte, war, dass sich die Endianess geändert hat. Worst Case war bisher, dass man die Bitfield-Members einfach in der reihenfolge umkehren musste. Das ist aber zu erwarten.

Wenn es danach geht, dass etwas nicht geht, wenn man etwas falsch macht, dürfte man gar kein C++ programmieren :P
Benutzeravatar
Top-OR
Establishment
Beiträge: 330
Registriert: 02.03.2011, 16:32
Echter Name: Jens H.
Wohnort: Esslingen/Dessau
Kontaktdaten:

Why programmers hate posting on online forums

Beitrag von Top-OR »

DerAlbi hat geschrieben:Wenn es danach geht, dass etwas nicht geht, wenn man etwas falsch macht, dürfte man gar kein C++ programmieren :P
Es drängelt sich mir der Gedanke hieran auf :-D ... natürlich off topic:

ALLEN: Hi, I’m new to driving and I need to move my car back around 5 meters. How can I move the car backwards?

(2 days later.)
ALLEN: Hello? This is still a problem. I’m sure someone knows how to do this.

BOB: I can’t believe you didn’t figure this out yourself. Just take your foot off the gas and let the car roll backwards down the hill. Tap the bake when you get to where you want to be. Boom. Done.

ALLEN: But I’m not on a hill. I’m in my driveway and it’s completely flat.

CARL: Dude, I don’t know what you’re trying to accomplish, but you should never be driving backwards. It’s dangerous and will confuse the other drivers. See the big window in FRONT of you? That’s your first clue. Don’t drive backwards.

ALLEN: I’m not trying to drive backwards. I just need to move back a little bit so I can get out of my driveway and start driving forwards.

CARL: So just drive in circle until you’re pointed the right way.

ALLEN: I don’t have enough room to turn around like that. I only need to move back a few meters. I don’t understand why this has to be so hard.

CARL: Sounds like your “driveway” isn’t compatible with cars. It’s probably made for bikes. Call a contractor and have them convert some of your yard into driveway to be standards-compliant with the turning radius of a car. Either way, you’re doing something wrong.

DAVE: I see your problem. You can adjust your car to move backwards by using the shifter. It’s a stick located right between the passenger and driver seats. Apply the clutch and move the stick to the “R” position.

ALLEN: But.. I don’t have a clutch. And there isn’t a stick between the seats.

CARL: Sounds like you’re trying to drive in Europe or something.

ALLEN: Ah. Nevermind. I figured it out.
--
Verallgemeinerungen sind IMMER falsch.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von dot »

DerAlbi hat geschrieben:Man beachtet die wohl überlegte Festlegung der Registergröße auf unsigned int. Man beachte ebenfalls, das die Summe der verwendeten bits exakt sizeof(unsigned int)*8 ist.
Und nun zeige mir jemand bitte irgend einen Compiler, der bei sizeof(AlbisBitfield) irgendwas anderes ausspuckt als sizeof(unsigned int) und dann reden wir nochmal, wie ultra massiv überungeeignet Bitfields sind und wie katastrophal wenig Kontrolle ich über die habe.
Nur weil etwas, wenn man ganz genau aufpasst, für einen bestimmten Spezialfall in der Praxis am Ende trozdem manchmal nicht schiefgeht, heißt noch lange nicht, dass es sich um eine brauchbare Lösung handelt. Bitfields sind rein konzeptionell ungeeignet für das was du machen willst. Da gibt es nicht viel zu diskutieren; das ist einfach so, denn die Semantik von Bitfields erlaubt es einfach nicht, die Dinge auszudrücken, die du ausdrücken willst. Und es gibt ja andere, mindestens genauso einfache Wege, exakt auszudrücken, was du ausdrücken willst. Es steht dir aber natürlich frei, all das zu ignorieren...
DerAlbi hat geschrieben:FunFact: die größte Inkompatibilität die ich über architekturübergreifenden Code mit Bitfields bisher hatte, war, dass sich die Endianess geändert hat. Worst Case war bisher, dass man die Bitfield-Members einfach in der reihenfolge umkehren musste. Das ist aber zu erwarten.
Man beachte, dass du uns hier gerade davon erzählst, wie dir selbst schon mit diesem einfachen Beispiel bereits reale Probleme aus der Tatsache erwachsen sind, dass Bitfields kein portables Layout haben. Lang hat's ja nicht gedauert. Soviel zum Thema "ist in der Praxis irrelevant"... ;)
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: -Wconversion is a Bitch! Battlefield 4.. ääh Bitfield:4

Beitrag von DerAlbi »

Man beachte, dass du uns hier gerade davon erzählst, wie dir selbst schon mit diesem einfachen Beispiel bereits reale Probleme aus der Tatsache erwachsen sind, dass Bitfields kein portables Layout haben. Lang hat's ja nicht gedauert. Soviel zum Thema "ist in der Praxis irrelevant"... ;)
Junge.. mit solchen Argumenten haust du dich doch selbst ins Aus :-/ Wenn die Endianess des zwischen Quell- und Zielsystem nicht stimmt hast du schon mit der Kompatibilität von normalen Integers ein Problem..Willst du die dann auch totreden? Das ist kein Anzeichen dafür, dass die von dir herbeigedichtete Bitfield-Katastrophe auch nur im Ansatz real ist.
Und es gibt ja andere, mindestens genauso einfache Wege, exakt auszudrücken, was du ausdrücken willst. Es steht dir aber natürlich frei, all das zu ignorieren...
Was konkret ist denn "genauso einfach"?
Kirshtys extra-Funktion für jedes Bitfield-Feld (das ist die enizig Sinnvolle Alternative für mich, aber unpaktikabel*)?
*hast du mal in einen Mikrocontroller geschaut wieviel tausende Bitfields es gibt? Für alles ein Read und Write? Und man macht bei keiner der Funktionen einen Fehler? Irgendwo müssen die hardgecodeten BitFeldOffset und Bitfeldlänge ja Konstanten hin.
Händische Bitschieberei? Incl Copy&Paste-Fehler und Lies-Die-Doku-Wenn-Du-Wissen-Willst-Was-Hier-Passiert und allem drun und dran?

Hast du die ersten paar minuten deines selbst geposteten Youtube-Links eigentlich selbst gehört? Sagt der da nicht, dass Bitfields für das Beschreiben von Hardwareregistern da sind...
Es steht dir aber natürlich frei, all das zu ignorieren... :roll:
Ich weiß im Moment ehrlich nicht, was du willst. Beschreibe bitte ein *reales* Problem, dass mit Bitfieldspezifisch, trotz korrekter Benutzung, auftritt. :o
Antworten