Forum: Mikrocontroller und Digitale Elektronik Unterschiedliche "static const struct Größe beim compilieren auf ARM/x86


von Mr Bean (Gast)


Lesenswert?

Hallo zusammen,

ich habe in einer Software für einen ARM Cortex M0 ein "static const 
struct" angelegt.
Über das Struct wird die Speicheraufteilung in einem EEPROM gemacht. 
Wenn ich das gleiche Struct jetzt in einem Programm für einen x86 PC 
verwende und mit dem gcc compiliere, ist das Struct 1 Byte größer, als 
mit dem ARM-compiler. Also sizeof(struct) gibt mir auf der x86 Platform 
ein byte mehr aus, als auf der ARM Platform. Ich habe mir schon die 
Größe jedes einzelne Element des structs ausgeben lassen mit 
sizeof(struct.element) und verglichen. Die Elemente sind alle gleich 
groß.

Kann mir jemand sagen, wo der Unterschied herkommt?
Das macht mir echt Probleme...

Gruß,
Bean

von Georg A. (georga)


Lesenswert?

Liegt wohl an unterschiedlichen Alignment/Padding-Regeln von 
Einzelbytes. Nimm doch mal offsetof(), dann solltest du sehen, nach 
welchem Element es auf einmal anders ist.

Notfalls kann man mit dem packed-Attribut das auch aufs Minimum 
zusammenquetschen. Der Zugriff kann dann aber sehr ineffizent werden.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Stichwort alignment.

Kann mit compilerspezifischen Anweisungen umgangen werden, #pragma pack 
oder attribute(packed) o.ä., hat aber Auswirkungen auf die Performance.

Ansonsten musst Du auch die endianness berücksichtigen, wenn Du 
Datenstrukturen zwischen verschiedenen Prozessorarchitekturen 
austauschen willst.

von Christian K. (the_kirsch)


Lesenswert?

Ich hab den compiller bei mir immer so eingestellt das alle Warnungen 
aktiviert sind, damit auch das auffällt.

-Wpadded

Warn if padding is included in a structure, either to align an element 
of the structure or to align the whole structure. Sometimes when this 
happens it is possible to rearrange the fields of the structure to 
reduce the padding and so make the structure smaller.



Wenn du mal die Strukturdefinition possted können wir dir Tipps geben 
wie man die eventuell besser machen kann.

Auf 32Bit wird der Speicher immer in DWORD (4Bytes) adressiert, daher 
fügt der Compiler Padding-Bytes ein die dann ungenutzt sind, damit ein 
int immer an einer 4-Byte Speichergrenze anfängt.

Es hilft uint8_t oder uint16_t zu verwenden, wenn man weiß das die werte 
nicht größer sin können.

von Matthias L. (Gast)


Lesenswert?

Mich wundert allerdings, das es auf dem x86 grösser ist. Ich hätte es 
eher andersrum erwartet...

von (prx) A. K. (prx)


Lesenswert?

Matthias L. schrieb:
> Mich wundert allerdings, das es auf dem x86 grösser ist. Ich hätte es
> eher andersrum erwartet...

Warum?

von Matthias L. (Gast)


Lesenswert?

>Warum?

Nach meiner bisherigen Erfahrung hat der x86 ein 1-Byte Alignment. ALso 
keinerlei Füllbytes. Alles einfach an die nächste Adresse

Beim ARM gilt, die Adresse muss ganzzahlig durch die grösse des Datums 
teilbar sein...

von (prx) A. K. (prx)


Lesenswert?

Matthias L. schrieb:
> Nach meiner bisherigen Erfahrung hat der x86 ein 1-Byte Alignment.

Ja, x86 kann misaligned, sofern nicht abgeschaltet.

> ALso keinerlei Füllbytes. Alles einfach an die nächste Adresse

Könnte man zwar so machen, wäre aber z.T. recht langsam, da etliche CPUs 
der letzten >20 Jahre das arg übel nehmen. Tatsächlich wird deshalb 
entsprechend dem Datentyp aligned.

> Beim ARM gilt, die Adresse muss ganzzahlig durch die grösse des Datums
> teilbar sein...

Hängt vom Typ ab. Alte ARMs und Cortex M0 ja. Cortex M3 kann misligned 
bei Load/Store, wenn ich mich recht erinnere.

