Forum: Compiler & IDEs Bytepuffer nach Struktur casten?


von ben (Gast)


Lesenswert?

Morgen,

ich mache mir gerade Gedanken über eine Software.
Hintergrund: über eine Schnittstelle gelangen Binärdaten in den uC. 
Diese werden in einem Puffer byteweise gesammelt (Puffer ist uint8_t[]).
Anschließend soll der Puffer einem Protokoll-Parser übergeben werden, 
der eben den Datenstrom auswertet.
Das Protokoll besitzt einen Header, außerdem sind, abhängig von diesem 
Header, die Nutzdaten zu interpretieren.

Ich habe daran gedacht mir für die verschiedenen Nutzdaten-Typen jeweils 
eine Struktur zu definieren.
Nachdem der Typ bestimmt wurde würde ich mir einen Zeiger auf die 
Nutzdaten besorgen und in einen Strukturzeiger casten.
Dann kann ich bequem auf die Felder zugreifen.

Das kann natürlich mächtig ins Auge gehen, zwecks Padding und Byteorder.
Padding kann man per Attribut abschalten, die Byteorder kann man per 
Makro oder Inline-Funktion richtig drehen, wobei das Makro im Idealfall 
einfach garnix macht.

Gibt es bei diesem Vorgehen noch andere Stolpersteine?
Gibt es ein schöneres Vorgehen?
Ich habe mal gegoogelt und folgenden Thread gefunden, wo sich auch Herr 
Buchegger geäußert hat: Beitrag "Aufbau von Structs im Speicher"
Das liest sich ja als gäbs keine weiteren Probleme. Oder verletze ich 
damit schon die strict-aliasing Regel?

von Udo S. (urschmitt)


Lesenswert?

Wenn du etwas über ein Netzwerk sendest solltest du dir angewöhnen die 
Daten immer in einem festen Format zu senden. Im Fall von integer oder 
longs hat es sich eingebürgert eine feste "Net Byte Order" zu benutzen, 
dafür gibt es in den TCP/UTP Stacks auch Makros.
Weiter solltest du dir mal "Unions" anschauen. Die dienen genau dazu 
einen Speicherbereich in mehrere logisch unterschiedliche Gliederungen 
zu unterteilen.

von ben (Gast)


Lesenswert?

Udo Schmitt schrieb:
> Wenn du etwas über ein Netzwerk sendest solltest du dir angewöhnen die
> Daten immer in einem festen Format zu senden. Im Fall von integer oder
> longs hat es sich eingebürgert eine feste "Net Byte Order" zu benutzen,
> dafür gibt es in den TCP/UTP Stacks auch Makros.
> Weiter solltest du dir mal "Unions" anschauen. Die dienen genau dazu
> einen Speicherbereich in mehrere logisch unterschiedliche Gliederungen
> zu unterteilen.

Leider nicht das was ich gefragt habe.

1) Da meine Binärdaten als Protokoll zu interpretieren sind besitzen 
diese bereits ein "festes Format". Der Sender regelt das bereits alles.
Wenn ich von Byteorder-Problemen rede dann gehts mir darum, den 
empfangenen Frame für die Zielarchitektur aufzubereiten. Der Empfänger 
muss unter Umständen die Bytes erst neu verdrösseln.

2) Ich kenne Unions. Diese haben aber mit den gleichen Problemen zu 
kämpfen wie meine Lösung. Laut C-Standard darf man auch nur den Member 
lesen, der zuletzt geschrieben wurde (salopp ausgedrückt).

von Peter II (Gast)


Lesenswert?

mach doch einfach ein memcpy in die Structur rein.

von ben (Gast)


Lesenswert?

Peter II schrieb:
> mach doch einfach ein memcpy in die Structur rein.

Damit umgehe ich die mögliche Verletzung des strict-aliasing (falls es 
zu so einer kommt, da bin ich mir nicht sicher).

Das behebt weder das Padding, noch das Byteorder Problem.

von Peter II (Gast)


Lesenswert?

ben schrieb:
> Das behebt weder das Padding,

das kann man mit einen mit einen pragma verhindern.

>  noch das Byteorder Problem.
damit muss man halb leben, wenn man Daten als Struct versenden will.

von Karl H. (kbuchegg)


Lesenswert?

ben schrieb:

> 1) Da meine Binärdaten als Protokoll zu interpretieren sind besitzen
> diese bereits ein "festes Format".

gut.
Der wichtigste Punkt ist, dass es ein für alle mal definiert und 
festgenagelt wird, wie sich die Byte Order auf der Protokoll Ebene 
präsentiert.

> Der Sender regelt das bereits alles.

Auch gut

> Wenn ich von Byteorder-Problemen rede dann gehts mir darum, den
> empfangenen Frame für die Zielarchitektur aufzubereiten. Der Empfänger
> muss unter Umständen die Bytes erst neu verdrösseln.

Ja, das kann dir passieren.
Hast du denn so viele unterschiedliche Empfänger-Architekturen, dass das 
zum Problem werden könnte?

