Forum: Compiler & IDEs GCC [STM32] -> wie sehen wie ein struct realisiert wird?


von Michael U (Gast)


Lesenswert?

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
typedef struct __attribute__((packed)){
4
    uint8_t a;
5
    int32_t b;
6
    bool c;
7
    uint64_t d;
8
} ts_test;

oder gerne auch in Kombination mit Bitfields und einer Union:
1
#include <stdint.h>
2
3
typedef struct __attribute__((packed)){
4
    uint8_t a : 6;
5
    int32_t b : 20;
6
    bool c : 1;
7
    uint64_t d : 35;
8
} ts_test;
9
10
typedef union{
11
    ts_test obj;
12
    uint8_t can_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.

von stm apprentice (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

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 Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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
typedef struct {
2
    uint64_t d;
3
    int32_t b;
4
    bool c;
5
    uint8_t a;
6
} ts_test;

(bool ist normalerweise ein 8-Bit-Typ, aber da das nicht immer so sein 
muss, noch oberhalb von uint8_t.)

von Michael U (Gast)


Lesenswert?

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.
1
#include <stdint.h>
2
3
typedef struct __attribute__((packed)){
4
    uint8_t Feuchtigkeit: 6;
5
    int32_t Temperatur: 20;
6
    bool QBit_Temperatur : 1;
7
    uint64_t Systemzeit_us : 35;
8
} ts_test;
9
10
typedef union{
11
    ts_test obj;
12
    uint8_t can_data[8];
13
} tu_test;

Ideen sind natürlich herzlich Willkommen!

von Markus F. (mfro)


Lesenswert?

1
void hexdump(uint8_t buffer[], int size)
2
{
3
   int i;
4
   int line = 0;
5
   uint8_t *bp = buffer;
6
7
   while (bp < buffer + size) {
8
      uint8_t *lbp = bp;
9
10
      printf("%08x  ", line);
11
12
      for (i = 0; i < 16; i++) {
13
          uint8_t c = *lbp++;
14
         if (bp + i > buffer + size) {
15
            break;
16
         }
17
         printf("%02x ", c);
18
      }
19
20
      lbp = bp;
21
      for (i = 0; i < 16; i++) {
22
         int8_t c = *lbp++;
23
24
         if (bp + i > buffer + size) {
25
            break;
26
         }
27
         if (c > ' ' && c < '~') {
28
            printf("%c", c);
29
         } else {
30
            printf(".");
31
         }
32
      }
33
      printf("\r\n");
34
35
      bp += 16;
36
      line += 16;
37
   }
38
}
39
40
...
41
42
hexdump(tu, sizeof(tu));

von pegel (Gast)


Angehängte Dateien:

Lesenswert?

Ein Blick in den Speicher zeigt es mMn:

typedef struct __attribute__((packed)){
    uint8_t a;
    int32_t b;
    bool c;
    uint64_t d;
} ts_test;
...
ts_test ts;
ts.a=0x11;
ts.b=0x22222222;
ts.c=true;
ts.d=0x3333333333333333;
&ts=&ts;
...
Bilder...

von pegel (Gast)


Angehängte Dateien:

Lesenswert?

und das

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

Ich weiß, das hilft nicht wirklich, aber in Ada würde man das so machen 
(Representation Clause):
1
type a_type is mod 2 ** 6;
2
type b_type is range 0 .. 2 ** 20 - 1;
3
type c_type is range 0 .. 1;
4
type d_type is range 0 .. 2 ** 35 - 1;
5
type Byte is range 0 .. 255;
6
...
7
type ts_test is record
8
   a    : a_type;
9
   b    : b_type;
10
   c    : c_type;
11
   d    : d_type;
12
end record;
13
14
for ts_test use record
15
   a at 0 range 0 .. 5;
16
   b at 1 range 0 .. 19;
17
   c at 4 range 0 .. 1;
18
   d at 5 range 0 .. 34;
19
end record;
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.

von Jens R. (tmaniac)


Lesenswert?

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 🙈

: Bearbeitet durch User
von Cyblord -. (cyblord)


Lesenswert?

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.

von Bauteiltöter (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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

: Bearbeitet durch User
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.