von Matthias L. (Gast)


Lesenswert?

Ich kenns hier nur von den PC-SPSen. Alle x86er haben 1Byte Alignemt, 
wärend ARMs so anlegen, wie beschrieben...

von Adib (Gast)


Lesenswert?

wenn beim Cortex das int16 nit bei einer geraden Addresse liegt bzw. 
int32 nicht auf einer durch 4 teilbaren Addresse, wird das mit 
zusätzlichen Operationen erkauft. Ist aber prinzipiell möglich.

Deshalb ist default beim GCC Compiler der effizientere Zugriff statt 
Memory-sparsamere Mode.

Ich kann nur empfehlen, wenn du im Memory die Struktur abbilden willst, 
das Padding im Code explizit auszuschalten:
1
struct blank_struct_t
2
{
3
  uint8_t    eins;
4
  uint16_t  zwei;
5
  uint8_t    drei;
6
} __attribute__((__packed__));

weil ja sonst durch Compilerschalter das Verhalten beeinflusst werden 
würde.

Grüße, Adib.
--

von (prx) A. K. (prx)


Lesenswert?

Alle mir bekannten C Compiler für x86 alignen entsprechend dem Datentyp. 
Details siehe Handbuch vom Compiler.

Programmiert man eine SPS in C?

von Matthias L. (Gast)


Lesenswert?

>Programmiert man eine SPS in C?

ST (strukturierter Text), ähnlich Pascal.

Im Hintergrund läuft WinCE und ein RealTime Treiber. Keine Ahnung, was 
da getrieben wird.

lässt man sich die Adressen der Member einer Struktur geben, so erhalte 
ich obiges Bild.

von (prx) A. K. (prx)


Lesenswert?

Dann sind Erfahrungen mit SPS auch nicht relevant für die Einschätzung 
von C. Zumal ich eine SPS nicht mit Wettrennen um die Performance-Krone 
assoziiere. Da gibts andere Ziele.

von Matthias L. (Gast)


Lesenswert?

Natürlich nicht. Mich würde es nur wundern, wenn sich die Compiler auf 
dem selben Zielsystem unterscheidlich verhalten...

von (prx) A. K. (prx)


Lesenswert?

Matthias L. schrieb:
> Natürlich nicht. Mich würde es nur wundern, wenn sich die Compiler auf
> dem selben Zielsystem unterscheidlich verhalten...

Unterschiedliche Ziele. Auf einem x86 kann ein einzelner misaligned 
Zugriff auch mal zig Takte Laufzeit kosten. Es hängt aber sehr vom 
konkreten Code und dem jeweiligen Core ab, wie das ausfällt.

Das kann sich kein C Compiler als Standardeinstellung erlauben.

Der technische Hintergrund ist hier beschrieben:
http://de.slideshare.net/RamdasMozhikunnath/load-storeexecution

von Mr Bean (Gast)


Lesenswert?

Gutem Morgen!

Vielen Dank für eure Antworten! Soetwas habe ich mir schon gedacht. 
Allerdings arbeite ich bereits mit dem __attribute__((_packed_)).

Hier meine Typdefinition:
1
typedef struct
2
{
3
  uint32_t         _operating_running_time[MAXIMUM_CHANNELS]; ///<accumulated hours of closed relay for each channel
4
  uint32_t         _service_time[MAXIMUM_CHANNELS];           ///<the service time (in hour) for each channel
5
  uint8_t          _ort_channel_select; ///<bit map for selected channel for hour and service counter. bitx = 1: channel x is selected
6
  FirstDayOfWeek   _fdow;          ///< configured first work day (uint8_t) @ref FirstDayOfWeek
7
  TimeFormat       _tf;            ///< configured time format (uint8_t) @ref TimeFormat
8
  WorkingMode      _working_mode;  ///< configured working mode (uint8_t) @ref WorkingMode
9
  Country_t        _country;       ///< configured country for astro (uint8_t) @ref Country_t
10
  City_t           _locations;     ///< configured locations (cities) for astro (uint16_t) @ref City_t
11
  int16_t          _longitude;     ///< configured longitude
12
  int16_t          _latitude;      ///< configured latitude
13
  int16_t          _time_zone;     ///< configured time zone in minute
14
  DaylightSaving   _dst;           ///< configured day light (uint8_t) @ref DaylightSaving
15
  uint8_t          _ws_month;      ///< configured month for end of winter time
16
  uint8_t          _ws_day;        ///< configured day for end of winter time
17
  uint8_t          _sw_month;      ///< configured month for end of sommer time
18
  uint8_t          _sw_day;        ///< configured day for end of sommer time
19
  Language         _lang;          ///< configured language (uint8_t) @ref Language
20
  uint8_t          _selection_flags; ///< configured Configuration
21
  BacklightMode    _blmode;        ///< configured back light mode (uint8_t) @ref BacklightMode
22
  uint16_t         _pin_code;      ///< configured default pin code
23
  networkInfo_t    networkInfo;    ///< configured channel info @ref networkInfo_t
24
  uint8_t         _deviceName[BLE_DEVICE_MAX_DEVICENAME_LEN];
25
} __attribute__((__packed__)) Config;

