Forum: Compiler & IDEs volatile wirklich nötig?


von Philipp (Gast)


Lesenswert?

Hallo,

Ich habe ein externes ADC welches über einen Timer ausgelesen und der 
Wert in einem Buffer gespeichert wird.
Von Zeit zu Zeit lass ich den Buffer auslesen, das Ergebnis aus dem 
Buffer aufsummieren und bilde den Mittelwert.
1
struct adcBuffer
2
{
3
  int16 data[ 8 ][ 8 ];
4
  uint8 index;
5
};
6
7
...
8
cli( );
9
for( uint8 i = 0; i < 8; i++ )
10
{
11
  result += _buffer.data[ ch ][ i ];
12
}
13
sei( );
14
..

Eigentlich müsste es auch funktionieren wenn "data[]" im Buffer nicht 
volatile deklariert ist, da meiner Ansicht nach (bin kein Assembler 
spezialist) der ist, dass er den Z Pointer jedesmal neu beschreibt, 
anstatt ihn zu inkrementieren.

Ohne volatile:
1
ld  r24, Z+
2
ld  r25, Z+
3
add  r18, r24
4
adc  r19, r25
5
cp  r30, r20
6
cpc  r31, r21
7
brne  .-14

mitm volatile:
1
movw  r30, r22
2
add  r30, r20
3
adc  r31, r21
4
add  r30, r30
5
adc  r31, r31
6
subi  r30, 0xEF  ; 239
7
sbci  r31, 0xFD  ; 253
8
ldd  r24, Z+9  ; 0x09
9
ldd  r25, Z+10  ; 0x0a
10
add  r18, r24
11
adc  r19, r25
12
subi  r20, 0xFF  ; 255
13
sbci  r21, 0xFF  ; 255
14
cpi  r20, 0x08  ; 8
15
cpc  r21, r1
16
brne  .-32       ; 0x12d4

Wenn ich das richtig verstanden habe, welchen grund hat der Compiler 
hier, den Z Pointer jedesmal neu zu beschreiben?
Oder bin ich mit meinen Überlegungen komplett falsch?

grüße
Philipp

von Timmo H. (masterfx)


Lesenswert?


von Andreas B. (andreas_b77)


Lesenswert?

Philipp schrieb:
> Eigentlich müsste es auch funktionieren wenn "data[]" im Buffer nicht
> volatile deklariert ist, da meiner Ansicht nach (bin kein Assembler
> spezialist) der ist, dass er den Z Pointer jedesmal neu beschreibt,
> anstatt ihn zu inkrementieren.

Was der Compiler macht oder nicht kann sich mit Optionen und Versionen 
ändern. Anhand des erzeugten Assembler-Code darauf zu schließen, was man 
machen kann wird früher oder später in Tränen enden.

Hier braucht man kein volatile, solange auf adcBuffer wie oben 
zugegriffen wird, also nur im Interrupt und außerhalb nur in cli()/sei() 
geschützten Bereichen.

Im Interrupt braucht man es nicht, da es eine Funktion ist, die nur 
aufgerufen, durchgelaufen und beendet wird ohne dass es unterbrochen 
wird oder parallel etwas anderes läuft. Also genau so, wie es der 
Compiler annimmt.

Außerhalb braucht man das volatile nicht, weil durch das cli() auch 
nichts unterbricht und nichts parallel läuft. Ein Problem wäre, dass der 
Compiler Daten aus dem Array vor dem cli() oder nach dem sei() laden 
oder speichern könnte. Das passiert hier nicht, weil die cli() und sei() 
Makros zweckmäßigerweise als Speicherbarrieren definiert sind, d.h. der 
Compiler darf beim Optimieren niemals Speicherzugriffe über diese beiden 
Makro-Aufrufe hinweg verschieben.

Also wird hier kein volatile gebraucht. Auch wenn man es an einzelnen 
Stellen brauchen würde, muss man nicht gleich die ganze Variable 
volatile machen, man kann auch bei den entsprechenden Zugriffen die 
Variable nach volatile casten.

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


Lesenswert?

Andreas B. schrieb:
> Hier braucht man kein volatile, solange auf adcBuffer wie oben
> zugegriffen wird, also nur im Interrupt und außerhalb nur in cli()/sei()
> geschützten Bereichen.

Der Interruptschutz ist für volatile unerheblich.  Interessant ist,
ob der Compiler an der gegebenen Stelle noch implizite Annahmen
machen kann, welchen Wert _buffer tatsächlich hat bzw. ob er
schließen kann, dass das Beschreiben eigentlich für die Katz' ist
und daher auch weggelassen werden darf (weil sich an der Funktion
des sich ergebenden Codes aus seiner Sicht dann nichts ändert).

Mit dem Inkrementieren irgendwelcher Zeiger oder anderer
Implementierungsdetails hat das alles ohnehin nichts (direkt) zu
tun.  volatile ist dafür da, dem Compiler anzuzeigen, dass das
damit gekennzeichnete Objekt auf eine Weise geändert werden könnte,
die für ihn aus dem aktuellen Programmfluss nicht ersichtlich ist
(bspw. direkt durch die Hardware, wie bei IO-Registern, oder durch
andere Threads, wie eben in einer ISR).

