Hallo,
ich würde mich sehr dafür interessieren wie mein GCC ein von mir
definiertes struct realisiert. Also insbesondere die Auswirkungen von
'packed' und 'aligned' genauso wie eine potenzielle Nutzung von
Bitfields
1
#include<stdint.h>
2
3
typedefstruct__attribute__((packed)){
4
uint8_ta;
5
int32_tb;
6
boolc;
7
uint64_td;
8
}ts_test;
oder gerne auch in Kombination mit Bitfields und einer Union:
1
#include<stdint.h>
2
3
typedefstruct__attribute__((packed)){
4
uint8_ta:6;
5
int32_tb:20;
6
boolc:1;
7
uint64_td:35;
8
}ts_test;
9
10
typedefunion{
11
ts_testobj;
12
uint8_tcan_data;
13
}tu_test;
Da wäre es für mich sehr interessant, wie der Compiler das genau
umsetzt, sprich ob er wirklich (in Abhängigkeit des Optimize?)
Stuffing-Bits einführt, etc.
*Ist jemandem eine Möglichkeit bekannt wo das irgendwie dargestellt
ist?* Ich werde weder aus dem *.list noch aus dem *.map File schlau,
geschweige denn würde ich solche Informationen dort finden.
Schreib dir ein Minimalprogramm mit Verwendung deiner Struktur
und lasse es im Debugger laufen. Dann beobachte die Struktur
mit dem Debugger, er wird dir auch Adressen zeigen (können).
Daraus kannst du dann die Anordnung, Speicherausnutzung und
eventuelle "Löcher" erkennen.
Michael U schrieb:> *Ist jemandem eine Möglichkeit bekannt wo das irgendwie dargestellt> ist?* Ich werde weder aus dem *.list noch aus dem *.map File schlau,> geschweige denn würde ich solche Informationen dort finden.
Datentypen sind in der Form im finalen Compilat nicht mehr vorhanden. Am
einfachsten geht sowas, indem du dir die Offsets bzw. die Adressen der
Elemente und ihre Größe als Debug-Ausgabe rausschreibst. Allerdings geht
das für Bitfelder nicht.
Alternativ eine Instanz der Struktur als volatile anlegen und dann alle
Elemente nacheinander beschreiben. Dann kann man im Assembler-Code
nachschauen, wo er das hinschreibt.
Michael U schrieb:> Da wäre es für mich sehr interessant, wie der Compiler das genau> umsetzt, sprich ob er wirklich (in Abhängigkeit des Optimize?)> Stuffing-Bits einführt, etc.
Wenn das Layout von Datentypen von Optimierungs-Optionen abhängt, dann
ist das ein Compiler-Bug. Bzw. das Layout von Strukturen ist nicht von
Optimierungs-Einstellungen abhängig.
Optionen wie -f[no-]pack-struct, -f[no-][un-]signed-char etc. sind
keine Optimierungs-Optionen sondern Schalten zum Einstellen des ABI
(Binary Interface). Manchmal wird ein anderes ABI gewählt, weil das
kleineren Code ermöglicht oder Speicher spart, ist aber ne andere
Schublade als Optimierung.
Zur Frage: GCC-Schalter zur Ausgabe des Layouts (in welcher Form auch
immer) kenne ich keine.
Eventuell im Programm mal eine paar Prints einbauen die dir Sizeof und
offsetoff ausgeben. Das lässt schonmal Rückschlüsse darüber zu wie viele
Byte insgesammt verwendet werden und wo die einzelnen Variabeln
beginnen.
Das schöne ist das diese Werte bereits vom Compiler erzeugt werden und
somit im Assemblerlisting auffindbar sind (oder einfach laufen lassen).
- https://en.cppreference.com/w/cpp/language/sizeof
- https://en.cppreference.com/w/cpp/types/offsetof
Von Bitfeldern mal abgesehen, die allgemeine Daumenregel sagt: schreib
die größten Datentypen (also die mit dem größten Alignment) zuerst, von
da kleiner werdend. Auf diese Weise braucht es innerhalb einer
Struktur kein Padding, egal, wie die Anforderungen an die
Speicherausrichtung der Zielarchitektur aussehen. Maximal wird Padding
am Ende der Struktur vonnöten sein.
Also für deine erste Struktur:
1
typedefstruct{
2
uint64_td;
3
int32_tb;
4
boolc;
5
uint8_ta;
6
}ts_test;
(bool ist normalerweise ein 8-Bit-Typ, aber da das nicht immer so sein
muss, noch oberhalb von uint8_t.)
Vielen Dank euch allen für euren Input. Ich werde mich mal mit einem
Debugger ransetzen und schauen, ob ich was sehen kann.
Danke auch für die Informationen zu ABI und offsetof() -> das kannte ich
noch nicht.
Jörg W. schrieb:> Von Bitfeldern mal abgesehen, die allgemeine Daumenregel sagt: schreib> die größten Datentypen (also die mit dem größten Alignment) zuerst, von> da kleiner werdend.
Leider bin ich da nicht so frei wie ich es gerne wäre. Das Layout den
CAN Pakets (Listing korrigiert, siehe unten) ist gegeben. Ich suche
gerade Möglichkeiten geschwindigkeitsoptimiert den Inhalt des CAN-Pakets
in eine für die Applikation gut nutzbare (meschlich leserliche!) Form zu
bringen.
Michael U schrieb:> Das Layout den CAN Pakets (Listing korrigiert, siehe unten) ist gegeben.
Der generische Weg ist es dann, die Felder einzeln aus einem uint8_t
Array zu erzeugen / kopieren. "packed" verursacht misaligned access,
nicht jede Architektur kann das (bei manchen gibt das einen Trap), und
bei denen, die es können, führt es in der Regel zu
Geschwindigkeitseinbußen.
Michael U schrieb:> Ich suche> gerade Möglichkeiten geschwindigkeitsoptimiert den Inhalt des CAN-Pakets> in eine für die Applikation gut nutzbare (meschlich leserliche!) Form zu> bringen.
Wenn es geschwindigkeitsoptimiert sein soll (eigentlich nicht nur dann,
sondern so ziemlich immer), würde ich die Struktur nicht als Bitfeld
anlegen, sondern mit Variablen mit ausreichend großem Typ für das
jeweilige Element, und dann eine Funktion, die die Daten aus den
CAN-Rohbytes ausliest und da reinfüllt.
Abgesehen davon, dass das Layout von Bitfeldern implementation-defined
ist, ist auch der Zugriff langsam, da für jeden Zugriff im Hintergrund
das Bitgefummel durchgeführt werden muss. Daher lieber einmal von Hand
bitfummeln und danach dann nur noch effizient zugreifen.
Ich weiß, das hilft nicht wirklich, aber in Ada würde man das so machen
(Representation Clause):
1
typea_typeismod2**6;
2
typeb_typeisrange0..2**20-1;
3
typec_typeisrange0..1;
4
typed_typeisrange0..2**35-1;
5
typeByteisrange0..255;
6
...
7
typets_testisrecord
8
a:a_type;
9
b:b_type;
10
c:c_type;
11
d:d_type;
12
endrecord;
13
14
forts_testuserecord
15
aat0range0..5;
16
bat1range0..19;
17
cat4range0..1;
18
dat5range0..34;
19
endrecord;
Man kann also für jedes Feld im Record angeben, bei welchem Byte
(System.Storage_Unit) an welcher Bitposition es starten bzw. enden soll
(kann man auch über Atribute zur Compile-Zeit abfragen). Ada kennt die
Länge eines Maschinenwortes (System.Word_Size), so dass man auch das
Alignment flexibel anpassen kann.
Die Feldtypen können als Modulare oder als Range-Types (wrap-around oder
Laufzeitfehler bzw. Exception bei Überlauf) festgelegt werden.
Da es um CAN Frames geht ein paar Gedanken am Rande:
1. Die Bytereihenfolge (Endiness) kann sich je nach Controller beim
lesen/schreiben vom/in den CAN-Controller rum drehen.
-> Und evtl so nicht mir mit der Bytereihenfolge des eigentlichen
Controllers zusammenpassen.
2. Die Daten in der CAN Message unterliegen aus Architektursicht einem
"Übertragungsprotokoll". Auch wenn es oftmals ein einfaches Protokoll
ist, welches nur die Lage und Reihenfolge der Bytes definiert.
-> das Handling der rohen CAN-Bytes und der Nutzdaten sollte durch eine
"Implenentierung eine Protokolls" umgesetzt werden. Damit wären die
Daten oberhalb des Protokolls auch unabhängig von CAN und evtl
Bit/Byte-allignment.
3. Normaler Weise wird bei CAN nicht Bit und Byte-Übertragung gemischt.
-> ein Byte sollte immer als kleinste geschlossene Übertragungseinheit
angesehen werden (es kann vielleicht mehrere Bit große Zustände
beinhalten). Nachfolgende "Zahlen" beginnen dann im neuen Byte.
Zum Erkennen der Byte-Order wurde ja schon geschrieben, dass das mit dem
Debugger am schnellst geht.
Da ich das selber vor kurzen für den bxCAN Controller in den STM32F4
umgesetzt habe, kann ich dein Suchen gerade nach vollziehen 🙈
Michael U schrieb:> ich würde mich sehr dafür interessieren wie mein GCC ein von mir> definiertes struct realisiert. Also insbesondere die Auswirkungen von> 'packed' und 'aligned' genauso wie eine potenzielle Nutzung von> Bitfields
Mit dem Debugger kannst du direkt in den Speicher sehen und somit jedes
Byte deines Structs angucken.
Auch in ADA sollte man das nicht machen.
Genau dieses Pattern (für mich eher ein Antipattern) hat mich schon
Mannmonate beschäftigt, das ganze funktioniert nämlich plötzlich
nichtmehr, wenn man den selben Code auf Big- und Little-Endian-Maschinen
laufen lassen möchte.
Bauteiltöter schrieb:> Auch in ADA sollte man das nicht machen.> Genau dieses Pattern (für mich eher ein Antipattern) hat mich schon> Mannmonate beschäftigt, das ganze funktioniert nämlich plötzlich> nichtmehr, wenn man den selben Code auf Big- und Little-Endian-Maschinen> laufen lassen möchte.
Das funktioniert auch dann (wenn man's richtig macht):
https://www.adacore.com/gems/gem-140-bridging-the-endianness-gap