Ein C-programmierter Controller bekommt über Bussysteme Datenpakete
zugeschickt, die u.A. zig Flags enthalten (Überstrom, Unterstrom,
Übertemperatur etc). Diese Flags sollen lokal in einer Datenstruktur
gespeichert werden, um den Zustand der sendenden Geräte abzubilden.
Es wäre am lesbarsten, die Datenstrukturen im Controller logisch
abzubilden, so wie im Datenblatt beschrieben (zB durch verschachtelte
Structs und Bitfelder). Das ist natürlich langsam. Die Auswertung später
im Code wäre aber einfach, das Flag kann direkt abgefragt werden.
Alternativ kann ich die Pakete wie empfangen byteweise speichern, das
braucht wenig Takte. Dafür müsste ich später mit Masken arbeiten und die
Auswertung wäre deutlich kryptischer (Bitgeschubse eben).
1
//Beispiel:
2
Device.Parameters.Signals=packet;
3
4
if(Device.Parameters.Signals)
5
{
6
for(uint8_ti=0;i<8;i++)
7
{
8
//maskieren
9
//bit 1? -> Position speichern
10
//schieben
11
}
12
}
Ideal wäre es, wenn man ein empfangenes byte in einem Rutsch schreiben
könnte, aber dann wie ein Bitfeld abfragen. Meines Wissens nach
funktioniert das aber nicht, weil zB nicht definiert ist, wie die Bits
in einem Bitfeld angelegt werden.
1
//Beispiel:
2
Device.Parameters.Signals=packet;
3
4
if(Device.Parameters.Signals.Overtemp)
5
{
6
//oh boy, do something
7
}
Gibt es weitere Möglichkeiten, die ich übersehe? Wie löst man das am
geschicktesten?
Wenn ich Dein Problem richtig verstehe, dann scheint mir die "union" das
passende Mittel zu sein. Das Thema ist umfangreich, weil es ein paar
Konsequenzen hat, ist aber beherrschbar - nur eben nicht in Kürze und
gleichzeitig erschöpfend abzuhandeln.
Im wesentlichen aber läuft es (skizzenhaft) auf folgende beispielhafte
Deklaration hinaus:
1
typedefunionpacket{
2
struct{
3
unsignedintbit0:1;
4
unsignedintbit1:1;
5
unsignedintbit2:1;
6
unsignedintbit3:1;
7
unsignedintbit4:1;
8
unsignedintbit5:1;
9
unsignedintbit6:1;
10
unsignedintbit7:1;
11
unsignedintbit8:1;
12
unsignedintbit9:1;
13
unsignedintbit10:1;
14
unsignedintbit11:1;
15
unsignedintbit12:1;
16
unsignedintbit13:1;
17
unsignedintbit14:1;
18
unsignedintbit15:1;
19
}bits;
20
21
unsignedintdata;
22
}packet_t;
Bitte lies mal in Deinem C Buch und ergänzend im C11-Standard darüber
nach.
Theor schrieb:> Wenn ich Dein Problem richtig verstehe, dann scheint mir die "union" das> passende Mittel zu sein. Das Thema ist umfangreich, weil es ein paar> Konsequenzen hat, ist aber beherrschbar - nur eben nicht in Kürze /und/> gleichzeitig erschöpfend abzuhandeln.>> Im wesentlichen aber läuft es (skizzenhaft) auf folgende beispielhafte> Deklaration hinaus:>> Bitte lies mal in Deinem C Buch und ergänzend im C11-Standard darüber> nach.
Danke, das scheint mir das richtige Werkzeug zu sein.
Ich benutze C99, da sagt der Standard: "The order of allocation of
bit-fields within a unit (high-order to low-order or low-order to
high-order) is implementation-defined. The alignment of the addressable
storage unit is unspecified."
Bedeutet das in diesem Kontext nicht, dass je nach Architektur der Wert
von 'data' unterschiedlich ist?
Fragererer schrieb:> Theor schrieb:>> Wenn ich Dein Problem richtig verstehe, ...>> Danke, das scheint mir das richtige Werkzeug zu sein.
Bitteschön.
> Ich benutze C99, da sagt der Standard ...> Bedeutet das in diesem Kontext nicht, dass je nach Architektur der Wert> von 'data' unterschiedlich ist?
Das Thema ist, wie gesagt umfangreich. Und man muss bei solchen Themen,
den philologischen Ehrgeiz eines Juristen haben. Der zitierte Satz sagt
nur, dass die Reihenfolge, (eigentlich, genauer die Zuordnung von erstem
Bit in der Reihenfolge zu entweder Low oder High-Byte eines
Architektur-Wortes) in C99 nicht garantiert ist, nicht das die
Reihenfolge je nach Architektur unterschiedlich ist .
Ich empfehle folgende Lektüre: Den Standard der zu Deinem Compiler
passt. Da sich da Veränderungen ergeben habe, eben weil im
Embedded-Bereich gewisse absolute Festlegungen zweckmäßig sind, auch den
neueren Standard C11 und ggf. den Wechsel auf eine neuere
Compilerversion. Ebenso und aus dem selben Grund die Compiler-Manuals.
Ah. Jetzt ist mir auch ein Flüchtigkeitsfehler unterlaufen. Tut mir
leid.
"implementation-defined" sagt nicht - wie ich oben schrieb, dass die
Reihenfolge nicht garantiert ist, sondern das sie
"implementierungsabhängig" ist.
Hast Du wahrscheinlich selbst gemerkt, dass das nicht sein kann, aber
ich schreibs nochmal sicherheitshalber.
Der Satz von mir oben, muss also lauten:
"Der zitierte Satz sagt nur, dass die Reihenfolge, (eigentlich, genauer
die Zuordnung von erstem
Bit in der Reihenfolge zu entweder Low oder High-Byte eines
Architektur-Wortes) in C99 von der Implementierung abhängig ist, nicht
das die Reihenfolge je nach Architektur unterschiedlich ist."
Ich denke, dass diese Art, eine union zu verwenden (type-punning), UB
ist, obgleich sie wahrscheinlich oft funktioniert.
1) Es wird die Komponente bits beschrieben
2) es wird data gelesen.
Damit ist bits das active member und von data lesen induziert UB.
Die Ausnahme
---
6.5.2.3/6
One special guarantee is made in order to simplify the use of unions: if
a union contains several structures that share a common initial sequence
(see below), and if the union object currently contains one of these
structures, it is permitted to inspect the common initial part of any of
them anywhere that a declaration of the completed type of the union is
visible. Two structures share a common initial sequence if corresponding
members have compatible types (and, for bit-fields, the same widths) for
a sequence of one or more initial members.
---
zieht auch nicht leider.
Zudem bleibt die Anordnung der bit-Felder implementierungs-abhängig.
Fragererer schrieb:> Ideal wäre es, wenn man ein empfangenes byte in einem> Rutsch schreiben könnte, aber dann wie ein Bitfeld> abfragen.
Warum "wie ein Bitfeld abfragen"? Was gefaellt Dir an
Deinem Vorschlag "(packet & (1 << 6));" nicht?
> Meines Wissens nach funktioniert das aber nicht, weil> zB nicht definiert ist, wie die Bits in einem Bitfeld> angelegt werden.
Kannst Du nicht versuchen, aus dem empfangenen Bytestrom
Bloecke konstanter Laenge (z.B. 32bit = 4 Byte) zu machen?
Idee ist, immer nur vollstaendige Datenfelder in so einem
Doppelwort zu haben, wobei die Anzahl der Datenfelder
unterschiedlich sein wird. Dazu muesstest Du Fuellbytes
einfuegen bzw. nach Bedarf auch Empfangsbytes doppelt speichern.
Die Bits dieser Doppelworte kannst Du wie gewohnt mit AND
und SHIFT abfragen.
Die Endianess muesstest Du nur einmal beim Konvertieren
beruecksichtigen.
Theor schrieb:> Ich empfehle folgende Lektüre: Den Standard der zu Deinem Compiler> passt. Da sich da Veränderungen ergeben habe, eben weil im> Embedded-Bereich gewisse absolute Festlegungen zweckmäßig sind, auch den> neueren Standard C11 und ggf. den Wechsel auf eine neuere> Compilerversion. Ebenso und aus dem selben Grund die Compiler-Manuals.
IAR EWARM. Ich habe das entsprechende Dokument angehängt.
S.347 "Pragma directives": bitfields - Controls the order of bitfield
members.
S.349f "Descriptions of pragma directives" beschreibt die Möglichkeiten.
Default ist:
1
joined_types
2
Bitfield members are placed depending on the byte
3
order. Storage containers of bitfields will overlap other
4
structure members. For more information, see
5
Bitfields, page 322
Alternativ gibt es 'disjoint_types'
1
Bitfield members are placed from the least significant
2
bit to the most significant bit in the container type.
3
Storage containers of bitfields with different base
4
types will not overlap.
und 'reversed_disjoint_types'
1
Bitfield members are placed from the most significant
2
bit to the least significant bit in the container type.
3
Storage containers of bitfields with different base
4
types will not overlap.
Ich werde mal austesten, wie sich das auf den Maschinencode auswirkt.
Wilhelm M. schrieb:> 1) Es wird die Komponente bits beschrieben> 2) es wird data gelesen.
Wenn ich dich richtig verstehe ist es genau andersherum, d.h. es wird
data geschrieben und bits gelesen.
Fragererer schrieb:>> Wilhelm M. schrieb:>> 1) Es wird die Komponente bits beschrieben>> 2) es wird data gelesen.>> Wenn ich dich richtig verstehe ist es genau andersherum, d.h. es wird> data geschrieben und bits gelesen.
Ja, wie auch immer. UB entsteht, wenn man das nicht-aktive Element
verwendet.
Wilhelm M. schrieb:> Ich denke, dass diese Art, eine union zu verwenden (type-punning), UB> ist, obgleich sie wahrscheinlich oft funktioniert.
Ja, das ist sie. Man darf eben immer nur das Element lesen, das als
letztes geschrieben wurde. Die Verwendung (bzw. der Missbrauch) von
Unions zur Konvertierung verstößt gegen diese Regel.
Ich hab eh noch nie verstanden, warum Unions dafür so beliebt sind. Es
ist umständlicher als ein Cast oder ein memcpy, weil man sich extra nur
für die Konvertierung einen eigenen Datentyp definieren muss.
> Zudem bleibt die Anordnung der bit-Felder implementierungs-abhängig.
Ja, auch hier ist es besser, sich diese Abhängigkeit zu ersparen.
Rolf M. schrieb:> Wilhelm M. schrieb:>> Ich denke, dass diese Art, eine union zu verwenden (type-punning), UB>> ist, obgleich sie wahrscheinlich oft funktioniert.>> Ja, das ist sie. Man darf eben immer nur das Element lesen, das als> letztes geschrieben wurde. Die Verwendung (bzw. der Missbrauch) von> Unions zur Konvertierung verstößt gegen diese Regel.> Ich hab eh noch nie verstanden, warum Unions dafür so beliebt sind. Es> ist umständlicher als ein Cast oder ein memcpy, weil man sich extra nur> für die Konvertierung einen eigenen Datentyp definieren muss.>>> Zudem bleibt die Anordnung der bit-Felder implementierungs-abhängig.>> Ja, auch hier ist es besser, sich diese Abhängigkeit zu ersparen.Beitrag "Re: Viele Flags effizient speichern und schreiben/Byte in Bitfeld?"
Ist doch so nicht mehr undefined?
Fragerer schrieb:> Beitrag "Re: Viele Flags effizient speichern und schreiben/Byte in Bitfeld?"> Ist doch so nicht mehr undefined?
Es wurde ja schon gesagt, dass es implementation-defined ist. Das heißt,
dass jeder Compiler es machen kann, wie er will, aber es muss
dokumentiert sein. Das obige ist der Auszug aus der Doku eines ganz
spezifischen Compilers. Der nächste Compiler kann es dann ganz anders
machen.
Rolf M. schrieb:> Fragerer schrieb:>> Beitrag "Re: Viele Flags effizient speichern und schreiben/Byte in Bitfeld?">> Ist doch so nicht mehr undefined?>> Es wurde ja schon gesagt, dass es implementation-defined ist. Das heißt,> dass jeder Compiler es machen kann, wie er will, aber es muss> dokumentiert sein. Das obige ist der Auszug aus der Doku eines ganz> spezifischen Compilers. Der nächste Compiler kann es dann ganz anders> machen.
Schon klar. Fürs Erste soll mir das reichen.
Nochmal Danke für die vielen hilfreichen Antworten!
Unions mit 1-4 Byte Zugriff macht man eigentlich nur für Register des
µC, um die einzelnen Bits manipulieren zu können und dann in einem
Rutsch zu lesen und zu schreiben.
Telegramme, vor allem wenn sie mehr als 4 Byte enthalten, schreibt man
eigentlich in einer (verschachtelten) Struktur runter und betrachtet
dann die ganze Struktur fürs Senden und beim Empfangen als Stream, also
als void-Pointer mit Längenangabe.
Beispiel:
Achim S. schrieb:> Unions mit 1-4 Byte Zugriff macht man eigentlich nur für Register> des> µC, um die einzelnen Bits manipulieren zu können und dann in einem> Rutsch zu lesen und zu schreiben.>
Dachte genau das soll man nicht machen, da nicht portabel?
Gibts hier einige Threads zu, z.B.
Beitrag "Bitfeld und union" oder
Beitrag "Wie auf Union mit Bitfeldern direkt zugreifen?"> Telegramme, vor allem wenn sie mehr als 4 Byte enthalten, schreibt man> eigentlich in einer (verschachtelten) Struktur runter und betrachtet> dann die ganze Struktur fürs Senden und beim Empfangen als Stream, also> als void-Pointer mit Längenangabe.>> Beispiel:typedef struct> {> Theader head;> uint16 cnt;> tXyZ xyZ;> t... t...> }Tmsg1;>> Tmsg1 msg1, RcvMsg1;>> typedef struct> {> Theader head;> tAbc abc;> tefg efg;> t... t...> }Tmsg2;>> Tmsg2 msg2, RcvMsg2;>> typedef enum> {> TYP_MSG_unknown,> TYP_MSG_1,> TYP_MSG_2,> }TTypMsg;>> /* empfangsroutine */> void receiveMsg(TTypMsg typ, void *msg, int len)> {> if(typ == TYP_MSG_1 && len == sizeof(RcvMsg1))> {> memcpy(RcvMsg1, *msg, len);> }> }>> /* senden (geschieht auch mit memcpy)*/> sndMsg(TYP_MSG_2, &msg2, sizeof(msg2));
Auch das ist nicht portabel. Leider.
Ich habe das Gefühl, wir müssen mal einen Wikiartikel (oder gibts da
schon welche) mit essenziellen Sachverhalten erstellen: Registerzugriffe
per Bitfields (sind nicht gut...), portables Serialisieren und
Übertragen von Daten (Endianess, union Problematik [gelesene Variable !=
geschriebene ist implementation defined]). Die Fragen treten immer und
immerwieder auf. Auch ich bin mir jedes mal unsicher, wie es nun
portabel und möglichst elegant implementiert werden sollte.
Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?
Schöne Grüße,
Jan
Jan K. schrieb:> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?
Konkret für mich und in diesem Projekt ist das "nice to have", aber
erstmal muss die Kiste schnell laufen und beweisen ob etwas
funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.
Rolf M. schrieb:> Es wurde ja schon gesagt, dass es implementation-defined ist. Das heißt,> dass jeder Compiler es machen kann, wie er will,
"machen was er will" wäre undefined.
Der Compiler wird sich auch an das jeweilige Plattform ABI halten
müssen, da stehen die meisten implementation defined Sachen schwarz auf
weiß drin, nur was danach immer noch übrig bleibt fällt wirklich unter
"undefined".
Und im ABI wiederum sind die meisten Sachen nicht willkürlich festgelegt
sondern folgen einer gewissen Logik die sich oftmals geradezu aufdrängt,
z.B. Alignment und Padding von Structs, der Aufbau von Unions und
dergleichen.
Und wenn man es gut meint mit seinem Nachfolger oder seinem eigenen
zukünftigen Ich oder seinen Enkelkindern kann ja noch ein paar
Compile-Time Asserts an den wackeligen Stellen einbauen die
Compilerfehler werfen a la: "#error: Guten Tag mein sehr junger Padawan,
Du wirst Dich nicht mehr an mich erinnern, ich bin Dein Urgroßvater. Wie
ich sehe willst Du also wirklich diesen alten Code auf einer anderen
Plattform wiederbeleben? Dann finde als erstes den Compilerschalter der
xy bewirkt.".
Jan K. schrieb:> Ich habe das Gefühl, wir müssen mal einen Wikiartikel (oder gibts da> schon welche) mit essenziellen Sachverhalten erstellen: Registerzugriffe> per Bitfields (sind nicht gut...), portables Serialisieren und> Übertragen von Daten (Endianess, union Problematik [gelesene Variable !=> geschriebene ist implementation defined]). Die Fragen treten immer und> immerwieder auf. Auch ich bin mir jedes mal unsicher, wie es nun> portabel und möglichst elegant implementiert werden sollte.
M.E: wäre das eine lohnenswerte Aufgabe.
> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?
Auf gar keinen Fall (jedenfalls bei mir). Mir ist es sogar sehr wichtig,
denn ich muss auch sehr oft Code aus dem non-µC-Bereich für viele
Plattformen (e.g. Linux, *BSD, Windows, Solaris, HP-UX) schreiben. Und
da lernt man Plattformneutralität bzw. Portabilität sehr zu schätzen -
und zwar von Anfang an.
Fragerer schrieb:> Jan K. schrieb:>> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?>> Konkret für mich und in diesem Projekt ist das "nice to have", aber> erstmal muss die Kiste schnell laufen und beweisen ob etwas> funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.
Dann wird es wohl nie geschehen ...
Wilhelm M. schrieb:> Fragerer schrieb:>> Jan K. schrieb:>>> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?>>>> Konkret für mich und in diesem Projekt ist das "nice to have", aber>> erstmal muss die Kiste schnell laufen und beweisen ob etwas>> funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.>> Dann wird es wohl nie geschehen ...
Das ist definitiv möglich - wenn es bei der Plattform bleibt und
funktioniert, warum nicht...
Fragerer schrieb:> Wilhelm M. schrieb:>> Fragerer schrieb:>>> Jan K. schrieb:>>>> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?>>>>>> Konkret für mich und in diesem Projekt ist das "nice to have", aber>>> erstmal muss die Kiste schnell laufen und beweisen ob etwas>>> funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.>>>> Dann wird es wohl nie geschehen ...>> Das ist definitiv möglich - wenn es bei der Plattform bleibt und> funktioniert, warum nicht...
Das war nur eine Bemerkung aus der Erfahrung in SW-Projekten ;-) "Das
machen wir später" bedeutet leider manchmal eben: nie. Vor allem
natürlich, wenn da kein gescheites Projektmanagement beteiligt ist ...
Fragerer schrieb:> Wilhelm M. schrieb:>> Fragerer schrieb:>>> Jan K. schrieb:>>>> Oder ist euch die Portabilität egal (das ist nicht angreifend gemeint!)?>>>>>> Konkret für mich und in diesem Projekt ist das "nice to have", aber>>> erstmal muss die Kiste schnell laufen und beweisen ob etwas>>> funktioniert. Wenn das gegeben ist ist viel Zeit da für die Kür.>>>> Dann wird es wohl nie geschehen ...>> Das ist definitiv möglich - wenn es bei der Plattform bleibt und> funktioniert, warum nicht...
Das fiese ist halt, falls man irgendwann auf einmal doch mit einem
anderen System reden muss, das z.B. eine andere Endianess hat ist man
geliefert. All deine Binärstreams müssen auseinander genommen werden.
Sowas erneut zu implementieren ist mega aufwendig und fehleranfällig.
Jan K. schrieb:> Ich habe das Gefühl, wir müssen mal einen Wikiartikel (oder gibts da> schon welche) mit essenziellen Sachverhalten erstellen: Registerzugriffe> per Bitfields (sind nicht gut...), portables Serialisieren und> Übertragen von Daten (Endianess, union Problematik [gelesene Variable !=> geschriebene ist implementation defined]). Die Fragen treten immer und> immerwieder auf. Auch ich bin mir jedes mal unsicher, wie es nun> portabel und möglichst elegant implementiert werden sollte.
Ganz klasse wäre eigentlich ein clang static-analyzer dafür ...
> if(Device.Parameters.Signals.Overtemp)> {> //oh boy, do something> }> Gibt es weitere Möglichkeiten, die ich übersehe?> Wie löst man das am geschicktesten?
So:
#define sigUndrcurr (1<<5) //in dieser Reihenfolge, sonst
unübersichtlich
#define sigOvercurr (1<<6)
#define sigOvertemp (1<<7)
if(Device.Parameters.Signals & sigOvertemp)
{
//oh boy, do something
}
Fragererer schrieb:> Ein C-programmierter Controller bekommt über Bussysteme Datenpakete> zugeschickt, die u.A. zig Flags enthalten (Überstrom, Unterstrom,> Übertemperatur etc). Diese Flags sollen lokal in einer Datenstruktur> gespeichert werden, um den Zustand der sendenden Geräte abzubilden.> Es wäre am lesbarsten, die Datenstrukturen im Controller logisch> abzubilden, so wie im Datenblatt beschrieben (zB durch verschachtelte> Structs und Bitfelder). Das ist natürlich langsam.
Du machst einen Generalfehler, indem du versuchst, Dinge in die
Ausdrucksmöglichkeiten von C hineinzupressen, die von sonstwo kommen und
dort keinen definierten Platz haben. Bitfelder zum Beispielsind ne
Krätze, weil sie eben nur innerhalb eines kompilierten Projektes ihre
volle Gültigkeit haben. Schon dann, wenn dir ein anderes Gerät ein
Datenpaket zuschickt, was mit einem anderen Compiler oder gar anderer
HW-Plattform erzeugt wurde, kannst du dir nicht sicher sein, daß du
auf die richtigen Dinge zugreifst.
Mein Rat:
1. definiere dir ein hardwareunabhängiges Protokoll, also wie denn nun
ganz genau deine Informationen im Datenpaket dargestellt sein müssen.
Das brauchst du dringendst, damit Sender aller Art und Empfänger aller
Art etwas haben, worauf sie sich verlassen können. Endianess ist ein
Thema, also beschränke dich auf Bytes, am besten Textzeichen in deinen
Datenpaketen.
2. kapsele dieses Zeugs in einer Art Objekt. Also anstatt
{c]
Device.Parameters.Signals.Overcurrent = (packet & (1 << 6));
[/c]
definiere dir einen Satz von Funktionen zum Setzen und Abfragen, also
etwa sowas in de Art:
1
boolisOvercurrent(void);
2
voidsetOvercurrent(boolyesno);
um die Innereien gegenüber deiner sonstigen Firmware zu kapseln. Und
knicke den Gedanken an Bitfelder. Das hat sich schon seit langem als
prächtige Fehlerquelle erwiesen, wenn man nicht immerzu höllisch
aufpaßt.
W.S.
Bernd K. schrieb:> Rolf M. schrieb:>> Es wurde ja schon gesagt, dass es implementation-defined ist. Das heißt,>> dass jeder Compiler es machen kann, wie er will,>> "machen was er will" wäre undefined.
Nein, undefined heißt, dass er sich nicht drum kümmern muss, was
passiert. Der Compilerhersteller darf davon ausgehen, dass das nicht
vorkommt, und wenn doch, passiert halt irgendwas ggf. unvorhergesehenes
("nasal demons" für diejenigen, die das noch kennen).
Ich glaube, wir haben einfach eine unterschiedliche Definition von
"machen was er will". Für mich heißt es, dass ISO C nicht festlegt, wie
es umzusetzen ist, sondern dass das im Aufgabenbereich der
Compiler-Hersteller liegt.
> Der Compiler wird sich auch an das jeweilige Plattform ABI halten> müssen, da stehen die meisten implementation defined Sachen schwarz auf> weiß drin, nur was danach immer noch übrig bleibt fällt wirklich unter> "undefined".
Klar muss er sich um die Einhaltung eines ABI kümmern, wenn er dieses
korrekt umsetzen will. Läuft für mich dennoch unter "machen was er
will", denn ISO C zwingt ihn ja nicht dazu, ein bestimmtes ABI
umzusetzen. Wir sprechen schließlich davon, was "implementation-defined"
nach ISO-C bedeutet.
> Und im ABI wiederum sind die meisten Sachen nicht willkürlich festgelegt> sondern folgen einer gewissen Logik die sich oftmals geradezu aufdrängt,> z.B. Alignment und Padding von Structs, der Aufbau von Unions und> dergleichen.
Allerdings ist das ABI in der Regel mindestens von der
Prozessorarchitektur abhängig, und somit wird es schon der gleiche
Compiler in seinen Ausprägungen für unterschiedliche Architekturen
wahrscheinlich unterschiedlich machen - vor allem, wenn die ABIs das
jeweils so verlangen. Ggf. gibt's auch mal mehr als ein ABI für eine
Architektur, und der Compiler kann per Kommandozeile umgeschaltet
werden.
> Und wenn man es gut meint mit seinem Nachfolger oder seinem eigenen> zukünftigen Ich oder seinen Enkelkindern kann ja noch ein paar> Compile-Time Asserts an den wackeligen Stellen einbauen die> Compilerfehler werfen a la: "#error: Guten Tag mein sehr junger Padawan,> Du wirst Dich nicht mehr an mich erinnern, ich bin Dein Urgroßvater. Wie> ich sehe willst Du also wirklich diesen alten Code auf einer anderen> Plattform wiederbeleben? Dann finde als erstes den Compilerschalter der> xy bewirkt.".
Das gehört für mich zum Minimum dessen, was man tun sollte.
Wilhelm M. schrieb:> Das war nur eine Bemerkung aus der Erfahrung in SW-Projekten ;-) "Das> machen wir später" bedeutet leider manchmal eben: nie. Vor allem> natürlich, wenn da kein gescheites Projektmanagement beteiligt ist ...
In der Regel ist "später" auch weder Zeit, noch Budget für sowas da.
Deshalb leben Provisorien so lange.
W.S. schrieb:> Mein Rat:> 1. definiere dir ein hardwareunabhängiges Protokoll, also wie denn nun> ganz genau deine Informationen im Datenpaket dargestellt sein müssen.> Das brauchst du dringendst, damit Sender aller Art und Empfänger aller> Art etwas haben, worauf sie sich verlassen können. Endianess ist ein> Thema, also beschränke dich auf Bytes, am besten Textzeichen in deinen> Datenpaketen.
Ist exakt definiert, die Sender sind fertige Geräte mit Datenblättern
usw. Warum glaubst du dass nichtmal das Protokoll definiert ist?
> 2. kapsele dieses Zeugs in einer Art Objekt. Also anstatt> {c]> Device.Parameters.Signals.Overcurrent = (packet & (1 << 6));> [/c]> definiere dir einen Satz von Funktionen zum Setzen und Abfragen, also> etwa sowas in de Art:>
1
>boolisOvercurrent(void);
2
>voidsetOvercurrent(boolyesno);
3
>
> um die Innereien gegenüber deiner sonstigen Firmware zu kapseln. Und> knicke den Gedanken an Bitfelder. Das hat sich schon seit langem als> prächtige Fehlerquelle erwiesen, wenn man nicht immerzu höllisch> aufpaßt.
Das würde einen riesigen undefinierten Haufen von Funktionen und
Variablen bedeuten, denn ich bekomme nicht nur Flags von den Sendern,
sondern alle möglichen verschiedenen Werte und Datentypen.
So habe ich eine Datenstruktur, die alle verfügbaren Daten sehr
übersichtlich und verständlich auflistet - so wie sie im Datenblatt des
Senders beschrieben sind. Die Daten werden beim Empfang sauber in die
Struktur geschrieben und sind danach selbsterklärend - das ist doch
fehlerunanfälliger als deine Variante?
Fragerer schrieb:> Die Daten werden beim Empfang sauber in die> Struktur geschrieben und sind danach selbsterklärend - das ist doch> fehlerunanfälliger als deine Variante?
Es gibt auch noch die Möglichkeit Code automatisch zu generieren zu
lassen, gerade dann wenn eine einzige Änderung an einem Datenfeld oder
ein neues Datenfeld immer gleich an drei oder vier Stellen
(structeintrag, getter, setter, etc.) penibelst synchron mitzupflegenden
Boilerplate bedingt.
So lass ich zum beispiel die Arrays für USB-Descriptoren zur Compilezeit
per Python-Script erzeugen, all die penibelst anzupassenden
Längenangaben, indices, UTF16-Strings, etc. in so einer monströsen
Byte-Array-Wüste bei der auch keine Structs mehr helfen werden so
automatisch erzeugt aus einer einzigen Quelle in der nur das nötigste so
kompakt und übersichtlich wie möglich hingeschrieben wird.
Meist endet bei solchen Vorhaben schnell die Ausdrucksfähigkeit des
eingebauten C-Präprozessors (von der Debugbarkeit ganz zu schweigen),
jedoch wenn man sich stattdessen dazu durchringt die Codeerzeugung in
einer gut lesbaren Scriptsprache zu implementieren kann man nach
Herzenslust aus einer simplen Beschreibung der Datenstruktur tonnenweise
100% korrekten C-Boilerplate erzeugen ohne sich auch nur im Geringsten
verrenken zu müssen.
W.S. schrieb:> Also anstatt> Device.Parameters.Signals.Overcurrent = (packet & (1 << 6));> [...]
Hm. Ich habe das meiste hier im Thread gelesen und immer
noch nicht verstanden, was an meinem Vorschlag von oben
falsch sein soll.
Die Bitordnung innerhalb eines Bytes ist ja hoffentlich
eindeutig. Wenn man die empfangenen Bytes durch Schieben
und Verodern zu z.B. maximal 32bit langen Worten zusammen-
baut, sollte die Ordnung ja immer noch eindeutig sein,
und wenn man NUR mit der 32bit-Variablen rechnet, ist
auch die Endianess wurscht.
Probleme gibt es nach meinem Verstaendnis nur bei
Mischung unterschiedlicher Zugriffe auf denselben
Speicherbereich.
(packet & (1 << 19)) liefert halt immer das Bit mit
der Wertigkeit 2^19, voellig egal, wo dieses Bit im
Speicher steht.
Was ist daran falsch?
Possetitjel schrieb:> Was ist daran falsch?
Ganz einfach: der TO hat mit keiner einzigen Silbe davon gesprochen, wie
denn nun genau seine ominösen Datenpakete aufgebaut sind und wie sie
denn eintreffen.
Da kann man ohne nähere Kenntnis keine Vorschläge a la "(packet & (1 <<
19))" machen. Das ist der Punkt.
Der TO hat offensichtlich ebenso übersehen, daß das von ihm so
abgelehnte "Bitgeschubse" ja so oder so fällig ist. Entweder indem man
in einem LowLevel-Treiber die Datenpakete auseinander nimmt und die
Bruchstücke in ein struct eigener Wahl hineinschreibt oder indem man das
Paket so läßt wie es ist und mittels spezieller Zugriffs-Funktionen (a
la bool isOvercurrent(void);) die gewünschte Information aus dem Paket
herauspickt.
Ich bin allemal für die letztere Methode, weil sie die Datenpakete
besser kapselt und im übergeordneten Programm für mehr Les- und
Wartbarkeit sorgt.
W.S.