mikrocontroller.net

Forum: Compiler & IDEs volatile qualifier und interrupts in C/C++


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von volatily (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hi,

ausgehend von single core Mikrocontrollern:
Muss man strikt alle Variablen die zwischen Kontexten geteilt werden mit 
volatile qualifizieren?

Z.B. Interrupt erzeugt Daten und legt sie in einen Ringpuffer. Hier habe 
ich schon oft gesehen, dass head und tail volatile sind aber das 
Datenarray nicht. Das wundert mich, scheint aber zu funktionieren.

Gibt es hier Ausnahmen, dass man das Array im Beispiel nicht volatil 
markieren muss?

Beispielcode:
volatile uint32_t head, tail;
char data[1];     // not volatile

main()
{
    while (head - tail != 0) {
        char rx = data[tail++];        // correct access to data guaranteed?
        ...
    }
}

void ISR()
{
    data[head++] = ...;
    ...
}

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
volatily schrieb:
> Muss man strikt alle Variablen die zwischen Kontexten geteilt werden mit
> volatile qualifizieren?

Ja, und auch die Atomizität der Zugriffe sicherstellen.

volatily schrieb:
> Gibt es hier Ausnahmen, dass man das Array im Beispiel nicht volatil
> markieren muss?

Nein. Keine, die nicht unter bestimmten Bedingungen den Erwartungen 
eines allwissenden Beobachters (dem, der es debuggt), widersprechen 
würden.

volatily schrieb:
> Hier habe
> ich schon oft gesehen, dass head und tail volatile sind aber das
> Datenarray nicht. Das wundert mich, scheint aber zu funktionieren.

Das tut es in vielen Situationen auch. Aber nicht immer.
Im Speziellen ist es so z.B. undefiniert, was zwei Lesezugriffe auf den 
selben Wert, zwischen denen ein Interrupt stattfindet, als Ergebnis 
liefern.

: Bearbeitet durch User
von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
Heiko L. schrieb:
> Das tut es in vielen Situationen auch. Aber nicht immer.
> Im Speziellen ist es so z.B. undefiniert, was zwei Lesezugriffe auf den
> selben Wert, zwischen denen ein Interrupt stattfindet, als Ergebnis
> liefern.

Beim vom TO genannten Ringpuffer stellt der Code normalerweise sicher, 
dass der Main-Teil nur Daten liest, die vom Interrupt nicht verändert 
werden. Darum reicht es, nur Schreib- und Lesezeiger zu sichern 
(atomarer Zugriff, volatile).

Beim Beispiel des TO (char rx = data[tail++];) wär ich mir aber nicht 
sicher, ob das OK ist, da zwischen Lesen und Incrementieren kein 
Sequence-Point liegt. Nacheinander schon:
char rx = data[tail]; 
tail++;
/* unbeachtet der Fehler/Überläufe beim TO-Code */

von Jemand (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Ich empfehle die Zugriffe ausschließlich mit den standardisierten 
Funktionen fur explizites Lesen und Schreiben durchzuführen, das ist 
allgemein verständlicher.

von Rolf M. (rmagnus)


Bewertung
0 lesenswert
nicht lesenswert
A. S. schrieb:
> Beim vom TO genannten Ringpuffer stellt der Code normalerweise sicher,
> dass der Main-Teil nur Daten liest, die vom Interrupt nicht verändert
> werden. Darum reicht es, nur Schreib- und Lesezeiger zu sichern
> (atomarer Zugriff, volatile).
>
> Beim Beispiel des TO (char rx = data[tail++];) wär ich mir aber nicht
> sicher, ob das OK ist, da zwischen Lesen und Incrementieren kein
> Sequence-Point liegt. Nacheinander schon:

Sequence-Points haben damit gar nichts zu tun. Man bräuchte eher eine 
Memory Barrier, um das sicherzustellen.

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
A. S. schrieb:
> Beim vom TO genannten Ringpuffer stellt der Code normalerweise sicher,
> dass der Main-Teil nur Daten liest, die vom Interrupt nicht verändert
> werden. Darum reicht es, nur Schreib- und Lesezeiger zu sichern
> (atomarer Zugriff, volatile).

Im ersten Moment dachte ich "richtig", aber:
Woher weißt du, dass der Compiler nicht den Wert der letzten Schleife 
noch in einem Register hat? So, wie es da steht, ist data[1] nur ein 
char. Und auch, wenn es mehr wären - woher wüsste man, dass er nicht die 
letzten X Werte irgendwo herumschleppt?

Edit: Es ist erschreckend, wie weit man kommt, ohne individuelle 
Besonderheiten auch nur eines Blickes zu würdigen. :)

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Bewertung
1 lesenswert
nicht lesenswert
volatile ist deprecated in C++20, was ist da der Ersatz?

von Jemand (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Johann L. schrieb:
> volatile ist deprecated in C++20, was ist da der Ersatz?

std::atomic und stdatomic.h in C (feiert bald zehnjähriges Jubiläum)

von Spongebob (Gast)


Bewertung
0 lesenswert
nicht lesenswert

von Rolf M. (rmagnus)


Bewertung
0 lesenswert
nicht lesenswert
Das heißt, in Zukunft müssen sämtliche I/O-Register eines µCs als 
std::atomic-Spezialisierungen definiert sein statt volatile?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Johann L. schrieb:
> volatile ist deprecated in C++20, was ist da der Ersatz?

Nein, nur spezielle Operationen, deren Semantik nicht klar ist.

von Bernd K. (prof7bit)


Bewertung
1 lesenswert
nicht lesenswert
Johann L. schrieb:
> volatile ist deprecated in C++20, was ist da der Ersatz?

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1152r0.html#prop
3.2. Proposed changes

This proposal has the following goals:

  1. Continue supporting the time-honored usage of volatile to load and store variables that are used for shared memory, signal handling, setjmp / longjmp, or other external modifications such as special hardware support.

  2. Deprecate [other useless stuff ...]
  

Also alles weiterhin problemlos. Volatile in der einzig nützlichen Form 
soll erhalten beliben für genau den Zweck für den es schon seit Anbeginn 
der Zeit gedacht war und für den wir es auch verwenden.

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Das heißt, in Zukunft müssen sämtliche I/O-Register eines µCs als
> std::atomic-Spezialisierungen definiert sein statt volatile?

Nein, atomic und volatile sind unterschiedliche Dinge.

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
Johann L. schrieb:
> volatile ist deprecated in C++20, was ist da der Ersatz?

Das ist doch eine Ente.

Ich glaube, man sollte nur für HW-Register noch volatile benutzen. 
Ansonsten std::atomic usw. - ein volatile-Konstrukt, das unbemerkt die 
Plattform wechselt und dann auf einmal nicht mehr atomar ist, dürfte ein 
ärgerlicher Fehler sein.

von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht lesenswert
Heiko L. schrieb:
> Johann L. schrieb:
>> volatile ist deprecated in C++20, was ist da der Ersatz?
>
> Das ist doch eine Ente.
>
Leute, lest doch mal genau!

von Jemand (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Heiko L. schrieb:
> ein volatile-Konstrukt, das unbemerkt die
> Plattform wechselt und dann auf einmal nicht mehr atomar ist, dürfte ein
> ärgerlicher Fehler sein.

Wer sich auf undefiniertes Verhalten verlässt, hat es nicht besser 
verdient.

von Bernd K. (prof7bit)


Bewertung
1 lesenswert
nicht lesenswert
Jemand schrieb:
> std::atomic

atomic und volatile sind zwei komplett andere Schuhe. Volatile hat 
nichts mit atomic zu tun.

* Volatile bedeutet: "laden/speichern dieser Variable gilt als IO, muss 
also genau in der Reihenfolge und genauso oft stattfinden wie im 
Quelltext angegeben."

* Atomic bedeutet: "Wenn das gemacht wird dann wird es in einem einzigen 
Rutsch geschehen und nichts kann mittendrin dazwischen kommen."

Zwei grundverschiedene Dinge also, es ist mir schleierhaft warum das 
immer wieder verwechselt wird.

von Rolf M. (rmagnus)


Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Nein, atomic und volatile sind unterschiedliche Dinge.

Das ist schon klar. Ich hatte auf die diese Aussage geantwortet:

Jemand schrieb:
> Johann L. schrieb:
>> volatile ist deprecated in C++20, was ist da der Ersatz?
>
> std::atomic und stdatomic.h in C (feiert bald zehnjähriges Jubiläum)

Die klang für mich, als sei es ganz selbstverständlich, dass man 
pauschal volatile nicht mehr nutzt und durch std::atomic ersetzt.

von Jemand (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Bernd K. schrieb:
> atomic und volatile sind zwei komplett andere Schuhe. Volatile hat
> nichts mit atomic zu tun.
>
> * Volatile bedeutet: "laden/speichern dieser Variable gilt als IO, muss
> also genau in der Reihenfolge und genauso oft stattfinden wie im
> Quelltext angegeben."
>
> * Atomic bedeutet: "Wenn das gemacht wird dann wird es in einem einzigen
> Rutsch geschehen und nichts kann mittendrin dazwischen kommen."
>
> Zwei grundverschiedene Dinge also, es ist mir schleierhaft warum das
> immer wieder verwechselt wird.

Ja, offensichtlich hast du noch erhebliche Verständnisprobleme mit 
std::atomic.

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
Problematisch dürfte sein, dass std::atomic z.T. mit locks arbeitet, die 
natürlich für interrupts nicht taugen. Andererseits soll eine 
atomic_signal_fence unter Windows z.B. einfach überhaupt gar nichts tun.
Ein Dilemma.

von leo (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Bernd K. schrieb:
> * Volatile bedeutet: "laden/speichern dieser Variable gilt als IO, muss
> also genau in der Reihenfolge und genauso oft stattfinden wie im
> Quelltext angegeben."

Nope. Du bestaetigt hier nur, warum viele Anwendungen von volatile 
deprected sein sollen.

"Continue supporting the time-honored usage of volatile to load and 
store variables that are used for shared memory, signal handling, setjmp 
/ longjmp, or other external modifications such as special hardware 
support."

volatile Variable koennen sich abseits des Programmflusses aendern 
(wegen IO oder ISR usw.) und duerfen daher nicht in Registern gehalten 
werden. Das hat nichts mit einer Reihenfolge zu tun.

Was du hier meinst sind "memory barriers".

leo

von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht lesenswert
Ich habe mittlerweile alles schon umgestellt, da ich C++20 (c++2a) schon 
verwende. Insgesamt finde ich es sogar gut, weil die Semantik nun klarer 
ist.

von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
Heiko L. schrieb:
> Im ersten Moment dachte ich "richtig", aber:

Uuups, ja ich fürchte, Ihr habt recht und ich schrieb absoluten 
Blödsinn.

Vermutlich wird es kaum zu Problemen führen, wäre aber eine gefährliche 
Zeitbombe.

Ich behaupte darum nun das Gegenteil: Die Daten im Ringpuffer müssten 
ebenso volatile sein.

Vermutlich schadet das auch kaum (wird der Code kaum langsamer oder 
größer): Gelesen werden muss sowieso und wird meist eh nur einmal.

von Rolf M. (rmagnus)


Bewertung
1 lesenswert
nicht lesenswert
leo schrieb:
> volatile Variable koennen sich abseits des Programmflusses aendern
> (wegen IO oder ISR usw.) und duerfen daher nicht in Registern gehalten
> werden. Das hat nichts mit einer Reihenfolge zu tun.

Alle Volatile-Zugriffe müssen so erfolgen wie sie im Code stehen - auch 
in der Reihenfolge. Und neben File-I/O ist das das einzige überhaupt, 
was das tatsächlich für das Programm vorgeschriebene Verhalten 
definiert.

von Bernd K. (prof7bit)


Bewertung
0 lesenswert
nicht lesenswert
leo schrieb:
> Bernd K. schrieb:
>> * Volatile bedeutet: "laden/speichern dieser Variable gilt als IO, muss
>> also genau in der Reihenfolge und genauso oft stattfinden wie im
>> Quelltext angegeben."
>
> Nope. Du bestaetigt hier nur, [...]

Doch, doch, genau so wie ich schreibe ist es definiert. Mit volatile 
werden erwünschte Seiteneffekte markiert, diese Seiteneffekte sind die 
einzigen Punkte an denen eine reale Maschine an die abstrakte Maschine 
gebunden ist, ansonsten darf die reale Maschine Kapriolen schlagen oder 
ganze Programmteile weglassen und stattdessen Däumchen drehen, 
Hauptsache alle definierten Seiteneffekte finden immer noch genau so (in 
der exakten Anzahl und Reihenfolge) statt wie sie in der abstrakten 
Maschine stattfinden. Normalerweise ist das nur Datei-I/O und der 
Rückgabewert von main(), mit volatile kann man zusätzliche solche 
Schnittstellen definieren.

: Bearbeitet durch User
von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Ich habe mittlerweile alles schon umgestellt, da ich C++20 (c++2a) schon
> verwende. Insgesamt finde ich es sogar gut, weil die Semantik nun klarer
> ist

Auf was hast du den jetzt das, was berechtigterweise „formerly known as 
volatile“ war, umgestellt?

Oliver

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Oliver S. schrieb:
> Auf was hast du den jetzt das, was berechtigterweise „formerly known as
> volatile“ war, umgestellt?

Einfach auf die zusammengesetzten op= (+=, etc.) und 
pre/post-increment/decrement für volatiles verzichtet, und die volatile 
gekennzeichneten Elementfunktionen heraus genommen. Alles ersetzt durch 
die gleichwertigen Anweisungen.

Nicht ganz konsequent habe ich nur teilweise volatile_load<> und 
volatile_store<> als vocabulary-functions eingebaut. Ist zwar ganz gut, 
um die Semantik klar zu machen. Andererseits ist ja noch nicht klar, was 
in C++23 kommen wird.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.