Hi, ich bin nun von der 8051- in die ARM-Welt eingestiegen und verwende NxP Cortex-M0/M3 in Verbindung mit der LPCxpresso IDE von CodeRed und GCC. IDE läuft, Debugging geht, soweit alles im Grünen. Ich versuche nun im Selbststudium mit den Controllern fit zu werden, was auch klappt. Der UART läuft schon, der ADC wird grad zum Leben erweckt :) Wo ich nun eher Anlaufschwierigkeiten habe ist der GCC, beispielsweise wie die Variablen bzgl. Speichermanagement gehandhabt werden, wie Parameterübergabe stattfindet, etc. Ich komm leider momentan mit der in der LPCxpresso-IDE mitgelieferten GCC-Hilfe (noch) nicht zurecht, die Suchwörter spucken alles mögliche aus, nur aus meiner Sicht absolut nicht das was ich wissen will :( Ein konkretes Beispiel: Ich hab die 8051er-Programme u.a. mit dem Keil C51 Compiler erstellt. - Das Alignment spielte da keine Rolle, weil es ein 8-Bit-Controller ist. Wie ist das beim GCC & ARM? Wenn ich vier 8-Bit-Variablen (char) anlege, packt der GCC das automatisch in ein 32-Bit-Wort, oder verwendet er vier Mal ein 32-Bit-Wort? Wie kann ich das selber prüfen? Einfach mal ein paar Variablen anlegen und nach dem Build bei der Speicherangabe prüfen wieviel Bytes verbraten wurden? Ist das "aussagekräftig"? Was ich bis jetzt in der Hilfe zum GCC rausgefunden habe, ist dass es beispielsweise beim Anlegen von structs nicht gepackt wird, d.h. im Speicher sind Lücken, wenn die struct nicht ein Vielfaches von 32-Bit verwendet. Das Packen kann durch ein Attribut bei der struct-Definition aktiviert werden. - Der Keil C51-Compiler bzw. der Linker kann funktionsinterne Variablen überlagern, sodass Speicherplatz gespart wird. Allerdings geht das nur auf Sourcedatei-Ebene, d.h. die optimale Speicherauslastung hat man nur erreicht, wenn jede Funktion in einer eigenen Sourcedatei steht. Wie funktioniert dieser Mechanismus beim GCC? - Beim Keil C51-Compiler wurden aufgrund der 8051-Architektur die Parameter in den Registern übergeben, auch hier war die Reihenfolge der Parameter bei der Übergabe wichtig, um optimalen Code zu erzeugen, da ansonsten zusätzlicher RAM für die Parameterübergabe verwendet wurde. Ich vermute beim GCC werden die Parameter über einen Stack übergeben, richtig? Wie lässt sich hier der Code optimieren? Wenn ich beispielsweise für eine Funktion vier Parameter habe, für die der Typ char ausreichend ist, werden dann vier 32-Bit-Wörter auf den Stack geschoben oder wird's gepackt? Bzw. wenn's nicht gepackt wird, wäre doch die Verwendung eines structs als Parameter geschickter, oder? Ich versuche eben, meine Erfahrung mit dem 8051/Keil auf den ARM/GCC zu "portieren", um die Lernkurve bzgl. des Compilers zu optimieren :) Wenn also jemand nützliche Links hat, bitte hier posten. Ich werde das gleiche tun, vielleicht ergibt sich ja eine ordentliche Linksammlung. Ralf
Ralf schrieb: > ist. Wie ist das beim GCC & ARM? Wenn ich vier 8-Bit-Variablen (char) > anlege, packt der GCC das automatisch in ein 32-Bit-Wort, oder verwendet > er vier Mal ein 32-Bit-Wort? Register: jede einzeln in ein Register. Deklaration lokaler Variablen mit weniger als 32 Bits ist ineffizient. Speicher: in 8 Bits gespeichert. > Wie kann ich das selber prüfen? Code ansehen, Adressen von Variablen im Dump suchen oder ggf.ausgeben. > Was ich bis jetzt in der Hilfe zum GCC rausgefunden habe, ist dass es > beispielsweise beim Anlegen von structs nicht gepackt wird, d.h. im > Speicher sind Lücken, Es wird nicht gepackt, es sei denn das wird ausdrücklich gewünscht. Gepackt ist recht ineffizient. > - Der Keil C51-Compiler bzw. der Linker kann funktionsinterne Variablen > überlagern, sodass Speicherplatz gespart wird. 51er speichern lokale Daten statisch im RAM, da sie mit einem Stack für Daten schlecht umgehen können. ARMs verwenden einen Stack und damit löst sich das Problem in Luft auf. > Ich vermute beim GCC werden die Parameter über einen Stack übergeben, Nein, Register. Nur wenns zu viele sind, was seltten ist.
A. K. schrieb: >> ist. Wie ist das beim GCC & ARM? Wenn ich vier 8-Bit-Variablen (char) >> anlege, packt der GCC das automatisch in ein 32-Bit-Wort, oder verwendet >> er vier Mal ein 32-Bit-Wort? > > Register: jede einzeln in ein Register. Deklaration lokaler Variablen > mit weniger als 32 Bits ist ineffizient. das is so nicht richtig. Die Register sind immer 32 bit, es macht aber keinen unterschied, da man die Registeroperationen auch auf Bytes anwenden kann Die erste Variable wir immer mit dword aligment angelegt.
1 | struct { |
2 | uint8_t a[3], |
3 | uint32_t b[1] |
4 | } c; |
in diesem Beispiel hat c eine größe von 8, weil eine Lücke zwischen a und b ist. packed sollte man nur machen wenn man es wirklich braucht, z.B. bei Frames die über einen Bus kamen. Lieber beim erstellen selbst darauf auchten das keine Lücken entstehen.
Hi zusammen, Zum Attribut "packed" hätte ich ne Frage struct{ uint8_t a; uint8_t b; uint8_t c; uint8_t d; } foo; ist diese Struct ohne packed auch nur 4 Byte lang oder 16? Also packt der GCC Variablen die in ein Dword passen zusammen rein und fängt erst beim Nächsten an, wenn er eine Var nicht mehr in das erste Dword bekommt? MfG Tec
4 byte. Hier muss die CPU sowieso einen byte weisen zugriff machen.
Danke dachte ich brauche auch hier für packed.
@Prx & nicht Gast: Danke für die Erklärungen. >> Es wird nicht gepackt, es sei denn das wird ausdrücklich gewünscht. >> Gepackt ist recht ineffizient. > in diesem Beispiel hat c eine größe von 8, weil eine Lücke zwischen a > und b ist. Passt jetzt aber nicht zur der Antwort auf tecnologics Frage: >> ist diese Struct ohne packed auch nur 4 Byte lang oder 16? > 4 byte. Hier muss die CPU sowieso einen byte weisen zugriff machen. Oder ist im Beispiel von nicht Gast die Lücke in der struct weil der uint32_t Member "geschickterweise" auf ein Word-Boundary gelegt wird? Ralf
HI Ralf, so verstehe ich das auch, der uin32_t passt nicht mehr in das Dword mit den 3 uint8_t deshalb beginnt der Compiler ein neues Dword, so erreicht er ja 32bit aligned Zugriff, und muss sich die Var nicht aus 2 Blöcken zusammen suchen. Darauf wollte ich ja auch hinnaus, weil ich mir da nie sicher war. hab das dann immer in der Form gemacht. struct{ uint32_t bFlag:1; uint32_t reseve:31; } foo __attribute_((_packed_)); so ist der Compiler gezwungen. ich werde das aber noch mal ohne Packed probieren. MfG Tec
nicht Gast schrieb: > das is so nicht richtig. Die Register sind immer 32 bit, es macht aber > keinen unterschied, da man die Registeroperationen auch auf Bytes > anwenden kann Das ist so nicht richtig. Erstens kann man nur Lade/Speicheoperationen auf 8/16 Bits anwenden, nicht aber ALU Operationen. Zweitens stehen (deshalb) bestimmte Compiler-Konventionen im Weg.
1 | short reg16(short a, short b) |
2 | {
|
3 | short x = a + b; |
4 | return x; |
5 | }
|
6 | int reg32(int a, int b) |
7 | {
|
8 | int x = a + b; |
9 | return x; |
10 | }
|
wird zu
1 | reg16: add r0, r1, r0 |
2 | mov r0, r0, asl #16 |
3 | mov r0, r0, asr #16 |
4 | bx lr |
5 | reg32: add r0, r1, r0 |
6 | bx lr |
A. K. schrieb: > 51er speichern lokale Daten statisch im RAM, da sie mit einem Stack für > Daten schlecht umgehen können. Nö, der 8051 kann auch mit dem Stack arbeiten. Allerdings ist es erheblich effizienter, direkt auf den SRAM zuzugreifen. Es muß kein Stackframe angelegt werden und viele Befehle können im SRAM gemacht werden (MOV, INC, DEC, DJNZ, ANL, XRL, ..). Außerdem wird selten ein RTOS verwendet, daher lassen sich lokale Variablen überlagern. A. K. schrieb: > ARMs verwenden einen Stack und damit löst > sich das Problem in Luft auf. Sie können nichts direkt im SRAM ausführen (RISC), daher ist es fast egal, ob aus dem SRAM laden oder PUSH/POP. Peter
Peter Dannegger schrieb: > Nö, der 8051 kann auch mit dem Stack arbeiten. > Allerdings ist es erheblich effizienter, direkt auf den SRAM > zuzugreifen. Ich habe nichts anderes behauptet, mit schlecht=ineffizient.
Peter Dannegger schrieb: >> ARMs verwenden einen Stack und damit löst >> sich das Problem in Luft auf. > > Sie können nichts direkt im SRAM ausführen (RISC), daher ist es fast > egal, ob aus dem SRAM laden oder PUSH/POP. Es ging hier um die Platzfrage. Wenn lokale Variablen statisch angelegt werden, dann ist es aus Platzgründen sinnvoll, dass der Compiler etwas Aufwand treibt, um solche Variablen von sich nicht gegenseitig aufrufenden Funktionen überlagern zu können. Wenn lokale Variablen in Registern bzw. auf einem Stack angelegt werden, dann löst sich diese Platzfrage ohne jede Optimierung ganz von allein, da diese Überlagerung eine implizite Eigenschaft des Stacks ist.
Hier eine App Note von TI zu den Cortex M3 von TI: Optimizing Code Performance and Size for Stellaris® Microcontrollers Das dürfte auch für deine NXP gelten. Gruss
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.