Forum: Mikrocontroller und Digitale Elektronik initialisierung standard var int i


von Markus Z. (zelg)


Lesenswert?

Hallo zusammen

Ich schreibe am Code für ein AVR Atmel 328p (Arduino Projekt). 
Inzwischen habe ich zig funktionen in denen ich separat die variable
1
int i;
 initialisiere, definiere und für Loop-Schleifen brauche.

Wäre eine globale Definition nicht einfacher und sparender im RAM?
wie handhabt der Compailer meiner massen-definitionen in den Funktionen?

Beste Grüsse,
Markus

von Karl H. (kbuchegg)


Lesenswert?

markus zelg schrieb:
> Hallo zusammen
>
> Ich schreibe am Code für ein AVR Atmel 328p (Arduino Projekt).
> Inzwischen habe ich zig funktionen in denen ich separat die variable
>
1
int i;
 initialisiere, definiere und für Loop-Schleifen brauche.
>
> Wäre eine globale Definition nicht einfacher und sparender im RAM?
> wie handhabt der Compailer meiner massen-definitionen in den Funktionen?

Die Variable wird erzeugt, und wenn sie aus dem Scope geht, wird sie 
wieder gekillt.
So aufwändig ist dieser Vorgang auch wieder nicht, dass du dir da jetzt 
groß Geddanken darüber machen musst.

Auf jeden Fall ist das viel besser, als wenn dir die einzelnen i-s, die 
in den Funktionen eigentlich unabhängig voneinanander sein solltem , 
sich gegenseiteig in die Quere kommen, weil sie
* alle auf dieselbe Variable führen
* eine Funktion (die dieses i benutzt) eine andere Funktion aufruft
  (die ebenfalls dieses i benutzt), welche wieder eine Funktion
  benutzt (die auch dieses i benutzt), sich die einzelnen Funktionen
  gegenseitig die Werte für i unter dem Allerwertesten ändern und
  alles den Bach runter geht.

von Rolf Magnus (Gast)


Lesenswert?

markus zelg schrieb:
> Wäre eine globale Definition nicht einfacher und sparender im RAM?

Nein.

> wie handhabt der Compailer meiner massen-definitionen in den Funktionen?

Er reserviert sich innerhalb der Funktion ein Register für den 
Schleifenzähler, bzw. zwei, da int zwei Bytes groß ist.

von Sam P. (Gast)


Lesenswert?

Eine globale Variable würde bedeuten, dass keine deiner Funktionen eine 
andere aufrufen darf. Sie würden sich nämlich gegenseitig das i 
kaputtmachen.

Lokale Variablen landen auf dem Stack, d.h. der Speicher ist nur so 
lange reserviert, wie eine Funktion gerade ausgeführt wird.

von Ralf G. (ralg)


Lesenswert?

Sam P. schrieb:
> Lokale Variablen landen auf dem Stack, d.h. der Speicher ist nur so
> lange reserviert, wie eine Funktion gerade ausgeführt wird.
Wenn überhaupt!
Denn:
Rolf Magnus schrieb:
> Er reserviert sich innerhalb der Funktion ein Register für den
> Schleifenzähler

von Vlad T. (vlad_tepesch)


Lesenswert?

optimiert der Compiler eigentlich auch die Datentypen?
dH benutzt er statt dem deklariertem int ein int8, wenn die schleife mit 
Konstanten definiert ist?

von Maxx (Gast)


Lesenswert?

Vlad Tepesch schrieb:
> optimiert der Compiler eigentlich auch die Datentypen?
> dH benutzt er statt dem deklariertem int ein int8, wenn die schleife mit
> Konstanten definiert ist?

Er kann und wird sie in entsprechender Optimierungseinstellung sogar 
ganz entfernen. (Loop Unrolling)

Der Datentyp kann selten darüber hinaus wirklich geändert werden. 
Bedenke, dass Zuweisungen und Manipulationen von/mit dem Schleifenzähler 
sich mit unterschiedlichen Typen unterscheidlich verhalten und erstmal 
nicht identisch sind.

von Bronco (Gast)


Lesenswert?

markus zelg schrieb:
> Wäre eine globale Definition nicht einfacher und sparender im RAM?

Der AVR arbeitet am einfachsten und schnellsten mit seinen Registern. 
Ein RAM-Zugriff erfordert mehr Aufwand (mehr Code) wegen der 
Load/Store-Architektur.
D.h. wenn eine Funktion alle ihre lokalen Variablen in Registern halten 
kann, hast Du den optimalen Code.

Ganz abgesehen davon: Beim modularen Programmieren soll jede Funktion 
ihre privaten Daten (wie lokale Variablen) gegenüber dem Rest des 
Systems kapseln, damit niemand von außen Schabernack damit treiben kann.

von Vlad T. (vlad_tepesch)


Lesenswert?

Maxx schrieb:
> Er kann und wird sie in entsprechender Optimierungseinstellung sogar
> ganz entfernen. (Loop Unrolling)