Bezeichner, die mit Unterstrichen anfangen, sollte man (so man die
Regeln des C-Standards nicht gerade ganz genau kennt) tunlichst
vermeiden, da viele von ihnen "reserved for the implementation" sind.

von David .. (volatile)


Lesenswert?

:(

von Michael B. (mb_)


Lesenswert?

Andreas B. schrieb:
> Hier braucht man kein volatile, solange auf adcBuffer wie oben
> zugegriffen wird, also nur im Interrupt und außerhalb nur in cli()/sei()
> geschützten Bereichen.

> Ein Problem wäre, dass der
> Compiler Daten aus dem Array vor dem cli() oder nach dem sei() laden
> oder speichern könnte. Das passiert hier nicht, weil die cli() und sei()
> Makros zweckmäßigerweise als Speicherbarrieren definiert sind,

cli() und sei() sind nicht als memory barriers definiert:
1
mb@maggie:~$ egrep -e '(cli)|(sei)' /usr/lib/avr/include/avr/interrupt.h 
2
/** \def sei()
3
#define sei()
4
# define sei()  __asm__ __volatile__ ("sei" ::)
5
/** \def cli()
6
#define cli()
7
# define cli()  __asm__ __volatile__ ("cli" ::)

Der Compiler kann daher einen Variablenzugriff so verschieben, dass er 
nicht mehr innerhalb des IRQ-disable bereiches liegt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Michael Buesch schrieb:
> Andreas B. schrieb:
>> Makros zweckmäßigerweise als Speicherbarrieren definiert sind,
>
> cli() und sei() sind nicht als memory barriers definiert:
>
>
1
> mb@maggie:~$ egrep -e '(cli)|(sei)' /usr/lib/avr/include/avr/interrupt.h
2
> /** \def sei()
3
> #define sei()
4
> # define sei()  __asm__ __volatile__ ("sei" ::)
5
> /** \def cli()
6
> #define cli()
7
> # define cli()  __asm__ __volatile__ ("cli" ::)
8
>
>
> Der Compiler kann daher einen Variablenzugriff so verschieben, dass er
> nicht mehr innerhalb des IRQ-disable bereiches liegt.

Sagt wer? Ich halte mal dagegen mit avr-libc 1.7.1:
1
# 83 "./avr/include/avr/interrupt.h" 3
2
#define sei() __asm__ __volatile__ ("sei" ::: "memory")
3
# 103 "./avr/include/avr/interrupt.h" 3
4
#define cli() __asm__ __volatile__ ("cli" ::: "memory")

von Philipp (Gast)


Lesenswert?

vielen Dank Andreas und Jörg für eure detaillierten Antworten.

von Michael B. (mb_)


Lesenswert?

Johann L. schrieb:
>> cli() und sei() sind nicht als memory barriers definiert:
>>
>>
1
>> mb@maggie:~$ egrep -e '(cli)|(sei)' /usr/lib/avr/include/avr/interrupt.h
2
>> /** \def sei()
3
>> #define sei()
4
>> # define sei()  __asm__ __volatile__ ("sei" ::)
5
>> /** \def cli()
6
>> #define cli()
7
>> # define cli()  __asm__ __volatile__ ("cli" ::)
8
>>
>>
>> Der Compiler kann daher einen Variablenzugriff so verschieben, dass er
>> nicht mehr innerhalb des IRQ-disable bereiches liegt.
>
> Sagt wer?

ich?

> Ich halte mal dagegen mit avr-libc 1.7.1:
>
>
1
# 83 "./avr/include/avr/interrupt.h" 3
2
> #define sei() __asm__ __volatile__ ("sei" ::: "memory")
3
> # 103 "./avr/include/avr/interrupt.h" 3
4
> #define cli() __asm__ __volatile__ ("cli" ::: "memory")

Tja, wenn man portablen Code schreiben will, darf man sich eben nicht 
darauf verlassen. Aber schön zu sehen, dass dies in neueren avr-libc 
gefixt ist.

von (prx) A. K. (prx)


Lesenswert?

Michael Buesch schrieb:

> Tja, wenn man portablen Code schreiben will, darf man sich eben nicht
> darauf verlassen. Aber schön zu sehen, dass dies in neueren avr-libc
> gefixt ist.

Portabler Code mit direktem Aufruf von cli/sei. Aber klar doch.

von Michael B. (mb_)


Lesenswert?

A. K. schrieb:
> Michael Buesch schrieb:
>
>> Tja, wenn man portablen Code schreiben will, darf man sich eben nicht
>> darauf verlassen. Aber schön zu sehen, dass dies in neueren avr-libc
>> gefixt ist.
>
> Portabler Code mit direktem Aufruf von cli/sei. Aber klar doch.

Gehe nochmal zurück auf Los und lies worum es hier geht.

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.