Mir kam gerade diese Sonntagsfrage beim Durchsehen meines
Sourcecodes für einen Receive-Buffer (der interrupt-gesteuert
beschrieben wird) in den Sinn ....
Ist es überhaupt sinnvoll bei der Deklaration von statischen
Arrays ein >volatile< zu spezifizieren?
Da beim Zugriff auf Arrays ja meist ein Index berechnet werden
muss kann ein Compiler hier ja keine Voraussagen bzw. Register-
Optimierung anwenden um schnelleren Code zu erzeugen, es sei
denn man greift mit einem konstanten Index auf ein Array-
Element zu.
Übersehe ich da auf die Schnelle etwas? Gibt es dazu feste
Regeln oder ähnliches? Wirkt ein >volatile< gar nicht bei
Arrays? Fragen über Fragen. Vielleicht weiss hier der eine
oder andere mehr.
Na ja, vielleicht kann man sich der Sache nähern, indem man den Nutzen von volatile erstmal überhaupt definiert. Also, was soll damit gemacht werden?
Als Synchronisationsmittel ist es Quatsch, in fast jedem Fall (höchstens als Funktions-Qualifizierer bei selbstdefinierten classes vielleicht).
Wo es wohl angedacht ist, ist wenn sich der referenzierte Inhalt durch Nebenläufigkeiten außerhalb deiner C/C++-Laufzeitumgebung ändern kann. z.B. eine struct auf eine feste Speicheradresse, die z.B. durch SFR-Register geändert wird. Wäre also z.B. ein Pointer auf einen volatile-int, also sowas:
Da beim Zugriff auf Arrays ja meist ein Index berechnet werden
muss kann ein Compiler hier ja keine Voraussagen bzw. Register-
Optimierung anwenden um schnelleren Code zu erzeugen, es sei
denn man greift mit einem konstanten Index auf ein Array-
Element zu.
Und wie verhält sich es bei mehrfachem Zugriff, wenn sich der Index zwischendrin nicht ändert?
indem man den Nutzen
von volatile erstmal überhaupt definiert. Also, was soll damit gemacht
werden?
Bezogen auf mein (nicht vorhandenes) "Problem" wäre also die
Frage ob mein interrupt-gesteuertes Füllen eines Receive-
Buffers irgendwann mal mit dem Auslesen kollidiert, in dem
Sinne dass ein Compiler meint hier optimieren zu können,
das explizite Lesen aus dem Buffer wäre nicht nötig. Bei
einer einzelnen Variablen wäre das ja ein Thema.
ob mein interrupt-gesteuertes Füllen eines Receive-
Buffers irgendwann mal mit dem Auslesen kollidiert, in dem
Sinne dass ein Compiler meint hier optimieren zu können,
Typischerweise ist das Array eines Receiver-Buffers nicht das Problem, da jedes Byte einzeln einmalig gelesen wird.
Auch die Indexzeiger brauchen oft kein volatile.
Meist brauchst Du volatile nur, wenn eine Variable mehrfach ausgelesen/geschrieben wird UND auf eine Änderung reagiert werden soll bzw. der erneute Zugriff nicht weg optimiert werden darf.
Man muß bei interruptgesteuerten Sachen beachten, dass, wenn mehrere Variablen gleichzeitig geändert werden, diese untereinander immer einen konsistenten Zustand darstellen. Wenn alle Variablen volatile sind folgt die Variablenänderung strikt dem Programmablauf. Sind die Variablen nicht alle volatile, können Speicherzugriffe umsortiert werden, was die interne Konsistenz verletzt kann.
Als Beispiel (für nicht alle volatile) ein Teil meiner UART-Routinen:
1
staticu8txbuf[16],txw;
2
staticu8rxbuf[16],rxr;
3
staticvolatileu8txr,rxw;
4
5
ISR(USART_RX_vect)
6
{
7
u8err=UCSR0A&(B(FE0)|B(DOR0)|B(UPE0));
8
9
rxbuf[rxw]=UDR0;
10
if(err==0)
11
{
12
u8w=(rxw+1)%sizeof(rxbuf);
13
if(w!=rxr)
14
{
15
rxw=w;
16
wakeup("uart_rx");
17
}
18
}
19
}
20
21
ISR(USART_UDRE_vect)
22
{
23
if(txr!=txw)
24
{
25
UDR0=txbuf[txr];
26
txr=(txr+1)%sizeof(txbuf);
27
wakeup("uart_tx");
28
}
29
else
30
UCSR0B&=~B(UDRIE0);
31
}
32
33
u8uart_getc(void)
34
{
35
sleep_while(rxr==rxw);// sleep while rxbuf empty
36
u8c=rxbuf[rxr];
37
barrier("read rxbuf before changing rxr");
38
rxr=(rxr+1)%sizeof(rxbuf);
39
returnc;
40
}
41
42
voiduart_putc(u8c)
43
{
44
u8w=(txw+1)%sizeof(txbuf);
45
46
sleep_while(w==txr);// sleep while txbuf full
47
txbuf[txw]=c;
48
barrier("write txbuf before changing txw");
49
txw=w;
50
UCSR0B|=B(UDRIE0);
51
}
[Das wakeup-Makro kann man ignorieren, das sleep_while als while betrachen (die ermöglichen, dass der Prozessor in den sleep-Modus geht).]
Es gibt nur zwei volatile Variablen (txr und rxw), die Buffer selbst sind nicht volatile. Das barrier-Makro[1] ist eine memory barrier, die dem Compiler verbietet, Speicherzugriffe über dieses Makro hinweg zu verschieben. Das Argument von barrier dient ausschließlich der Kommentierung und zeigt hier, was verhindert werden soll, damit die Daten (Buffer ind read-/write-Indizes) konsistent bleiben.
Klar, man könnte auch alles volatile machen oder atomic-blocks benutzen, aber ich finde es so eleganter :-)
Ich habe mit Programmen auf PCs am meisten Erfahrung.
Wenn man auf dem PC mit Visual Studio (oder gcc ...) die Optimierung angeschaltet hat, macht der Compiler viele Dinge.
Er kann z.B. ganze Funktionsaufrufe, die zur Compile-Zeit schon berechnet werden können, ausrechnen. Im Code steht dann nur noch das Ergebnis als Konstante. (Siehe Anlage). Dort hat der Compiler nicht nur das ganze Array wegoptimiert, sonder die Suchfunktion gleich mit.
Wenn man das Array als volatile klassifiziert, dann macht er das nicht.
Also:
Wann immer sich die Daten außerhalb des eigenen Programmes/Threads ändern können, sollte man das Array als volatile deklarieren.
Die Verwendung ist aber nicht beliebt, weil der Compiler dann eben nicht gut optimieren kann, ...
Die Verwendung ist aber nicht beliebt, weil der Compiler dann eben nicht
gut optimieren kann, ...
Die Verwendung von Sprachkonstrukten in Programmiersprachen, besonders die Verwendung von volatile, sollte man niemals von irgend einer Beliebtheit abhängig machen.
"Die Verwendung von Sprachkonstrukten in Programmiersprachen, besonders
die Verwendung von volatile, sollte man niemals von irgend einer
Beliebtheit abhängig machen."
Hatte ich wohl unglücklich ausgedrückt. Gemeint ist:
Wenn das volatile nicht wirklich benötigt wird, sollte man es weglassen, aber eben nur dann.
Du hast auch "unglücklich" zitiert. Nachdem du schon mehr als
acht Jahre hier angemeldet bist solltest du langsam auf den
Trichter kommen wie man hier üblicherweise kinderleicht
Texte/Beiträge zitiert.
Wann immer sich die Daten außerhalb des eigenen Programmes/Threads
ändern können, sollte man das Array als volatile deklarieren.
Ich wüsste nicht, wo man volatile im Kontext von Threads sinnvoll nutzen könnte, oder wo die ganze Fragestellung, mit der sich volatile befasst, bei Userspace-PC-Programmierung überhaupt auftritt.
Der Kern-Anwendungsfall von volatile sind Adressen, die kein RAM sind, also z.B. Register auf einem Mikrocontroller. 99.9% aller Fälle, wo ein volatile qualifier an irgendwas dransteht was keine Registeradresse auf einem Mikrocontroller sind, sind Unsinn.
Ich wüsste nicht, wo man volatile im Kontext von Threads sinnvoll nutzen
könnte
Die kreative Nutzung von volatile für alles Mögliche und Unmögliche im Zusammenhang mit der Synchronisation von Daten zwischen Threads u.ä. hatte das C++-Standardization Commitee dazu gebracht, zukünftig so ziemlich alle Operationen auf volatile Variablen zukünftig zu verbieten. Zum Glück haben die das inzwischen zurückgenommen, weil damit auch die eigentliche Verwendung kaum noch möglich gewesen wäre.
Es wird da draußen tonnenweise Code geben, in denen volatile fälschlicherweise genutzt wird.