Hallo Hab eine kleine Anfängerfrage. Bei welcher Art von Variablen muss man volatile als Zusatz verwenden?
Den Typqualifizierer volatile verwendet man, wenn eine globale Variable auch in Interrupt-Handlern geändert werden soll.
Interessante Frage! Was passiert wenn 'volatile' nicht angegeben wird.
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
Aha, Danke! Dann muss ich schnell noch einige 'volatils' nachtragen. Schönen Gruß Dante
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.
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.
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?
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
Sollte heißen: Verwendet man eine Variable nur in einer Interruptfunktion, dann braucht man sie nicht volatile machen.
Nachteile gibts aber keine wenn ich zu viele volatiles mache, oder? Abgesehn von der Geschwindigkeit.
> 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 ^^
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.
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.
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
Die Antwort hätte auch 42 sein können ;) Mario fragt: A oder B und die Antwort lautet ja ;)
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.
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
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
Was passiert, wenn der Compiler entscheidet die kurze Funktion get_ticks() als Optimierung zu inlinen?
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.
@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
<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.
> "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.
> 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.
"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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.