Guten Morgen,
ich habe ein 16-Bit-Array, das ich in ein 8-Bit-Array doppelter Länge
verpacken will, also so:
1
voidput8(uint8_tdata[],size_tlen);
2
3
4
voidfoo(void)
5
{
6
// Existiert schon:
7
uint16_tvar1[]={0x1234,0x5678,0x9ABC};
8
9
// Soll so gelesen werden:
10
uint8_tvar2[]={0x12,0x34,0x56,0x78,0x9A,0xBC};
11
}
Meine Plattform (ARM32) ist little-endian, also fällt eine einfache
Kopie mit memcpy weg. Immerhin scheint das Array dicht gepackt. Der
folgende kleine Test macht zumindest das, was ich erwarte:
1
enum{
2
big_endian=0,
3
little_endian=1,
4
unpacked=2,
5
};
6
7
uint_fast8_tcheckTypPunning(void)
8
{
9
uint16_ttst1[]={0x1234,0x5678,0x9ABC};
10
uint8_t*tst2=tst1;
11
boolbe0=(tst2[0]==0x12);
12
boolbe1=(tst2[1]==0x34);
13
boolbe2=(tst2[2]==0x56);
14
boolbe3=(tst2[4]==0x78);
15
boolle0=(tst2[0]==0x34);
16
boolle1=(tst2[1]==0x12);
17
boolle2=(tst2[2]==0x78);
18
boolle3=(tst2[3]==0x56);
19
20
if(be0&be1&be2&be3)
21
returnbig_endian;
22
elseif(le0&le1&le2&le3)
23
returnlittle_endian;
24
else
25
returnunpacked;
26
}
Das einzige, was mir standardkonform einfällt, wäre:
1
// Existiert schon:
2
uint16_tvar1[]={0x1234,0x5678,0x9ABC};
3
4
// Konvertierung
5
size_tlen=sizeof(var1);
6
uint8_t*var2=(uint8_t*)malloc(len);
7
for(size_ti=0;i<len;i++){
8
var2[i]=ntohs(var1[i]);
9
}
10
11
// Nutzung
12
put8(var2,len);
malloc() würde ich allerdings gern weitestgehend vermeiden; das Array
darf auch zerstört werden. Was ist jetzt die richtige™ Vorgehensweise?
Grüße
Punny
Type Pun Intended schrieb:> Immerhin scheint das Array dicht gepackt.
Das ist eine Grundvoraussetzung für Arrays.
> Das einzige, was mir standardkonform einfällt, wäre:
1
var2[i]=ntohs(var1[i]);
ntohs gehört zum Posix-Standard und nicht zum C-Standard.
Du kannst dir aber selber eine Funktion mit Bitoperationen basteln.
Deine Konvertierung funktioniert so aber nicht.
Du holst 16-Bit-Werte raus und schreibst sie nach 8-Bit.
Dabei gehst du über die Grenzen deines 16-Bit Arraya hinaus.
> das Array> darf auch zerstört werden.
Dann interpretiere die Variable einfach als uint8_t Array.
1
uint16_tvar1[]={0x1234,0x5678,0x9ABC};
2
uint8_t*pvar2=&var1[0];
3
4
//hier dein code z.B:
5
if(pvar2[0]==0x34){
6
//..
7
}
Dann musst du halt überall wissen, dass die Bytes verkehrtherum
angeordnet sind. Dann könntest du z.B. eine Zugriffsfunktion schreiben
(finde ich jedoch unelegant!), wie:
Nachtrag:
Es geht dann z.B. auch folgendermaßen. Dann kommt man ganz ohne memcpy
aus und das Array wird auch nicht zerstört. Wenn du hingegen viele
Zugriffe hast, würde ich mir schon überlegen das Array zu formatieren,
braucht man weniger Rechenleistung.
Du versuchst, die "strict-aliasing"-Regel zu erfüllen. Das ist
grundsätzlich sehr löblich, hier jedoch völlig unnötig (zumindest in C).
Hier darf man ungestraft jeden beliebigen Zeigertyp auf einen
char-Zeiger casten (nur andersrum nicht):
1
var2=(uint8_t*)var1;
wär' hier also völlig ausreichend.
Ob die Bytes in der für dich richtigen oder falschen Reihenfolge stehen,
ist ein anderes Problem und mit ntohxxx()-Funktionen nicht sauber zu
erschlagen. Die können nur (weil sie eben so definiert ist) von
Network-Byteorder (big endian) auf Host-Byteorder wandeln. Auf einer
Big-Endian Plattform machen die folglich einfach genau nix (was
wahrscheinlich nicht das ist, was Du haben willst).
Markus F. schrieb:> Auf einer> Big-Endian Plattform machen die folglich einfach genau nix (was> wahrscheinlich nicht das ist, was Du haben willst).
Doch, genau das wollte der OP laut seinem 1. Beitrag haben.
Markus F. schrieb:> Du versuchst, die "strict-aliasing"-Regel zu erfüllen. Das ist> grundsätzlich sehr löblich, hier jedoch völlig unnötig (zumindest in C).
Ich behaupte jetzt mal einfach etwas:
Sowas passiert, wenn man ursprünglich aus der PC-Programmierung kommt,
nur vorgefertigte Bibliotheken nimmt, die einen ST z.B. für die STM32
zur Verfügung stellt und noch nie auf Registerebene programmiert hat.
Ich persönlich programmiere z.B. komplett ohne HAL und Stdlib etc.
Auf µC-Ebene sind eben auch sehr direkte Wege erlaubt.
Dirk B. schrieb:> Du holst 16-Bit-Werte raus und schreibst sie nach 8-Bit.> Dabei gehst du über die Grenzen deines 16-Bit Arraya hinaus.
Jetzt, wo ich meinen Kaffee hatte, sehe ich's auch.
Das ist gemeint:
1
for(size_ti=0;i<len;i+=2){
2
uint16_ttmp=var1[i/2];
3
var2[i]=tmp>>8;
4
var2[i+1]=tmp&0xFF;
5
}
Und dann sollte ich noch etwas nachliefern, sonst bin ich den ganzen Tag
nervös:
1
free(var2);
Jonas B. schrieb:>>if( be0 & be1 & be2 & be3 )>> Unabhängig vom Problem, das macht sicher nicht das was du denkst...
Ja.... vielleicht hätte ich vorher richtig wach werden sollen.
A. schrieb:> Wenn auch folgendes erlaubt sein darf:> uint8_t var2[] = {0x34, 0x12, 0x78, 0x56, 0xBC, 0x9A};
Neee, das ist genau nicht gewünscht. Eine Zugriffsfunktion im
Pilgerschritt wird mich spätestens bei der übernächsten Codeänderung
verwirren.
Dirk B. schrieb:> Das ist eine Grundvoraussetzung für Arrays.
Ich finde im C-Standard lediglich, dass Zeiger-Arithmetik immer den
tatsächliche Abstand der Elemente berücksichtigt. Wo finde ich denn,
dass sizeof(uint16_t[2]) == sizeof(uint8_t[4]) ?
Markus F. schrieb:> Auf einer> Big-Endian Plattform machen die folglich einfach genau nix (was> wahrscheinlich nicht das ist, was Du haben willst).
Hm... ich gehe davon aus, dass auf eine Big-Endian-Plattform meine
16-Bit-Arrays schon von sich aus richtig herum im Speicher liegen.
Brauche ich echt noch einen zweiten Kaffee?
A. schrieb:> Auf µC-Ebene sind eben auch sehr direkte Wege erlaubt.
Mit µC oder nicht hat das nicht das Geringste zu tun. C ist C - hier wie
dort.
Was das mit Registern zu tun haben soll, erschliesst sich mir
ebensowenig.
Na, wahrscheinlich programmierte er bisher nur dicke Anwendungen für
Windows/Linux.
Nun kommt er daher und will einen µC programmieren. Ist doch super,
sowas wie CubeMX initialisiert einen alles so wie man will und man macht
nur noch Klicki-Bunti.
Und dann kommen solche Fragen! Auch auf einem ARM32 benutze ich kein
malloc oder ähnliches. Hier reicht eine einfache Zeigerarithmetik,
Kopierfunktion, Transformationsfunktion oder Zugriffsfunktion.
Wenn man auf Registerebene programmiert setzt man sich häufig auch mit
der Struktur von µC auseinander und man lernt effizient zu
programmieren.
Type Pun Intended schrieb:> Hm... ich gehe davon aus, dass auf eine Big-Endian-Plattform meine> 16-Bit-Arrays schon von sich aus richtig herum im Speicher liegen.> Brauche ich echt noch einen zweiten Kaffee?
Definiere "richtig herum" (auch wenn ich selbst zur m68k-Generation
gehöre, der little endian immer noch wie Teufelswerk vorkommt...).
Ich bin davon ausgegangen, dass Du eine universelle
"Byte-verdreh"-Routine suchst, die abhängig von der Host-Byteorder immer
das richtige tut.
Die gibt's nicht fertig.
Type Pun Intended schrieb:> Ich finde im C-Standard lediglich, dass Zeiger-Arithmetik immer den> tatsächliche Abstand der Elemente berücksichtigt. Wo finde ich denn,> dass sizeof(uint16_t[2]) == sizeof(uint8_t[4]) ?
Der C-Standard verlangt, das bei Arrays von Objekten keine zusätzlichen
Füllbytes eingebaut werden - die sind dann schon im eigentlichen Objekt
vorhanden.
Du möchtest auf der einen Seite einen 16-Bit Typ haben, auf der anderen
einen 8-Bit Typ. 16 = 2*8
Wenn es einen davon nicht gibt, meckert der Compiler.
Anders sieht es bei uint_fast8_t oder uint_least8_t. Die können auch
mehr Bits haben.
Type Pun Intended schrieb:> Das ist gemeint: for( size_t i = 0; i<len; i+=2 ) {> uint16_t tmp = var1[i/2];> var2[i] = tmp>>8;> var2[i+1] = tmp & 0xFF;> }
Das ist die standardkonforme und kompatible Vorgehensweise.
Moderne Compiler erkennen das und machen daraus dann auch ein
Assembler-Byteswap, wenn es nötig ist.
Hier scheinen wieder menschliche Wesen den Turing-Test nicht zu
bestehen. Sobald das Wort "ARM" fällt, wird direkt auf "Klicki-Bunti",
"CubeMX" und Wasweisich geschlossen. "Ein richtiger Commander kennt jede
Schraube und jede Schweißnaht seines Schiffes und ein richtiger
Programmierer arbeitet grundsätzlich mit Registern." Nein. Das ist alles
nicht das Thema.
Es gibt ein Array, das von der Größe so Richtung 1/10 SRAM geht. Also
gross genug, dass es sinnlos ist, in Registern zu denken, und noch klein
genug, dass es schön wäre, eine Kopie zu vermeiden, aber nicht absolut,
um jeden Preis notwendig ist.
Das Problem ist so elementar, dass es garantiert ziemlich tief im
Quelltext-Berg verschwindet und irgendwann einmal, wenn vielleicht alle
µC auf irgendeinem SPARC18-Kern basieren, garantiert nicht mehr als
Fehlerursache vermutet wird. Oder alternativ schon nächste Woche wieder
gelöscht sein.
Also suche ich die
Jetzt-und-dann-Ruhe-für-immer-richtige™-Vorgehensweise.
Es gibt hier kein falsch oder richtig. Viele Wege führen nach Rom. Über
die Randbedingungen wissen wir hier ja nichts.
Du kannst das Ding kopieren und dabei gleich richtig formatieren. Du
kannst auch das bestehende Array formatieren und überschreiben. Du
kannst auch eine Funktion nehmen um die bestehenden Daten formatiert
auszulesen, auch ok. Auch kannst du gar nichts machen und das Array als
8bit interpretieren (hier musst du gar nichts rechnen, nur müssen deine
restlichen programmcodes wissen, dass es big endian ist).
Was du machst liegt doch in deiner Entscheidung? Ich (also .. ich) würde
den Weg gehen, der am wenigsten Rechenzeit verbraucht, also entweder
einmal formatieren oder die Daten einfach so lassen und bei jedem
zugriff formatieren (wenn es die Rechenleistung zulässt).
Was du machst, das liegt bei dir..
Dirk B. schrieb:> Das ist die standardkonforme und kompatible Vorgehensweise.> Moderne Compiler erkennen das und machen daraus dann auch ein> Assembler-Byteswap, wenn es nötig ist.
Danke für die Info. Ich erwarte allerdings irgendwelchen unterwarteten
Ärger, wenn ich die Ersetzung In-Place vornehme:
1
// Existiert schon:
2
uint16_tvar1[]={0x1234,0x5678,0x9ABC};
3
4
// Konvertierung
5
size_tlen=sizeof(var1);
6
uint8_t*var2=var1;
7
for(size_ti=0;i<len;i+=2){
8
uint16_ttmp=var1[i/2];
9
var2[i]=tmp>>8;
10
var2[i+1]=tmp&0xFF;
11
}
12
13
// Nutzung
14
put8(var2,len);
Ich verletze an der Stelle ja die Strict-Alias-Regel. Würde ich das
Ganze nur über eine Variable machen, um die Regel nicht zu verletzten
also so:
1
// Existiert schon:
2
uint16_tvar1[]={0x1234,0x5678,0x9ABC};
3
4
// Konvertierung
5
size_tlen=sizeof(var1);
6
boolbyteswap=// Ausdruck, der zur Compilezeit berechnet wuerde, waere toll
7
if(byteswap){
8
for(size_ti=0;i<len/n;i++){
9
uint16_ttmp=var1[i];
10
var1[i]=(tmp>>8)|(tmp<<8);
11
}
12
}
13
14
15
// Nutzung
16
put8(var2,len);
bräuchte ich ja irgendeinen Ausdruck, mit dem ich feststellen kann, ob
die Vertauschung überhaupt notwendig ist. Am besten irgendetwas, was
schon vom Compiler ausgewertet werden kann.
Type Pun Intended schrieb:> Danke für die Info. Ich erwarte allerdings irgendwelchen unterwarteten> Ärger, wenn ich die Ersetzung In-Place vornehme:
Warum? Interrupts, die zwischendurch auf die Daten zugreifen wollen?
Vielleicht habe ich ja das Problem noch nicht verstanden.
ist es
- ein int16_t-Array von (irgendeiner) byte order in ein int16_t-Array
mit Host-Byteorder
- ein int16_t-Array von Network-Byteorder in ein int16_t-Array mit
Host-Byteorder
- ein int16_t-Array in ein int8_t-Array
zu wandeln? Oder nochmal was anderes.
Oder in anderen Worten: wo kommen die Daten her und was willst Du damit
machen?
Type Pun Intended schrieb:> bräuchte ich ja irgendeinen Ausdruck, mit dem ich feststellen kann, ob> die Vertauschung überhaupt notwendig ist. Am besten irgendetwas, was> schon vom Compiler ausgewertet werden kann.
Das ist simpel:
1
staticinlineboolhost_is_be(void)
2
{
3
constuint16_tcheck=1;
4
5
if(((uint8_t*)(&check))[1]==1)
6
returntrue;
7
returnfalse;
8
}
Daraus macht der Compiler bei Optimierung einfach ein true oder false,
je nach Host-Plattform.
Wenn du von Anfang nur wissen wolltest, ob eine Konvertierung notwendig
ist, warum fragst du dann nicht direkt nach so einer Funktion?
1. Ist Konvertierung notwendig?
2. Dann konvertieren.
Letztendlich hast du dich dann jetzt für ein swap entschieden, also die
bestehenden Daten zu konvertieren - falls dies notwendig ist.
Was war die ursprungsfrage nochmal?
Type Pun Intended schrieb:> ... das ist genau nicht gewünscht. Eine Zugriffsfunktion im> Pilgerschritt wird mich spätestens bei der übernächsten Codeänderung> verwirren.
Dann verkapsele sie halt hypsch.
Bei der Längenermittlung von einem Array, dividiert man nicht durch die
Größe vom Typ, sondern durch die Größe eines Elementes vom Array.
sizeof(var1)/sizeof(var1[0]);
oder
sizeof(var1)/sizeof(*var1);
Das kann man dann auch als Makro machen