> 2) Ich kenne Unions. Diese haben aber mit den gleichen Problemen zu
> kämpfen wie meine Lösung. Laut C-Standard darf man auch nur den Member
> lesen, der zuletzt geschrieben wurde (salopp ausgedrückt).

Im Prinzip ja.
Es gibt nur eine Ausnahme. Und die ist, wenn man unsigned char, signed 
char oder char Arrays mit etwas anderem überlagert. Also genau der Fall 
den du hast. Wobei der C-Standard nichts darüber aussagt, ob es Padding 
Bytes gibt oder geben könnte, bzw. wie dann die Byte-Order aussieht. Der 
C-Standard legitimiert im Grunde
1
union convert
2
{
3
  long          i;
4
  unsigned char a[sizeof(long)];
5
};

in der Form, dass es zwar zulässig ist, das Verhalten aber von der 
Implementierung bestimmt wird.

> Oder verletze ich damit schon die strict-aliasing Regel?

Genau darum gehts bei dieser Ausnahme einer Union von irgendwas mit 
char-Arrays: die werden immer als aliased angenommen.

Mehr zu dem Thema "struct aliasing"
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

> Gibt es ein schöneres Vorgehen?
Ob schöner oder nicht, ist Geschmackssache. Eine Übertragung in Textform 
kennt diese Probleme so nicht, erzeugt aber mehr Overhead. Will man 
binär Übertragen, dann hat man diese "Probleme" und solange sich die 
Chip-Schmieden bzw. Compilerbauer dieser Welt nicht darüber einigen, 
welches die einzige wahre Byte-Order ist, bzw. wieviele Padding Bytes 
von jeder einzelnen Architektur immer gleich und an welcher Stelle 
eingefügt werden sollen, solange wird es System-Programmierer geben, die 
an den Schnittstellen nach aussen die entsprechenden Anpassungen 
einprogrammieren müssen.
Das ist ein ganz normaler Vorgang, also nichts wovor du groß Bammel 
haben musst. Sorge dafür, dass dein eingehender Datenstrom möglichst 
rasch und frühzeitig in die Systematik deines konkreten Prozessors 
gebracht wird, so dass darüber liegende Softwareschichten sich nicht 
mehr damit herumschlagen müssen. Kapsle diesen "Anpass-Code" 
einigermassen gut ein, mach es für nachfolgende 
Programmierergenerationen leicht, das alles auf eine andere Architektur 
anzupassen. Mehr kannst du nicht tun.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

ben schrieb:
> Gibt es ein schöneres Vorgehen?
Man kann Binärdaten auch explizit einlesen... Man definiert sich im 
Protokoll genau in welchem Byte welche Daten stehen und welche 
Byte-Reihenfolge die Integer haben. Dann füllt man jedes Byte einzeln im 
Code manuell, und zerlegt dabei Integer mit Bitshifts. Beim Empfänger 
macht mans umgekehrt, und holt jedes Byte einzeln raus und schreibt es 
in die entsprechenden C-Variablen. Integer setzt man aus mehreren Bytes 
zusammen mit Bitshifts und "|". Das ist zwar ziemlich aufwändig, stellt 
aber sicher dass sich der Code auf jeder Plattform genau gleich verhält 
und somit das Protokoll überall gleich verstanden wird...

von ben (Gast)


Lesenswert?

Danke dir, Karl-Heinz.

D.h., folgender Code wäre in Ordnung?
1
void protocolParser(uint8_t *data, uint8_t length)
2
{
3
  uint8_t payloadType;
4
  
5
  payloadType = getPayloadType(data, length);
6
  
7
  if (payloadType == PAYLOAD_TYPE_A)
8
  {
9
    domeSomething_A( (payloadA_t *) data);
10
  }
11
  else if (payloadType == PAYLOAD_TYPE_B)
12
  {
13
    domeSomething_B( (payloadB_t *) data);
14
  }
15
  else if (payloadType == PAYLOAD_TYPE_C)
16
  {
17
    domeSomething_C( (payloadC_t *) data);
18
  }
19
  else
20
  {
21
    //error
22
  }
23
}
Wobei payloadA_t, payloadB_t und payloadC_t Strukturen mit 
attribute_packed sind.

Oder sollte man vor dem Cast + Funktionsaufruf doch noch ein memcpy() 
spendieren?

von Karl H. (kbuchegg)


Lesenswert?

ben schrieb:

> Oder sollte man vor dem Cast + Funktionsaufruf doch noch ein memcpy()
> spendieren?

Was würde dir der memcpy bringen, ausser das ein und dieselbe Bytefolge 
von A nach B kopiert wird?