Und hier, die Zuweisung:
1
static const Config config_default = {
2
    ._operating_running_time = {0LU},                  // operating running time
3
    ._service_time           = {0LU},                  // service time
4
    ._ort_channel_select     =  0x03,                  // ORT channel select
5
    ._fdow                   = FDOW_Monday,            // first day of the week
6
    ._tf                     = TF_24H,                 // time format
7
    ._working_mode           = ModeAdvance,            // working mode
8
    ._country                = country_Germany,        // astro selected country
9
    ._locations              = Germany_city7,          // astro selected locations (city of Freiburg)
10
    ._longitude              = DEFAULT_LONGITUDE,      //  8 is the default longitude (Freiburg)
11
    ._latitude               = DEFAULT_LATITUDE,       // 48 is the default latitude (Freiburg)
12
    ._time_zone              = DEFAULT_TIMEZONE,       // GMT+1 is the default timezone
13
    ._dst                    = DST_EST,                // daylight saving
14
    ._ws_month               =  3,                     // Calibrated winter->summer transition month
15
    ._ws_day                 = 27,                     // Calibrated winter->summer transition day
16
    ._sw_month               = 10,                     // Calibrated summer->winter transition month
17
    ._sw_day                 = 30,                     // Calibrated summer->winter transition day
18
    ._lang                   = Lang_English,           // Default language
19
    ._selection_flags        = SLT_LANGUAGE_SELECTED | // language and
20
                               SLT_LOCATION_SELECTED | // location have been selected
21
                               SLT_VARIANCE_5,         //
22
    ._blmode                 = BL_Timed,               // backlight mode
23
    ._pin_code               = DEFAULT_PIN_CODE,       // 0
24
    .networkInfo = {
25
       .device = { {Channel_Disabled, 0} },
26
       .lastReference = 0,
27
       .commissioningAllowed = 0
28
    },      // NetworkInfo structure
29
  ._deviceName             = DEFAULT_DEVICE_NAME     // BLE device Name
30
};

Wie gesagt, habe immernoch das Problem, dass sizeof(config_default) 
1Byte größer ausgibt, als auf der Zielarchitektur. Die einzelnen 
Elemente aber wohl alle gleich groß sind.
Gibt es noch irgendwelche Compiler-Flags, die ich probieren kann?
(--short-enums habe ich schon an)

Gruß und vielen Dank für eure Hilfe,
Bean

von Klaus W. (mfgkw)


Lesenswert?

Deine struct hilft nicht viel, wenn man die einzelnen Typen nicht kennt, 
die darin vorkommen.

Vielleicht ist das alles ja auch nur Fehlalarm und das fehlende Byte 
wird ganz am Ende nach dem uint8_t dazugedichtet, damit man in einem 
Array aufeinanderfolgende Elemente so eines structs ablegen kann.

Wie bereits gesagt: gib dir halt die Offsets aus, dann muß man nicht 
spekulieren.

von Mr Bean (Gast)


Lesenswert?

Hallo Klaus,

Ok, ich habe mir die Offsets jetzt ausgeben lassen. Und ich kann 
erkennen, dass sich ab dem Eintrag _locations, der Offset um 1Byte 
verschiebt.
Also ist wohl das Element vor _locations, ein byte größer geworden.
Was fange ich mit dieser Information jetzt an?
Wie bekomme ich es wieder kleiner? ;-)

Gruß,
Bean

