Hallo,
wie wandle ich am besten ein uin32 Array in ein uint8 Array am
effizientesten (Kein Pfusch ;-) ).
Klar kann ich so was verwenden, aber vielleicht hat jemand noch andere
gute Ideen.
Was möchtest Du denn machen? Oder was willst Du optimieren?
Viele Konstrukte setzt Dein Compiler bei höchste Optimierung in wenige
oder 1 Assemblerzeile um. Genauso gut kann er aber auch daran
verzweifeln, weil ihm irgendwas nicht passt.
Und ja, manche nehmen memcpy, unions oder was auch immer und sichern
dass mit ein paar asserts oder Randbedingungen ab. Sauber ist das nicht
immer, aber oft schneller oder pragmatischer, wenn ich z.B. nur einen
pointercast brauche.
Wenn du es portabel halten möchtest, dann ist die jetzige Lösung wohl
das geeigete. Bei allem anderem musst du den Prozessor kennen.
Von TI gibt es z.B. noch eine DSP Serie, die ist nur 16 bittig, d.h.
auch ein uint8_t und ein Byte besteht aus 16 Bit. Und wenn du so eine
CPU benutzt, dann sind sämtliche Unions etc nicht mehr möglich. Das
portable Konstrukt funktioniert aber weiterhin.
Weil es nicht zu erkennen ist, ob C oder C++:
- in C ist type-punning per union explizit ok.
- in C ist type-punning per pointer-cast auch UB wegen strict-aliasing
- in C++ ist type-punning per union immer UB.
Und: type-punning per union ist auch gar nicht notwendig, denn Deine
Lösung ist die richtige und nicht aufwändiger als per union.
Wilhelm M. schrieb:> - in C ist type-punning per union explizit ok.
Wäre mir neu. Ist das in einem neuen C-Standard aufgenommen worden?
> - in C ist type-punning per pointer-cast auch UB wegen strict-aliasing
Für uint8_t*/char* (also für genau diesen Fall hier) ist es nicht UB.
MaWin schrieb:> Wäre mir neu. Ist das in einem neuen C-Standard aufgenommen worden?
Ja. https://stackoverflow.com/a/25672839
@OP, deine Lösung ist korrekt und die portabelste, solltest du es doch
mal mit nem C++ compiler bauen wollen. Verpacks in eine inline Funktion
oder ein makro und gut ist.
Ergo70 schrieb:> Undefined behavior vermutlich
korrekt, UB = undefined behavior. D.h. kann gut gehen, tut es aber nicht
unter allen Bedingungen. Nicht schön. Auf jeden Fall vermeiden
jojo schrieb:> @OP, deine Lösung ist korrekt und die portabelste, solltest du es doch> mal mit nem C++ compiler bauen wollen. Verpacks in eine inline Funktion> oder ein makro und gut ist.
Ich bin etwas erstaunt/erschrocken, dass (std::)memcpy noch nicht
genannt wurde. DAS ist die portable Methode in C++ und auch C, und wird
ohnehin weboptimiert.
Seppel schrieb:> Harry L. schrieb:>> https://www.learn-c.org/de/Unions>> Das ist undefigned, wegen dem Alignement, oder? Bei Embedded habe ich> ewig keine Unions gesehen, ...
Nein, weil das größte alignment der member für den ganzen union zählt.
Somit gibt es kein Alignmentproblem.
Huch schrieb:> Ich bin etwas erstaunt/erschrocken, dass (std::)memcpy noch nicht> genannt wurde. DAS ist die portable Methode in C++ und auch C, und wird> ohnehin weboptimiert.
Es ist zwar legal, aber das Ergebnis ist trotzdem plattformabhängig und
damit nicht besonders portabel. Was am Ende in "arr" steht hängt von der
Byte-Reihenfolge ab. Die Original-Lösung mit Bitshifts hingegen ist
portabel, denn da ist das Ergebnis immer gleich.
Ich hatte das mal hier alles zusammengefasst: Serialisierung
Niklas G. schrieb:> Es ist zwar legal, aber das Ergebnis ist trotzdem plattformabhängig und> damit nicht besonders portabel. Was am Ende in "arr" steht hängt von der> Byte-Reihenfolge ab. Die Original-Lösung mit Bitshifts hingegen ist> portabel, denn da ist das Ergebnis immer gleich.>> Ich hatte das mal hier alles zusammengefasst: Serialisierung
Ganz meine Meinung!
Die portable Lösung nehmen, den Rest optimiert eh der Compiler. Da merkt
man keinen Geschwindigkeitsunterschied. Ausser, das Programm macht
einzig diese Konvertierungen und nichts anderes.
PittyJ schrieb:> den Rest optimiert eh der Compiler.
Für amd64 macht der GCC aus dem Codeschnipsel im Eröffnungspost genau
drei ASM-Anweisungen:
1
mov eax, DWORD PTR Tx_PP[rip]
2
bswap eax
3
mov DWORD PTR arr[rip], eax
für ARM in etwa dasselbe.
interessanter ist, was für 8-Bit-Architekturen dabei rauskommt, oder auf
BigEndian-Maschinen, oder für eine Schleife über ein größeres Array, die
sich evtl. per AVX&co weiter optimieren ließe...
Huch schrieb:> Ich bin etwas erstaunt/erschrocken, dass (std::)memcpy noch nicht> genannt wurde.
Wurde 20 Stunden vorher genannt:
A. S. schrieb:> Und ja, manche nehmen memcpy, unions oder was auch immerHuch schrieb:> DAS (memcpy) ist die portable Methode in C++ und auch C,> und wird ohnehin weboptimiert.
Ich denke nicht, denn memcpy ordnet die Reihenfolge der Bytes ebenso
wenig um, wie unions.
MaWin schrieb:> Wilhelm M. schrieb:>> - in C ist type-punning per union explizit ok.>> Wäre mir neu. Ist das in einem neuen C-Standard aufgenommen worden?
Naja, so arg neu ist das nicht. Es wurde mit C99 aufgenommen, also vor
23 Jahren.
Ich finde unions dafür aber trotzdem wenig geeignet. Sie sind eigentlich
dafür nicht gedacht, und ich finde es auch umständlich, extra nur für
die Umwandlung einen neuen Datentyp definieren zu müssen.
Ja nachdem, ob es einfach oder portabel sein soll, würde ich entweder
die memcpy-Variante oder die mit den Shifts verwenden.
Die memcpy Variante funktioniert nur mit den drei großen Compilern, und
auch nur wenn die Kommandozeilenparameter passen.
UB sind alle Varianten wegen Bitreihenfolge. Das &0xff ist überflüssig,
wenn man gerne viel schreibt, kann man explizit casten.
udok schrieb:> Die memcpy Variante funktioniert nur mit den drei großen Compilern, und> auch nur wenn die Kommandozeilenparameter passen.
Nein, der Standard schreibt vor dass das funktionieren muss. Daher
können alle Compiler das. Lediglich das Ergebnis ist plattform-abhängig
(IB).
udok schrieb:> UB sind alle Varianten wegen Bitreihenfolge.
Wenn schon Bytereihenfolge, und es ist IB, nicht UB.
Niklas G. schrieb:> Nein, der Standard schreibt vor dass das funktionieren muss. Daher> können alle Compiler das. L
Funktionieren tut es ja auch, langsam halt.
udok schrieb:> Funktionieren tut es ja auch, langsam halt.
Nicht unbedingt, wenn die Variablen lokal sind können Compiler das gut
wegoptimieren. Ganz sicher dass das nur die drei großen können?
udok schrieb:> Klugscheißer?
Das ist sin signifikanter Unterschied.
Rolf M. schrieb:> Ja nachdem, ob es einfach oder portabel sein soll, würde ich entweder> die memcpy-Variante oder die mit den Shifts verwenden.
Ich bin kein SW-Guru, haut mich also nicht 😀.
Ich hätte das mit einem uint8_t-Pointer auf das uint32_t Array versucht.
Das müsste doch genauso funktionieren wie ein union-Konstrukt?
Natürlich mit den selben Einschränkungen bez. der Byteorder und ggf. der
Portierbarkeit.
Bezüglich Portierbarkeit: oft hat man genau eine Umgebung, einen
Compiler und eine Plattform. Dann braucht die Portierbarkeit nicht
zwingend eine wichtige Randbedingung sein, wenn es in der Umgebung den
Zweck erfüllt. Ein Kommentar diesbezüglich im Quelltext wäre sicher
nicht falsch!
HildeK schrieb:> Ich hätte das mit einem uint8_t-Pointer auf das uint32_t Array versucht.> Das müsste doch genauso funktionieren wie ein union-Konstrukt?
In C ist es identisch, in C++ ist es sogar besser als die union, denn
wie schon geschrieben ist die Verwendung von union's auf diese Art in
C++ fehlerhaft. Im Endeffekt ist es aber exakt das Gleiche wie memcpy(),
denn das benutzt "nominell" einen char-Pointer.
HildeK schrieb:> Bezüglich Portierbarkeit: oft hat man genau eine Umgebung, einen> Compiler und eine Plattform. Dann braucht die Portierbarkeit nicht> zwingend eine wichtige Randbedingung sein
Das haben sich schon viele gedacht. Dann kam AMD64 um die Ecke. Und dann
ARM. Und dann ARM64... Gerade wenn man die korrekte Variante mit den
Bitshifts schon da hat muss man ja nicht auf was weniger portables
wechseln?
Niklas G. schrieb:> Im Endeffekt ist es aber exakt das Gleiche wie memcpy(),> denn das benutzt "nominell" einen char-Pointer.
Ich hatte gedacht, dass memcpy() eine echte Kopie erstellt, also zweimal
den Speicherplatz benötigt.
HildeK schrieb:> Ich hatte gedacht, dass memcpy() eine echte Kopie erstellt, also zweimal> den Speicherplatz benötigt.
Wie gesagt, nicht unbedingt wenn es um lokale Variablen geht. Dann wird
das gern mal wegoptimiert.
Εrnst B. schrieb:> Für amd64 macht der GCC aus dem Codeschnipsel im Eröffnungspost genau> drei ASM-Anweisungen:
Und das auch nur, weil der Codeschnipsel vom Anfang nämlich implizit
auch noch eine Konvertierung nach Big-Endian macht. (im uint8_t array
sind die Daten in Big-Endian abgelegt, und zwar immer, unabhängig von
der Byteorder der CPU). Sonst wären es sogar nur zwei Anweisungen.
Deswegen sind alle Vorschläge mit memcpy hier sogar komplett falsch...
Andreas M. schrieb:> Deswegen sind alle Vorschläge mit memcpy hier sogar komplett falsch...
Naja, Seppel hat nicht gesagt auf welcher Plattform er arbeitet. Wenn er
auf einer Big Endian Plattform arbeitet, wäre memcpy identisch. Aber
eben nicht portabel auf Little Endian Plattformen...
Ich würde auf jeden fall die Bitshifts nehmen. Das kann man auch in eine
Funktion packen. Wird alles wegoptimiert.
https://godbolt.org/z/MGjYEKzWc
Auf godbolt.org kann man auch verschiedene Compiler ausprobieren. Jeder
dort kriegt das wegoptimieren hin. Naja, fast, msvc zum Beispiel schafft
es nicht einmal, den Code zu kompilieren. MS hat es also immernoch nicht
geschafft, einen funktionierenden C Compiler zu machen...
Das Ursprüngliche Beispiel des TO war übrigens auch Big Endian, und all
die anderen hier haben nun host byte order, auf den meisten Rechnern
little endian, Beispiele gebracht.
Wilhelm M. schrieb:> HildeK schrieb:>> Ein Kommentar diesbezüglich im Quelltext wäre sicher>> nicht falsch!>> Echt jetzt?
Klar doch, dann kann man nachdem man den bug gefunden hat ganz klar
sehen, welcher Autor das bewusst unportabel programmiert hat und ihm in
den Hintern treten oder ihm kündigen.
Michael
HildeK schrieb:> Ein Kommentar diesbezüglich im Quelltext wäre sicher> nicht falsch!A. S. schrieb:> manche [...] sichern dass mit ein paar asserts oder Randbedingungen ab.> Sauber ist das nicht immer, aber oft schneller oder pragmatischer,> wenn ich z.B. nur einen pointercast brauche.
Es gibt verschiedene Möglichkeiten, Alignment oder Endianess zur
Compilezeit oder spätestens zur Laufzeit zu prüfen. Das ist m.E. einem
Kommentar vorzuziehen.
Michael D. schrieb:> Wilhelm M. schrieb:>> HildeK schrieb:>>> Ein Kommentar diesbezüglich im Quelltext wäre sicher>>> nicht falsch!>>>> Echt jetzt?>> Klar doch, dann kann man nachdem man den bug gefunden hat ganz klar> sehen, welcher Autor das bewusst unportabel programmiert hat und ihm in> den Hintern treten oder ihm kündigen.
Ich hatte extra darum gebeten, mich nicht zu hauen 😀.
Wenn ich mal Software schreibe, ist die für mich selber. Ich bin kein
Softwareentwickler und verwende die µCs im Wesentlichen deshalb, weil
ich damit kleiner Projekte mit minimalem HW-Aufwand flexibel durchführen
kann. Mich kann auch niemand mehr kündigen. Und meine Plattform und
Umgebung ist über Jahre gleich geblieben. Da reicht für mich selber
tatsächlich ein diesbezüglicher Kommentar für den unwahrscheinlichen
Fall, dass ich das Schnipsel möglicherweise mal wo ganz anders
wiederverwenden will.
Nur unter den Bedingungen wollte ich das verstanden wissen ...
Niklas G. schrieb:> Wie gesagt, nicht unbedingt wenn es um lokale Variablen geht. Dann wird> das gern mal wegoptimiert.
Danke für die Info. Ich hatte das so nicht auf dem Schirm. Sobald aber
mal ein Array statisch oder global angelegt wird, dürfte das nicht mehr
der Fall sein.
HildeK schrieb:> Ich hatte extra darum gebeten, mich nicht zu hauen 😀.> Wenn ich mal Software schreibe, ist die für mich selber.
Macht es dann Sinn suboptimale Vorschläge zu machen, wenn bereits im
Ausgangsposting die korrekte Lösung steht, die auch schon von Experten
bestätigt wurde?
Es ging darum, welche Möglichkeiten es noch geben könnte. Dass solche
mit den ggf. damit verbundenen Nachteilen diskutiert werden, ist doch
nicht falsch?
HildeK schrieb:> Rolf M. schrieb:>> Ja nachdem, ob es einfach oder portabel sein soll, würde ich entweder>> die memcpy-Variante oder die mit den Shifts verwenden.>> Ich bin kein SW-Guru, haut mich also nicht 😀.> Ich hätte das mit einem uint8_t-Pointer auf das uint32_t Array versucht.
Und dann? Nehme ich diesen Pointer, um per for-Schleife den Inhalt ins
Zielarray zu kopieren? Welchen Vorteil sollte das gegenüber memcpy
haben?
> Das müsste doch genauso funktionieren wie ein union-Konstrukt?
Bei der union kopiere ich es sogar zweimal: Einmal von der Quelle in die
union und einmal von der union ins Zielarray.
Wobei ein vernünftiger Compiler am Ende aus allen Varianten den selben
oder zumindest gleichwertigen Code generiert.
> Bezüglich Portierbarkeit: oft hat man genau eine Umgebung, einen> Compiler und eine Plattform. Dann braucht die Portierbarkeit nicht> zwingend eine wichtige Randbedingung sein, wenn es in der Umgebung den> Zweck erfüllt.
Meine Erfahrung aus fast 30 Jahren Programmierung: Wenn der Aufwand für
die portable Lösung nicht signifikant größer ist als für die unportable,
dann ist es sinnvoll, erstere umzusetzen.
Oft genug wird aber das Thema Portabilität anfangs komplett ignoriert,
und wenn es dann später doch mal portiert werden soll, hat man einen
riesigen Aufwand, der erheblich kleiner hätte sein können, ohne vorher
großartig mehr Aufwand reingesteckt zu haben. Deshalb achte ich immer
von Anfang an darauf, Dinge höchstens dann unportabel zu machen, wenn
sie an der Stelle für mich einen signifikanten Vorteil gegenüber der
portablen Variante haben.
> Ein Kommentar diesbezüglich im Quelltext wäre sicher nicht falsch!
Besser dann ein paar asserts, die fehlschlagen, wenn die getroffenen
Annahmen über das System nicht zutreffen.
Rolf M. schrieb:> Und dann? Nehme ich diesen Pointer, um per for-Schleife den Inhalt ins> Zielarray zu kopieren? Welchen Vorteil sollte das gegenüber memcpy> haben?
Nein, nicht kopieren. Ich dachte an den Zugriff auf die einzelnen Bytes
des uint32_t-Arrays. Keine Kopie erstellen, denn dann natürlich memcpy.
So in der Art:
@DerEgon und @Ernst B.
Mir ging es nur darum, zu verdeutlichen, was ich ganz oben meinte.
Danke für die Ergänzungen!
Wobei: auf var8[(i&~3)+3-(i&3)] wäre ich nicht gekommen, ich verstehe
nicht mal, wie es funktioniert, aber es tut 😀.
Εrnst B. schrieb:> Kommt auch die Byteorder wie vom TE gewünscht raus.
Naja, es kommt die umgekehrte Reihenfolge vom Host raus. Wenn der TE von
Big Endian ausgeht, kommt Little Endian raus, also genau falsch.
Niklas G. schrieb:> Naja, es kommt die umgekehrte Reihenfolge vom Host raus. Wenn der TE von> Big Endian ausgeht, kommt Little Endian raus, also genau falsch.
Stimmt natürlich.
HildeK schrieb:> Wobei: auf var8[(i&~3)+3-(i&3)] wäre ich nicht gekommen, ich verstehe> nicht mal, wie es funktioniert, aber es tut 😀.
Es verwurschtelt die zwei niedrigsten Bits in der Adresse (& 3
entspricht & 0b011), damit diese "rückwärts" zählen.
Erzeugt dabei aber vmtl. furchtbar ineffizienten ASM-Code.
Niklas G. schrieb:> Naja, es kommt die umgekehrte Reihenfolge vom Host raus. Wenn der TE von> Big Endian ausgeht, kommt Little Endian raus, also genau falsch.
Richtig. Er hat es vermutlich auf meinen Ausschnitt bezogen und da kommt
es mit meiner PC-Umgebung dann beginnend mit dem MSByte richtig rum
heraus.
Plattform abhängig bleibt es, da hilft nur der vom TO schon im
Eröffnungspost angegebene Variante.