Nach
1
void protocolParser(uint8_t *data, uint8_t length)
2
{
3
  uint8_t payloadType;
4
  
5
  payloadType = getPayloadType(data, length);
6
  
7
  if (payloadType == PAYLOAD_TYPE_A)
8
  {
9
    payloadA_t convData;
10
    memcpy( convData, data, length );
11
12
    domeSomething_A( &convData );
13
  }
14
...
hat sich ja nichts grundsätzliches geändert, ausser dass dieselben Bytes 
jetzt an einer anderen Stelle im Speicher stehen.
Es kann natürlich Gründe geben, warum man das tun möchte. Spontan fallen 
mir ein
* wenn data asychron befüllt wird und als Buffer daher möglichst schnell 
für die Aufnahme der nächsten Übertragung wieder zur Verfügung stehen 
soll
* wenn die Bytes in domeSomething_A die Daten verändert werden und diese 
Veränderung nicht nach aussen an den Originalen sichtbar sein soll.

: Bearbeitet durch User
von ben (Gast)


Lesenswert?

Karl Heinz schrieb:
> Was würde dir der memcpy bringen, ausser das ein und dieselbe Bytefolge
> von A nach B kopiert wird?

Hatte Bedenken auf den selben Speicher via zwei verschiedene Pointer 
zuzugreifen. Nur weil ich während der Parameterübergabe caste und der 
Zugriff in der Unterfunktion stattfindet ändert es ja nichts daran dass 
es der selbe Zeiger ist.

Ob es notwendig ist (oder schöner) kann ich nicht beurteilen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ja nach Architektur läuftst du auf segmentation fault (oder wie die 
ensprechende Trap dort heißen mag) wegen Alignment-Problemen.  Mit 
memcpy in ein korrekt aligntes Zielobjekt funktioniert es dann hingegen.

von Fabian O. (xfr)


Lesenswert?

ben schrieb:
> D.h., folgender Code wäre in Ordnung?

Streng genommen nicht.

Karl Heinz schrieb:
> Mehr zu dem Thema "struct aliasing"
> http://cellperformance.beyond3d.com/articles/2006/...

Aus dem Artikel:
The converse is not true. Casting a char* to a pointer of any type other 
than a char* and dereferencing it is usually in volation of the strict 
aliasing rule.

: Bearbeitet durch User
von Ernst L. (Gast)


Lesenswert?

Johann L. schrieb:
> Ja nach Architektur läuftst du auf segmentation fault (oder wie die
> ensprechende Trap dort heißen mag) wegen Alignment-Problemen.  Mit
> memcpy in ein korrekt aligntes Zielobjekt funktioniert es dann hingegen.

ich habe ein ähnliches problem.

ich verstehe deine aussage so, du hast ein uint8 Array und kopierst das 
mittels memcpy in eine struktur (so wie der TE).
Wenn du das auf einem 32Bitter machst, dann landen doch die einzelnen 
bytes irgendwo, aber sicher nicht da wo sie sollen, oder irre ich mich?

Bei einer packed- Struktur kannst du in einen fault laufen. Ich sehe 
also immer noch keine Lösung des Problems??

von huhuuu (Gast)


Lesenswert?

schau dir mal die kleinen IP stacks an ..
die machen quasi deine errste idee

die Daten liegen  in einem
1
uint8_t eth_rxbuf[1536];

Dann gibt es dirverse typedef structs
1
typedef struct __attribute__((packed)){
2
  uint64_t         dst_mac   : 48; //48bit Dst MAC
3
  uint64_t         src_mac   : 48; //48bit Src MAC
4
  unsigned int     type      : 16; //16bit Type
5
} ETH_Header;
6
7
typedef struct __attribute__((packed)){
8
  unsigned int     hd_len    :  4; // 4bit Version
9
  unsigned int     ver       :  4; // 4bit Header Len
10
  unsigned int     tos       :  8; // 8bit Type of Service
11
  unsigned int     len       : 16; //16bit Packet Len
12
  unsigned int     id        : 16; //16bit ID
13
  unsigned int     flg_offs  : 16; // 3bit Flags 
14
  unsigned int     ttl       :  8; // 8bit Time to Live
15
  unsigned int     proto     :  8; // 8bit Protocol
16
  unsigned int     checksum  : 16; //16bit Checksum 
17
  unsigned long    src_ip    : 32; //32bit IP Src
18
  unsigned long    dst_ip    : 32; //32bit IP Dst
19
} IP_Header;

in den funktionen wird der eth_rxbuf  als parameter übergeben
1
ETH_Header *rx_eth;
2
IP_Header *rx_ip;
3
4
rx_eth = (ETH_Header*) &eth_rxbuf[0];
5
rx_ip  = (IP_Header*) &eth_rxbuf[14];
6
7
switch(rx_eth->type){
8
    case ETH_TYPE_IP:
9
         switch(rx_ip->proto){
10
             case IP_PROTO_ICMP:
11
                rx_icmp = (ICMP_Header*) &eth_rxbuf[ICMP_OFFSET];
12
                if(rx_icmp->type == ICMP_ECHO_REQ){
13
                }
14
                break;
15
         }      
16
         break;
17
           
18
    case ETH_TYPE_ARP:
19
        break;

von Steffen R. (steffen_rose)


Lesenswert?

ben schrieb:
> Das behebt weder das Padding, noch das Byteorder Problem.

Casten ändert nicht an den Daten. Insofern ist dies kein Ansatz für die 
Lösung deines Problems.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.