von (prx) A. K. (prx)


Lesenswert?

Unbekannte Attribute werden ignoriert. Probiers mal mit
__attribute__((packed))

von Christopher B. (chrimbo) Benutzerseite


Lesenswert?

Kannst du nicht die Member nach ihrer Groesse absteigend sortieren? Dein 
Country liegt auf einer ungeraden Adresse wenn ich mich nicht verzaehlt 
habe.

von Mr Bean (Gast)


Lesenswert?

Hallo,
ja das habe ich schon probiert. Bei Country_t (dem Typ vor locations), 
handelt es sich um einen typdef enum. Kann ich das attribut auch darauf 
anwenden?

Gruß,
Bean

von Kirsch (Gast)


Lesenswert?

nur mal kurz überflogen, und anhand der Kommentare die Bytegröße 
genommen
1
typedef struct
2
{
3
  uint32_t         _operating_running_time[MAXIMUM_CHANNELS]; ///<accumulated hours of closed relay for each channel
4
  
5
  uint32_t         _service_time[MAXIMUM_CHANNELS];           ///<the service time (in hour) for each channel
6
  
7
  uint8_t          _ort_channel_select; ///<bit map for selected channel for hour and service counter. bitx = 1: channel x is selected
8
  FirstDayOfWeek   _fdow;          ///< configured first work day (uint8_t) @ref FirstDayOfWeek
9
  TimeFormat       _tf;            ///< configured time format (uint8_t) @ref TimeFormat
10
  WorkingMode      _working_mode;  ///< configured working mode (uint8_t) @ref WorkingMode
11
  
12
  Country_t        _country;       ///< configured country for astro (uint8_t) @ref Country_t
13
  City_t           _locations;     ///< configured locations (cities) for astro (uint16_t) @ref City_t
14
  /*1 Byte padding*/
15
  
16
  int16_t          _longitude;     ///< configured longitude
17
  int16_t          _latitude;      ///< configured latitude
18
  
19
  int16_t          _time_zone;     ///< configured time zone in minute
20
  DaylightSaving   _dst;           ///< configured day light (uint8_t) @ref DaylightSaving
21
  uint8_t          _ws_month;      ///< configured month for end of winter time
22
  
23
  uint8_t          _ws_day;        ///< configured day for end of winter time
24
  uint8_t          _sw_month;      ///< configured month for end of sommer time
25
  uint8_t          _sw_day;        ///< configured day for end of sommer time
26
  Language         _lang;          ///< configured language (uint8_t) @ref Language
27
  
28
  uint8_t          _selection_flags; ///< configured Configuration
29
  BacklightMode    _blmode;        ///< configured back light mode (uint8_t) @ref BacklightMode
30
  uint16_t         _pin_code;      ///< configured default pin code
31
  
32
  networkInfo_t    networkInfo;    ///< configured channel info @ref networkInfo_t
33
  uint8_t         _deviceName[BLE_DEVICE_MAX_DEVICENAME_LEN];
34
} __attribute__((__packed__)) Config;


es wird zwischen _Locations und _longitude ein Byte Padding eingefügt

Größe von networkInfo_t fehlt


Mit -Wpadded zeigt dir der Compiler aber auch die Stelle an

von Mr Bean (Gast)


Lesenswert?

Hm, umsortieren möchte ich nur ungern, dann muss ich die Datenstruktur 
ja auch auf der Zielplatform ändern...
Gibt es keine andere Möglichkeit, dieses Problem zu lösen?

Gruß,
Bean

von Mr Bean (Gast)


Lesenswert?

Hallo,

-Wpadded habe ich eingeschaltet.

wenn ich __attribute__((_packed_)) weglasse, bekomme ich auch eine 
Warnung, "warning: padding struct size to alignment boundary 
[-Wpadded]".

Da ich das __attribute__((_packed_)) zu dem typedef hinzugefügt habe, 
verschwindet die Warnung.

Trotzdem kommt irgendwie ein Byte hinzu.

Gruß,
Bean

von Klaus W. (mfgkw)


Lesenswert?

Mr Bean schrieb:
> Hm, umsortieren möchte ich nur ungern, dann muss ich die Datenstruktur
> ja auch auf der Zielplatform ändern...
> Gibt es keine andere Möglichkeit, dieses Problem zu lösen?

