Forum: Mikrocontroller und Digitale Elektronik Bei welchen Variablen sollte man volatile verwenden


von Philipp Mayr (Gast)


Lesenswert?

Hallo
Hab eine kleine Anfängerfrage.
Bei welcher Art von Variablen muss man volatile als Zusatz verwenden?

von johnny.m (Gast)


Lesenswert?

Den Typqualifizierer volatile verwendet man, wenn eine globale Variable
auch in Interrupt-Handlern geändert werden soll.

von Philipp Mayr (Gast)


Lesenswert?

Danke sehr

von Dante (Gast)


Lesenswert?

Interessante Frage!


Was passiert wenn 'volatile' nicht angegeben wird.

von Wiesi (Gast)


Lesenswert?

Dann kann es sein, dass der Compiler auf der Variablen Optimierungen
durchführt, sie z.B. in einem Register für mehrfache Verwendung
zwischenspeichert. Daher ist der Code dann schneller und oft auch
kleiner.

Aber: Wenn die Variable z.B. in einer Timer-Interruptroutine geändert
wird, dann bekommt das Huptprogramm nicht von der Änderung mit, weil es
ja nicht die RAM-Zelle sondern das Zwischenspeicherregister liest, das
sich aber nicht ändert.

Folgender Code in main produziert dann eine Endlosschleife, wenn ticks
nicht volatile ist:

while(ticks < 50)
   ;

(Vorausgesetzt, dass ticks von einem Timer-ISR inkrementiert wird.)


    Wiesi

von Dante (Gast)


Lesenswert?

Aha, Danke!

Dann muss ich schnell noch einige 'volatils' nachtragen.




Schönen Gruß   Dante

von M&M's (Gast)


Lesenswert?

Noch mal kurz zum Durchüberlegen:

Man muss also bei jeder Variable die in einer ISR vorkommt ein volatile
davor setzen.

Also z.B.

unsigned char xx=0;


Timer ISR:
xx=5;

void main (void)
{
bb=xx;
}

kommt dann 0 für bb heraus, da ich ja kein volatile verwendet hab.

von johnny.m (Gast)


Lesenswert?

Es muss gar nichts passieren. Es hängt davon ab, wie viele Variablen im
Programm verwendet werden und was der Compiler daraus macht. Ein
durchschnittlicher Compiler versucht, so viele Variablen wie möglich im
Registersatz abzulegen, um die Geschwindigkeit zu optimieren. Da aber
beim Einsprung in einen Interrupt Handler (ISR) die verwendeten
Register gesichert und hinterher wieder neu geladen werden, kann ein
Zugriffsversuch aus einer ISR ohne Ergebnis bleiben. 'volatile' tut
eigentlich nicht viel anderes als dem Compiler mitzuteilen, dass er die
entsprechende Variable nicht im Registersatz sondern im SRAM ablegen
soll, was natürlich auf Kosten der Geschwindigkeit geht, was aber bei
den paar Zugriffen in einem durchschnittlichen Programm zu verschmerzen
ist.

von M&M\'s (Gast)


Lesenswert?

Und wenn eine Variable nur in ISR's vorkommt.
Also z.B: in ADC-ISR und in Timer-ISR, aber nicht in Main
Muss man dann auch volatile machen?

von Markus K. (markus-)


Lesenswert?

Man braucht volatile immer dann, wenn die Variable außerhalb der
aktuellen Funktion zwischendurch geändert werden kann. Also wenn eine
Variable im Hauptprogramm von einem Interrupt unterbrochen wird, beim
Multitasking/Multithreading bzw. bei mehreren CPUs, usw. Aber auch bei
RAM das nicht nur vom Programm geändert wird, also z.B. einem
DMA-Buffer oder bei Memory-Mapped I/O.

Verwendet man eine Variable nur in einer Interruptfunktion, dann
braucht sie nicht volatile sind. Verwendet man sie in zwei
Interruptroutinen, die sich gegenseitig unterbrechen können, dann
schon.

Markus

von Markus K. (markus-)


Lesenswert?

Sollte heißen:
Verwendet man eine Variable nur in einer Interruptfunktion, dann
braucht man sie nicht volatile machen.

von M&M's (Gast)


Lesenswert?

Nachteile gibts aber keine wenn ich zu viele volatiles mache, oder?
Abgesehn von der Geschwindigkeit.

von struberg (Gast)


Lesenswert?

> Und wenn eine Variable nur in ISR's vorkommt.
> Muss man dann auch volatile machen?
Nein, natürlich nicht

volatile bringt es nur, wenn die Variable in einm 'multithreaded'
environment verwendet wird um zwischen den einzelnen prozessen Daten
auszutauschen/sharen.

Alle übrigen Variablen brauchen nicht volatile zu sein.

Aber ACHTUNG: volatile ist nur der erste Schritt! Um für eine IPC
(inter process communication) ein stabile data interaction zu bekommen
braucht man eventuell eine Art semaphore handling!
Auf diese kann nur dann verzichtet werden, wenn die Dateneinheit in
einem atomic step geschrieben und gelesen werden kann.

Beispiel:

In einem Programm eines AVR (bekanntlich ein 8-bitter) wir eine 4-Byte
langer Zähler ausgelesen.
Aktueller Stand: 0x 00 ff ff ff
Das Lesen beginnt mit dem LSB (least significant bit/byte).

Vor dem Lesen des letzen (höchsten) Bytes wird der Zählerinterrupt
ausgelöst. (aktueller Stand im Programm: 0x .. ff ff ff

Die ISR (interrupt service routine) inkrementiert den 4-Byte langen
Zähler auf den neuen Wert 0x 01 00 00 00
Danach wird die ISR mit iret beendet.

Das Programm macht also genau an der Stelle weiter wo es aufgehört:
Beim Lesen des höchsten Byte, wo nun aber nicht mehr 0x00 sondern 0x01
drinnen steht.

Das Programm liest also in Summe den Wert 0x 01 ff ff ff ( die 3
niederen Bytes waren ja schon eingelesen)!

Dss ist ein Fehlbetrag von 16,777.215
Wären das die Sekunden einer Uhr, würden wir uns um 4660 Stunden also
um ganze 194 Tage verrechnet haben ^^

von Markus K. (markus-)


Lesenswert?

Genau, die Geschwindigkeit ist der einzige Nachteil.

Aber das Problem sollte doch sowieso nur bei globalen Variablen
auftreten, d.h. es bringt nichts, wenn Du jetzt einfach alle Variablen
volatile machst.

von Mark S. (struberg)


Lesenswert?

ad zu meinem obigen Posting:

Vermeiden kann man das ganze am einfachsten, indem man das Lesen des
Zählers 'atomic' macht, also die Interrupts in dieser Zeit disabled.

Das sollte jedoch immer nur sehr kurze Zeit gemacht werden, da man
sonst Zeitverzerrungen in der ISR bekommen kann.

von Mario (Gast)


Lesenswert?

Hallo!

Ich habe dazu auch noch eine Frage.
Sollte man dieses Volatile auch dann benutzen, wenn es sich um globale
Varaiblen handelt und diese in verschiedenen Funktionen verändert
werden oder ist die volatile-Angabe nur dann notwendig wenn die
Variable in der IRQ-Routine und im Hauptprogramm verändert wird?
Danke.

Gruß, Mario

von Rahul (Gast)


Lesenswert?

ja

von Daniel M. (usul27)


Lesenswert?

Die Antwort hätte auch 42 sein können ;)

Mario fragt: A oder B und die Antwort lautet ja ;)

von Johnny (Gast)


Lesenswert?

Globale Variabeln, welche NICHT in IRQ's verwendet werden, benötigen
kein Volatile.

Volatile ist auch nötig, wenn man von einem IO Port was einliest. Der
Compiler weiss ja nicht, dass sich der Portzustant von Aussen her
ändern kann. Darum muss man das per Volatile bekannt geben. Ansonsten
kann es vorkommen, dass diese Variable ganz wegoptimiert wird vom
Compiler.

von Peter D. (peda)


Lesenswert?

Hier mal ein Beispiel:
1
unsigned long timerticks; // Interrupt zählt diese hoch
2
3
void test(void)
4
{
5
  while( timerticks <= 123456L); // Warte bis Zeit vergangen
6
}

geht schief aus 2 Gründen:

1. Lesen nicht atomar

2. Variable nicht volatile


Aber so gehts einwandfrei:
1
unsigned long timerticks; // Interrupt zählt diese hoch
2
3
unsigned long get_ticks( void )
4
{
5
  unsigned long ticks;
6
  cli();
7
  ticks = timerticks;
8
  sei();
9
  return ticks;
10
}
11
12
void test(void)
13
{
14
  while( get_ticks() <= 123456L); // Warte bis Zeit vergangen
15
}

Nun die Preisfrage:

Warum geht das ohne volatile ?


Peter

von Peter D. (peda)


Lesenswert?

Na gut, wenn keiner den Preis haben will:

Es geht deshalb ohne volatile, weil der erste Zugriff innerhalb einer
Funktion immer erfolgen muß.

Und die Auslesefunktion greift ja nur einmal zu, d.h. da gibt es
nichts, was der Compiler wegoptimieren kann.

Und einen Funktionsaufruf darf der Compiler nicht aus der
while-Schleife rausziehen.
Funktionsaufrufe sind per se immer volatile.


Peter

von Roland Schmidt (Gast)


Lesenswert?

Was passiert, wenn der Compiler entscheidet die
kurze Funktion get_ticks() als Optimierung zu inlinen?

von Johnny (Gast)


Lesenswert?

Also wenn ich weiss dass es Probleme mit einer Variabeln geben kann,
dann schreib ich lieber mal ein volatile zuviel hin als zu wenig...

Die Suche nach solchen Fehlern kann ziemlich lange dauern. Und schnell
ist mal was am Code geändert und dann funktioniert dieser plötzlich
nicht mehr ohne ersichtlichen Grund.

von Peter D. (peda)


Lesenswert?

@Roland

"Was passiert, wenn der Compiler entscheidet die kurze Funktion
get_ticks() als Optimierung zu inlinen?"