bei -Os unwahrscheinlich, zumindest für größere n

von Peter D. (peda)


Lesenswert?

Vlad Tepesch schrieb:
> dH benutzt er statt dem deklariertem int ein int8, wenn die schleife mit
> Konstanten definiert ist?

Nö.
Er jongliert dann auf nem 8Bitter fleißig mit den unnötigen höheren 
Bytes rum, d.h. addiert 0, vergleicht mit 0 usw.

Der AVR-GCC macht allerdings gerne mal das Gegenteil, d.h. er bläht bei 
bestimmten Operationen die 8Bit unnötig auf 16Bit auf.


Ein globales uint8_t i ist sehr teuer. Der Compiler holt es immer aus 
dem SRAM in ein Register, macht was und schreibt es wieder zurück:

Load + Operation + Store = 5 Zyklen, 10 Byte Code, 1 Byte SRAM

Ein lokales i kann er dagegen oft in einem Register halten, d.h. es 
belegt keinen SRAM und das ständige Load/Store entfällt:

Operation = 1 Zyklus, 2 Byte Code, 0 Byte SRAM


Peter

von Rolf Magnus (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Nö.
> Er jongliert dann auf nem 8Bitter fleißig mit den unnötigen höheren
> Bytes rum, d.h. addiert 0, vergleicht mit 0 usw.
>
> Der AVR-GCC macht allerdings gerne mal das Gegenteil, d.h. er bläht bei
> bestimmten Operationen die 8Bit unnötig auf 16Bit auf.

Eigentlich macht er da genau das gleiche. Er erweitert halt erstmal 
alles auf int, wie C es vorschreibt, optimiert aber eben nicht überall, 
wo es möglich wäre, diese Erweiterung wieder weg.

> Ein globales uint8_t i ist sehr teuer. Der Compiler holt es immer aus
> dem SRAM in ein Register, macht was und schreibt es wieder zurück:

Nur wenn es volatile ist. Sonst optimiert er die Zugriffe bei 
Zwischenwerten, wie sie in einer Schleife beim Zähler ja recht häufig 
vorkommen, durchaus auch weg. Ich würde mal schätzen, daß letztendlich 
nur ein einzelner Schreibzugriff nach der Schleife mit dem Endwert des 
Schleifenzählers durchgeführt wird.

von Peter D. (peda)


Lesenswert?

1
uint8_t ig;
2
3
void iglobal()
4
{
5
  for( ig = 8; ig; ig-- ){
6
  90:   88 e0           ldi     r24, 0x08       ; 8
7
  92:   80 93 00 01     sts     0x0100, r24
8
  96:   09 c0           rjmp    .+18            ; 0xaa <iglobal+0x1a>
9
    PORTB = ig;
10
  98:   85 b9           out     0x05, r24       ; 5
11
    if( PIND & 1 )
12
      return;
13
  9a:   98 2f           mov     r25, r24
14
  9c:   91 50           subi    r25, 0x01       ; 1
15
  9e:   48 9b           sbis    0x09, 0 ; 9
16
  a0:   03 c0           rjmp    .+6             ; 0xa8 <iglobal+0x18>
17
  a2:   80 93 00 01     sts     0x0100, r24
18
  a6:   08 95           ret
19
  a8:   89 2f           mov     r24, r25
20
21
  aa:   88 23           and     r24, r24
22
  ac:   a9 f7           brne    .-22            ; 0x98 <iglobal+0x8>
23
  ae:   10 92 00 01     sts     0x0100, r1
24
  b2:   08 95           ret
25
  }
26
}
= 36 Byte
1
void ilocal()
2
{
3
  b4:   88 e0           ldi     r24, 0x08       ; 8
4
  uint8_t il;
5
6
  for( il = 8; il; il-- ){
7
    PORTB = il;
8
  b6:   85 b9           out     0x05, r24       ; 5
9
    if( PIND & 1 )
10
      return;
11
  b8:   48 99           sbic    0x09, 0 ; 9
12
  ba:   02 c0           rjmp    .+4             ; 0xc0 <ilocal+0xc>
13
14
  bc:   81 50           subi    r24, 0x01       ; 1
15
  be:   d9 f7           brne    .-10            ; 0xb6 <ilocal+0x2>
16
  c0:   08 95           ret
17
  }
18
}
= 14 Byte

Peter

von Rolf Magnus (Gast)


Lesenswert?

Das passiert aber auch nur, weil du innerhalb der Schleife abhängig von 
einer anderen Bedinung ein return machst. Dann macht der Compiler bei 
jedem Durchlauf einen Schreibzugriff, weil die Funktion mittendrin 
verlassen werden könnte. Lesezugriff macht er aber auch hier entgegen 
deiner Behauptung keinen einzigen.

von Peter D. (peda)


Lesenswert?

Ich bin selber erstaunt, wie sehr der Optimierer durch die globale 
Schleifenvariable aus dem Tritt kommt.
Es hätten eigentlich nur 4 Bytes mehr sein dürfen, aber nicht 22.


Peter

von Markus Zelg (Gast)


Lesenswert?

Hallo

Besten Dank für die vielen Beiträge.
Werde auf lokale Initialisierung bleiben.

Rolf Magnus schrieb:
> Eigentlich macht er da genau das gleiche. Er erweitert halt erstmal
> alles auf int, wie C es vorschreibt, optimiert aber eben nicht überall,
> wo es möglich wäre, diese Erweiterung wieder weg.

Ja, erst neulich habe ich alle for(int i=0) auf byte i=0 gesetzt und 
siehe da, schon sind wesentlich viel ROM Flash frei geworden. Was der 
freeMemory() meldet habe ich noch nicht angeschaut.

von Maxx (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Das passiert aber auch nur, weil du innerhalb der Schleife abhängig von
> einer anderen Bedinung ein return machst. Dann macht der Compiler bei
> jedem Durchlauf einen Schreibzugriff, weil die Funktion mittendrin
> verlassen werden könnte. Lesezugriff macht er aber auch hier entgegen
> deiner Behauptung keinen einzigen.

Das wird in der Regel gelöst, indem das "Aufräumen am Ende einer 
Funktion gemeinsam genutzt wird. Das macht üblicher Weise auch GCC so 
für andere Plattformen. In dieser könnte und sollt er auch i sichern.

Das "Return" übersetzt er ja auch nicht mit "ret" sondern einem "rjmp" 
zum Aufräumen/Verlassen. Das Speichern in jedem Durchlauf ist also nicht 
benötigt.

von Peter D. (peda)


Lesenswert?

Rolf Magnus schrieb:
> Dann macht der Compiler bei
> jedem Durchlauf einen Schreibzugriff, weil die Funktion mittendrin
> verlassen werden könnte.

Nein, macht er nicht. Nur am Anfang und nach der 1. Abbruchbedingung 
oder nach der 2.
Aber er verliert völlig den Faden und und macht unnötige 
Zwischenschritte. Er benutzt sogar ein Register mehr.


Peter

von Rolf Magnus (Gast)


Lesenswert?

Maxx schrieb:
> Das "Return" übersetzt er ja auch nicht mit "ret" sondern einem "rjmp"
> zum Aufräumen/Verlassen

Nein, er übersetzt es durchaus mit einem ret. Das rjmp springt nicht zum 
Aufräumen, sondern es überspringt das ret.

Peter Dannegger schrieb:
> 9a:   98 2f           mov     r25, r24
>   9c:   91 50           subi    r25, 0x01       ; 1

Hier wird der Schleifenzähler dekrementiert.

>   9e:   48 9b           sbis    0x09, 0 ; 9
>   a0:   03 c0           rjmp    .+6             ; 0xa8 <iglobal+0x18>

Das ist die Prüfung von PIND. Das rjmp springt über's return, sofern 
PIND0 gesetzt ist.

>   a2:   80 93 00 01     sts     0x0100, r24

Speichern des Zählers.

>   a6:   08 95           ret

Und das return im if.

>   a8:   89 2f           mov     r24, r25
>
>   aa:   88 23           and     r24, r24
>   ac:   a9 f7           brne    .-22            ; 0x98 <iglobal+0x8>

Rücksprung zum Anfang der Schleife.

>   ae:   10 92 00 01     sts     0x0100, r1
>   b2:   08 95           ret

Und das ist das Aufräumen am Schluß.


Peter Dannegger schrieb:
> Nein, macht er nicht. Nur am Anfang und nach der 1. Abbruchbedingung
> oder nach der 2.

Stimmt.

> Aber er verliert völlig den Faden und und macht unnötige
> Zwischenschritte. Er benutzt sogar ein Register mehr.

Das macht er, weil er das Dekrementieren des Registers am Anfang macht, 
aber beim Abbruch in der Variable den Wert von vor dem Dekremeniteren 
speichern muß. Deshalb nimmt er r25 als temporäres Zwischenregister für 
den dekrementieren Wert. Das könnte sicher schöner gelöst werden, vor 
allem, weil er am Schluß dann noch ein zusätzliches "and" einfügt, nur 
um die Flags für's brne zu setzen. Würde er dort dekrementieren, dann 
könnte einiges wegfallen.

von Rolf Magnus (Gast)


Lesenswert?

Rolf Magnus schrieb:
>>   9e:   48 9b           sbis    0x09, 0 ; 9
>>   a0:   03 c0           rjmp    .+6             ; 0xa8 <iglobal+0x18>
>
> Das ist die Prüfung von PIND. Das rjmp springt über's return, sofern
> PIND0 gesetzt ist.

Grmpf... das hätte natürlich heißen sollen: "... sofern PIND0 nicht 
gesetzt ist.

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.