Wenn es dir nur darum geht, es auf mehreren Architekturen gleich zu 
bekommen, kannst du das Padding auch manuell machen, indem du 
Dummyelemente einfügst, sodaß alle größeren Alemente auf glatte Adressen 
kommen.

So oder so ist es aber nicht wirklich portabel, sich auf die Auslegung 
einer struct zu verlassen.

von Klaus W. (mfgkw)


Lesenswert?

Klaus W. schrieb:
> Vielleicht ist das alles ja auch nur Fehlalarm und das fehlende Byte
> wird ganz am Ende nach dem uint8_t dazugedichtet, damit man in einem
> Array aufeinanderfolgende Elemente so eines structs ablegen kann.

Mr Bean schrieb:
> Trotzdem kommt irgendwie ein Byte hinzu.

Es geht mir immer noch um dein einsames Byte am Ende:

Wenn ich den folgenden Quelltext auf mit gcc auf einem i386 kompiliere
1
  struct st
2
  {
3
    int i;
4
    char c;
5
  };
6
7
  std::printf( "%u\n", (unsigned)sizeof(struct st) );
, dann bekomme ich 8 als Größe raus.

Das heißt, es wird auch am Ende noch gepadded.

Ist auch logisch, weil man ja mit z.B. malloc(5*sizeof(struct st)) ein 
Feld allokieren kann, bei dem auch im zweiten oder dritten Feldelement 
dessen jeweils erstes struct-Element auf einer für int geeigneten 
Adresse liegen muss.
D.h. sobald in einer struct etwas größeres als nur Bytes liegt, wird am 
Ende auch auf glatte Adressen aufgefüllt (/2 oder /4 teilbar, je nach 
System).

von Le X. (lex_91)


Lesenswert?

@ TO:

Ist es denn wichtig, dass die Strukturen auf dem Target und auf x86 das 
gleiche Layout haben?
Willst du irgendwie raw-Daten austauschen und bist darauf angewiesen 
dass das Layout passt?

Ansonsten würde ich das Padding einfach in Kauf nehmen und das "packed" 
weglassen.

Ich organisiere meinen EEPROM auch als Struktur und greife per Makro 
darauf zu. Das Makro liefert mir den Offset eines Members und der 
wiederum dient als Adresse für mein Eep_WriteByte(...).

Wenn sich das Layout also ändert dann spielt das keine Rolle.
Allerdings sind Datensätze so nicht zwischen verschiedenen 
Firmwareversionen kompatibel, weswegen meine Software eine 
Standard-Bedatung mitbringt die nach dem Flashen erstmal zum Einsatz 
kommt.

von Clemens L. (c_l)


Lesenswert?

Mr Bean schrieb:
> Bei Country_t (dem Typ vor locations), handelt es sich um einen
> typdef enum.

Die Größe eines Enum-Typs ist extrem Compiler-abhängig.

Du solltest für das Speichern im EEPROM besser ein Typ mit bekannter 
Größe verwenden (uint8_t, oder was halt benötigt wird).

von Matthias L. (Gast)


Lesenswert?

> Hm, umsortieren möchte ich nur ungern, dann muss ich die Datenstruktur
> ja auch auf der Zielplatform ändern...
> Gibt es keine andere Möglichkeit, dieses Problem zu lösen?

Machst Du etwa memcpy der Struktur in den Sendepuffer einer 
Netzwerkverbindung? (Schreiend wegrenn...)

von Mr Bean (Gast)


Lesenswert?

Also:

Vielen Dank erstmal an alle für eure Hilfe. Ich habe das Problem jetzt 
so gelöst, dass ich "filler" bytes in die Struktur einbaue, um das 
alignment Problem zu lösen.

Die Struktur muss auf arch x86 gleich sein, wie auf der Zielhardware, da 
ich mit dem PC Programm ein EEPROM image erstelle, das ich dann wieder 
in die Zielhardware einspiele.

Wenn dann die Adressen nicht passen, habe ich Datensalat auf meiner 
Zielhardware.

Und das Erstellen des image, ist auf einem x86 einfach wesentlich 
schneller, als auf der Zielhardware. Außerdem kann ich den Inhalt so 
direkt in eine .bin Datei schreiben (auf der Zielhardware nicht 
möglich).

Gruß,
Bean

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.