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
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.
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.
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.
>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...
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.
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
structblank_struct_t
2
{
3
uint8_teins;
4
uint16_tzwei;
5
uint8_tdrei;
6
}__attribute__((__packed__));
weil ja sonst durch Compilerschalter das Verhalten beeinflusst werden
würde.
Grüße, Adib.
--
>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.
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.
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
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
typedefstruct
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
._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
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.
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
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
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
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
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
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.
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
structst
2
{
3
inti;
4
charc;
5
};
6
7
std::printf("%u\n",(unsigned)sizeof(structst));
, 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).
@ 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.
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).
> 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...)
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