Dann ist natürlich alles forn Arsch.

Deshalb schalte ich diese Optimierung nie ein.

Inlining hat ja noch viele andere unerwünschte Seiteneffekte.


Es verwundert mich immer wieder, warum die Compilerbauer
Inlinefunktionen nicht genau so behandeln können, wie echte Funktionen.
Man braucht doch nur das CALL+RET weglassen.


Peter

von Bodo Borland (Gast)


Lesenswert?

<i>Es verwundert mich immer wieder, warum die Compilerbauer
Inlinefunktionen nicht genau so behandeln können, wie echte
Funktionen.
Man braucht doch nur das CALL+RET weglassen.</i>

So einfach ists nicht. Veneers waeren z.B. überflüssig und evtl. sogar
gefährlich. Mag ein, dass diese sehr vereinfachte Betrachtungsweise auf
einem Primitiv-Prozessor wie dem AVR sogar funktioniert. Aber es gibt
genug Fälle, da ist eine Funktion mehr als ein bisschen Assembly mit
Call und Return. Alleine die Registernutzung ist beim Inlining meist
dramtisch anders.

Funktionsaufrufe sind ausserdem nicht grundsätzlich volatile.

von Rolf Magnus (Gast)


Lesenswert?

> "Was passiert, wenn der Compiler entscheidet die kurze Funktion
> get_ticks() als Optimierung zu inlinen?"
>
>
> Dann ist natürlich alles forn Arsch.

Naja, es ist eher so, daß das fälschliche Fehlen von volatile nun einen
Effekt zeigt.

> Deshalb schalte ich diese Optimierung nie ein.

Seltsame Denkweise. Außerdem könntest du die Funktion auch in einer
anderen Übersetzungseinheit definieren. Damit unterbindest du das
Inlining auch.

> Inlining hat ja noch viele andere unerwünschte Seiteneffekte.

Ja? Es hat meines Erachtens jede Menge erwünschter Effekte.

> Es verwundert mich immer wieder, warum die Compilerbauer
> Inlinefunktionen nicht genau so behandeln können, wie echte
> Funktionen. Man braucht doch nur das CALL+RET weglassen.

Dann kann man sich das inlining fast schenken. Richtig effektiv wird
das erst dadurch, daß noch jede Menge mehr optimiert werden kann.

von Unbekannter (Gast)


Lesenswert?

> Es geht deshalb ohne volatile, weil der erste Zugriff innerhalb
> einer Funktion immer erfolgen muß.

Nein. Das ist natürlich falsch. Ein Beispiel wurde schon genannt,
Inlining.

Gerne lasse ich mich vom Gegenteil überzeugen. Dazu bitte die
entsprechende Stelle aus dem C99-Iso-Standard zitieren.


> Es verwundert mich immer wieder, warum die Compilerbauer
> Inlinefunktionen nicht genau so behandeln können, wie echte
> Funktionen.

Nun, Compilerbauer implementieren den Standard. Wenn der C-Compiler vom
Standard abweicht, ist er defekt.

Wenn der Programmierer den Standard nicht liest bzw. versteht, und z.B.
"volatile" an entsprechenden Stellen weglässt, dann ist produziert der
Programmierer defekten Code.

von A.K. (Gast)


Lesenswert?

"Es verwundert mich immer wieder, warum die Compilerbauer
Inlinefunktionen nicht genau so behandeln können, wie echte
Funktionen."

C wurde zwar anfangs sehr hardwarenah definiert und benutzt, und die
Compiler dieser Ära waren dementsprechend einfach. Aber heute ist es
eine allgemeine Programmiersprache für recht hoch angesiedelte
Anwendungen (mag man das nun gut oder schlecht finden).

Nur: Das war nie offiziell so definiert. K&R-C konnte einzig deshalb
auf "volatile" verzichten, weil die Compiler dieser Ära kaum über
Statement-Grenzen hinaus optimierten. Manche heutigen Compiler
betrachten das gesamte Programm als Einheit und sind in den Grenzen der
jeweiligen C-Definition frei, alles nach belieben umzuordnen und auch
wegzulassen. Wobei C hier immer noch weit weniger Freiheitsgrade hat
als beispielsweise Fortran (z.B. Aliasing).

In Controller-Anwendungen hingegen sind globale Optimierungen weniger
wichtig, allein schon weil es keinen entsprechenden Benchmark gibt. Die
einzig wirklich wichtige funktionsübergreifende Optimierung analysiert
statische Variablen um RAM zu sparen, bei Maschinen die sich mit
Variablen auf dem Stack schwer tun. Im Gegenteil, Transparenz von
Quellcode zu Objektcode ist durchaus hilfreich, und wenn man den
eigenen Code im Assembler-Listung noch wiedererkennt, ist das kein
Nachteil.

Wird nun aber ein Compiler für SPECmark-optimierte Standardsysteme auf
Controller-Anwendungen losgelassen, kann es für den Anwender schon mal
einen kleiner Kulturschock geben, weil die gewohnte Transparenz beim
Teufel ist. Und nun vieles zum Problem wird, das bei einfacher
gehaltenen Compilern wunderbar funktioniert.

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.