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?
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.
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).
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.
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.
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
unionconvert
2
{
3
longi;
4
unsignedchara[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.
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...
Danke dir, Karl-Heinz.
D.h., folgender Code wäre in Ordnung?
1
voidprotocolParser(uint8_t*data,uint8_tlength)
2
{
3
uint8_tpayloadType;
4
5
payloadType=getPayloadType(data,length);
6
7
if(payloadType==PAYLOAD_TYPE_A)
8
{
9
domeSomething_A((payloadA_t*)data);
10
}
11
elseif(payloadType==PAYLOAD_TYPE_B)
12
{
13
domeSomething_B((payloadB_t*)data);
14
}
15
elseif(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?
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
voidprotocolParser(uint8_t*data,uint8_tlength)
2
{
3
uint8_tpayloadType;
4
5
payloadType=getPayloadType(data,length);
6
7
if(payloadType==PAYLOAD_TYPE_A)
8
{
9
payloadA_tconvData;
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.
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.
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.
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.
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??
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.