Seite 1 von 1
C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 12:44
von Spiele Programmierer
Ich möchte einige Binärdaten von einen etwas älteren Computer- und Konsolenspiel in C++ einlesen. Wegen der einfachen Umsetzung habe ich mich dazu entschieden, die Daten am besten direkt in POD-Strukturen einzulesen. Trotz "#pragma pack(1)" weigert sich aber der MSVC bei mir, die Bitfelder wirklich richtig zu packen. Um Sicherzustellen das es keine Probleme mit Padding gibt, habe ich "static_assert" verwendet.
Zum Beispiel folgender Code funktioniert bei mir aber nicht:
Code: Alles auswählen
struct TextureColor16
{
std::uint8_t R : 5;
std::uint8_t G : 5;
std::uint8_t B : 5;
std::uint8_t A : 1;
};
static_assert(sizeof(TextureColor16) == 2, "Wrong size");
Beim "static_assert" meldet die IDE und der Compiler einen Fehler. Scheinbar ist die Struktur tatsächlich 3(?!?) Bytes groß. Ich hätte erwartet, dass das Bitfeld entweder 2 oder 4 Bytes groß ist, wenn Packing eingeschaltet ist.
Warum ausgerechnet 3 Bytes und viel wichtiger: Wie kann ich den MSVC10 Compiler dazu bewegen, das Bitfeld zu 2 Bytes zusammenzupacken?
Bei Bitfeldern die nur 1 Byte groß sind, scheint es das Problem nicht zu geben.
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 13:00
von FlorianB82
Ich weiss nicht, ob du das jetzt hören magst, aber wenn nicht, ignoriere es einfach: Rumfummeln mit Packing und Bitfeldern ist fehlerträchtig, compilerabhängig, und früher oder später wird es nervig - im Allgemeinen also ganz Bäääh.
Gerade bei dem genannten Beispiel mit der RGBA-Farbe würde ich einfach den 32-Bit Werte durch die Gegend reichen - im Zweifelsfalle ist das sowieso flotter als wenn du das mit einzelnen Bytes machst. An den Stellen, wo du Zugriff auf die einzelnen Farbkanäle brauchst, würde ich mir einfach ein paar inline-Funktionen schreiben, die das Bitmasking und Shifting erledigen. Das ist portabel, einfacher, und bringt dir auch keine Nachteile - denn der Compiler wird letzten Endes den selben Code generieren wie für die Variante mit den Bitfeldern.
Ist das für dich eine Option?
Nachtrag: Ich würde mir vom Dateiformat, das gerade bei alter Software häufig hauptsächlich platz-optimiert ist, nicht meine interne Repräsentation in der Anwendung vorschreiben lassen wollen - denn da ist Speicherplatz oftmals eher zweitrangig, und du willst stattdessen Geschwindigkeit oder einfache Handhabung.
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 13:09
von dot
Gehen wir das mal aus Sicht des Compilers durch:
- R: 5 Bit Bitfeld vom Typ uint8_t => R kommt in die ersten 5 Bits
- G: 5 Bit Bitfeld vom Typ uint8_t => die übrigen 3 Bit von R bieten keinen Platz für G => G kommt in die ersten 5 Bits von einem neuen uint8_t
- B: 5 Bit Bitfeld vom Typ uint8_t => die übrigen 3 Bit von G bieten keinen Platz für B => B kommt in die ersten 5 Bits von einem neuen uint8_t
- A: 1 Bit Bitfeld vom Typ uint8_t => die übrigen 3 Bit von B bieten Platz für A => A kommt in Bit Nummer 6 des dritten uint8_t
Die Lösung wäre, std::uin16_t zu verwenden.
Gut gemeinter Rat: Der Code ist so nicht portabel, denn das genaue Layout von Bitfeldern ist zu einem großen Teil davon abhängig, wie der Compiler gerade aufgelegt ist. Auch wenn es in der Regel funktionieren wird, gibt es keinerlei Garantie. Vergiss Bitfelder, lies das Zeug in einen std::uint16_t und schreib dir eine Funktion, die die Werte per Bitgefummel selbst ver- bzw. entpackt oder gleich eine Klasse für R5G5B5A1 Farben oder sowas...
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 13:22
von Spiele Programmierer
schreib dir eine Funktion, die die Werte per Bitgefummel selbst ver- bzw. entpackt
In C++ gibt es halt keine Properties das heißt, ich muss auf die Daten immer als Funktion zugreifen. Des Weiteren brauche ich einen Getter und einen Setter. Gibt es wirklich keine andere Möglichkeit?
wird es nervig
Nicht nerviger als komplizierte Loader schreiben oder Getter und Setter entwerfen und vorallendingend auch zu benutzen.
EDIT:
Wofür gibt es dann in C(++) überhaupt Bitfelder?
Wenn die Bitfelder nicht richtig gepackt werden sind sie sowohl von der Platzersparnis als auch als Dateninterface wie bei mir vollkommen ungeeignet.
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 13:28
von dot
Spiele Programmierer hat geschrieben:In C++ gibt es halt keine Properties
Ja, gottseidank is C++ von Properties verschont geblieben... ;)
Spiele Programmierer hat geschrieben:Gibt es wirklich keine andere Möglichkeit?
Die Frage ist, ob du die Daten programmintern auch wirklich so speichern willst und wenn ja: warum? Warum intern nicht einfach mit 8 Bit RGBA arbeiten und nur beim Speichern/Laden konvertieren? Die Platzersparnis bezahlst du natürlich mit Performance...
Spiele Programmierer hat geschrieben:Wofür gibt es dann in C(++) überhaupt Bitfelder?
Weil es sie in C gab und man C++ mit C kompatibel halten wollte...
Spiele Programmierer hat geschrieben:Wenn die Bitfelder nicht richtig gepackt werden sind sie sowohl von der Platzersparnis als auch als Dateninterface wie bei mir vollkommen ungeeignet.
Bitfelder sind für praktisch alles völlig ungeeignet... ;)
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 13:37
von Spiele Programmierer
Ja, gottseidank is C++ von Properties verschont geblieben... ;)
Hm? Was hast du den gegen Properties?
In C# wurden sie zwar im Framework meiner Meinung nach zu inflationär eingesetzt, können aber in einigen Fällen auch echt nützlich sein. (Siehe hier)
Die Frage ist, ob du die Daten programmintern auch wirklich so speichern willst und wenn ja: warum?
Weil das Einlesen dann einfach und schnell ist.
Außerdem habe ich damit eine Garantie, dass Daten die nicht verändert werden, auch nach der Verarbeitung exakt genauso wieder zurück in die Datei geschrieben werden. Wenn ich Anfange beim Laden und Speichern die Daten verarbeite, dann besteht die Gefahr, dass das eine oder andere Bit "unter die Räder" kommt. Sollte zwar nicht sein, aber ich sehe die Gefahr, dass es passieren könnte.
Außerdem gibt es auch einige Daten im Format deren Zweck unbekannt ist. Diese möchte ich auch genau wieder richtig schreiben und deshalb kann ich nicht die gesamten Daten auseinander nehmen und dann "intern sauber speichern".
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 13:46
von dot
Spiele Programmierer hat geschrieben:Die Frage ist, ob du die Daten programmintern auch wirklich so speichern willst und wenn ja: warum?
Weil das Einlesen dann einfach und schnell ist.
Außerdem habe ich damit eine Garantie, dass Daten die nicht verändert werden, auch nach der Verarbeitung exakt genauso wieder zurück in die Datei geschrieben werden. Wenn ich Anfange beim Laden und Speichern die Daten zu verarbeiten, dann besteht die Gefahr das das eine oder andere Bit "unter die Räder" kommt. Sollte zwar nicht sein, aber ich sehe die Gefahr das es passieren könnte.
Und wie genau sollte das passieren? Um was für eine Art Verarbeitung geht es da? Ist die Gefahr, dass, aufgrund der mangelnden arithmetischen Genauigkeit, Artefakte durch Rundungsfehler entstehen, nicht viel realer?
Spiele Programmierer hat geschrieben:Außerdem gibt es auch einige Daten im Format deren Zweck unbekannt ist. Diese möchte ich auch genau wieder schreiben können und ich kann nicht den gesamten Daten auseinander nehmen und dann "intern sauber speichern".
Dann mach das einfach, wo liegt das Problem?
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 14:02
von Spiele Programmierer
Und wie genau sollte das passieren?
Wenn ich eine wirklich saubere interne Datenrepräsentation will, wie auch im restlichen Programm, so müsste ich einen Gleitkommatypen wählen. Gut ok, hier kein Problem weil nur 0 bis 31
sollte sich in einen Floatingpoint exakt abspeichern lassen. Es gibt aber natürlich auch größere Integer bei denen das nicht mehr ginge und außerdem sind exakte Gleitkommawerte immer problematisch.
Um was für eine Art Verarbeitung geht es da?
Nun, es gibt einen Leveleditor, der leider aber nicht alle Features der Spielengine unterstützt. Im Editor lassen sich Dinge zum Beispiel nur mit rechteckigen Texturausschnitten texturieren, was heutzutage nicht mehr schön ist. Im Levelformat lassen sich aber beliebige Texturkoordinaten angeben, deshalb möchte ich die Daten aus dem Leveleditor in meinen Programm weiterverarbeiten.
Ist die Gefahr, dass, aufgrund der mangelnden arithmetischen Genauigkeit, Artefakte durch Rundungsfehler entstehen, nicht viel realer?
Nein, weil keine Berechnungen in der Binärrepresentation der Spieldaten vorgenommen werden.
Dann mach das einfach, wo liegt das Problem?
Dafür ist das Beispiel oben nicht so optimal.
Es gibt aber auch beispielsweise eine Struktur die so aussieht:
Code: Alles auswählen
struct RoomFlags
{
bool Water : 1;
std::uint8_t Unknown000 : 2;
bool SkyVisible : 1;
bool Unknown001 : 1;
bool BlowHair : 1;
bool Unknown002 : 1;
bool Quicksand : 1; //NL-Flag, Unknown effect
bool Mist : 1; //Unknown effect
bool WaterReflectivity : 1; //R-Flag
bool Unknown003 : 1;
bool FlagD : 1; //No effect
bool FlagP : 1; //No effect
std::uint8_t Unknown003 : 3;
} ;
Es wäre zwar theoretisch möglich, das auseinadern zu nehmen, aber ich sehe nur Nachteile wie den viel höheren Speicherverbrauch und komplexe Bitschiebereien.
Mir gefällt hat einfach die Einfachheit ganze Strukturen mit einen einfachen "iotream::read" einlesen zu können.
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 14:18
von Schrompf
Wie die anderen Poster schon geschrieben haben: es gibt eben keine einfache Möglichkeit, diese Struktur einzulesen, weil das genaue Layout der bitweisen Strukturelemente dem Compiler überlassen ist. dot hat Dir im dritten Beitrag ja beschrieben, was Du ausprobieren könntest, aber eine Garantie für das Funktionieren wirst Du nie bekommen.
Re: C++, MSVC10, Bitfelder packen
Verfasst: 17.11.2013, 14:23
von dot
Doch, die Variante mit std::uint16_t und Bitoperationen funktioniert, im Gegensatz zu Bitfields, garantiert und portabel, wenn man es richtig macht...
Re: C++, MSVC10, Bitfelder packen
Verfasst: 19.11.2013, 18:03
von BeRsErKeR
dot hat geschrieben:Gehen wir das mal aus Sicht des Compilers durch:
- R: 5 Bit Bitfeld vom Typ uint8_t => R kommt in die ersten 5 Bits
- G: 5 Bit Bitfeld vom Typ uint8_t => die übrigen 3 Bit von R bieten keinen Platz für G => G kommt in die ersten 5 Bits von einem neuen uint8_t
- B: 5 Bit Bitfeld vom Typ uint8_t => die übrigen 3 Bit von G bieten keinen Platz für B => B kommt in die ersten 5 Bits von einem neuen uint8_t
- A: 1 Bit Bitfeld vom Typ uint8_t => die übrigen 3 Bit von B bieten Platz für A => A kommt in Bit Nummer 6 des dritten uint8_t
Die Lösung wäre, std::uin16_t zu verwenden.
Gut gemeinter Rat: Der Code ist so nicht portabel, denn das genaue Layout von Bitfeldern ist zu einem großen Teil davon abhängig, wie der Compiler gerade aufgelegt ist. Auch wenn es in der Regel funktionieren wird, gibt es keinerlei Garantie. Vergiss Bitfelder, lies das Zeug in einen std::uint16_t und schreib dir eine Funktion, die die Werte per Bitgefummel selbst ver- bzw. entpackt oder gleich eine Klasse für R5G5B5A1 Farben oder sowas...
Also mal abgesehen davon, dass du Recht hast mit deinem Rat, ist unter Windows / MSVC eigentlich die Grenze 16 Bit (geprüft: von VS2008 bis VS2013 ist das immer noch so:
http://msdn.microsoft.com/de-de/library ... 20%29.aspx). Das heißt die 16 Bits sollten auch vom Compiler in 16 Bits verpackt werden. Daher ist das Verhalten durchaus verwunderlich. Jedenfalls beim genannten Beispiel.
Was mich wundert ist, dass Spiele Programmierer von
#pragma pack(1) geredet hat, denn das legt doch nur das Alignment auf Byte-Ebene fest (1 bedeutet hier nicht 1Bit-Alignment, sondern 1Byte-Alignment) und hat somit keine Auswirkungen auf Bitfelder. Es ist aber in dem Fall wahrscheinlich dennoch sinnvoll, falls man beim Ansatz mit Bitfeldern bleiben möchte.
Hier ist die Frage allerdings ob das
#pragma pack(1) eventuell auch die Grenze für das Verpacken von Bits herabsetzt und somit die Aussage von dot wieder zutrifft. Das weiß ich gerade aber auch nicht.
Re: C++, MSVC10, Bitfelder packen
Verfasst: 19.11.2013, 18:12
von dot
BeRsErKeR hat geschrieben:Also mal abgesehen davon, dass du Recht hast mit deinem Rat, ist unter Windows / MSVC eigentlich die Grenze 16 Bit (geprüft: von VS2008 bis VS2013 ist das immer noch so:
http://msdn.microsoft.com/de-de/library ... 20%29.aspx). Das heißt die 16 Bits sollten auch vom Compiler in 16 Bits verpackt werden. Daher ist das Verhalten durchaus verwunderlich. Jedenfalls beim genannten Beispiel.
Was genau meinst du damit? Wenn sein Bitfeld vom Typ std::uint8_t ist, dann ist die Grenze 8 Bit. Hätte er stattdessen std::uint16_t verwendet, würde es, wie gesagt, funktionieren...
Re: C++, MSVC10, Bitfelder packen
Verfasst: 19.11.2013, 18:14
von BeRsErKeR
dot hat geschrieben:BeRsErKeR hat geschrieben:Also mal abgesehen davon, dass du Recht hast mit deinem Rat, ist unter Windows / MSVC eigentlich die Grenze 16 Bit (geprüft: von VS2008 bis VS2013 ist das immer noch so:
http://msdn.microsoft.com/de-de/library ... 20%29.aspx). Das heißt die 16 Bits sollten auch vom Compiler in 16 Bits verpackt werden. Daher ist das Verhalten durchaus verwunderlich. Jedenfalls beim genannten Beispiel.
Was genau meinst du damit? Wenn sein Bitfeld vom Typ std::uint8_t ist, dann ist die Grenze 8 Bit. Hätte er stattdessen std::uint16_t verwendet, würde es, wie gesagt, funktionieren...
Achso ok da war ich wohl blind. Bin davon ausgegangen, dass er 16Bit-Werte verwendet hat. Naja dann ist es natürlich kein Wunder. ;) Ich hatte vermutet, dass der Compiler trotz größerem Datentyp da auf 8 Bit runterregelt. Daher auch die Verwirrung meinerseits.
Re: C++, MSVC10, Bitfelder packen
Verfasst: 19.11.2013, 18:20
von dot
BeRsErKeR hat geschrieben:Ich hatte vermutet, dass der Compiler trotz größerem Datentyp da auf 8 Bit runterregelt. Daher auch die Verwirrung meinerseits.
Das wäre natürlich in der Tat verwunderlich... ;)
Re: C++, MSVC10, Bitfelder packen
Verfasst: 20.11.2013, 12:21
von odenter
Hat jetzt nicht direkt was mit dem Thema zu tun, aber in C# sind Properties auch nur setter und getter ohne die "()" beim Aufruf. :) Der Compiler mach da Funktionen ala "set_Propertyname()" und "get_Propertyname()" von.