Forum: Compiler & IDEs volatile -- Anwendung auf Arrays


von Wastl (hartundweichware)


Lesenswert?

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.

von Falk S. (falk_s831)


Lesenswert?

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:

volatile int* mySFR = (volatile int*)(0x12345678);

Auf der Grundlage könnte eventuell auch ein Array sinnvoll sein.

volatile int* mySFR[2] = {(volatile int*)(0x12345678), (volatile 
int*)(0x12340000)};

Was meiner Meinung nach kaum Sinn machen würde, wäre, das volatile 
hinter das Sternchen zu schreiben, also so, als ob sich der Pointer 
ändern würde.

Aber prinzipiell könnteste so wohl mySFR[0] und mySFR[1] als Register 
ansprechen, glaube ich.

von (prx) A. K. (prx)


Lesenswert?

Wastl schrieb:
> 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?

von (prx) A. K. (prx)


Lesenswert?

Es gibt Systeme, die das gleiche RAM an mehreren Stellen des Adressraums 
einblenden, mit unterschiedlichen Zugriffseigenschaften.

von Wastl (hartundweichware)


Lesenswert?

Falk S. schrieb:
> 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.

von Bruno V. (bruno_v)


Lesenswert?

Wastl schrieb:
> 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.

Zeig mal (Pseudo-) Code deiner Implementierung.

von Foobar (asdfasd)


Lesenswert?

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
static u8 txbuf[16], txw;
2
static u8 rxbuf[16], rxr;
3
static volatile u8 txr, rxw;
4
5
ISR(USART_RX_vect)
6
{
7
    u8 err = UCSR0A & (B(FE0)|B(DOR0)|B(UPE0));
8
9
    rxbuf[rxw] = UDR0;
10
    if (err == 0)
11
    {
12
        u8 w = (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
u8 uart_getc(void)
34
{
35
    sleep_while (rxr == rxw);   // sleep while rxbuf empty
36
    u8 c = rxbuf[rxr];
37
    barrier("read rxbuf before changing rxr");
38
    rxr = (rxr + 1) % sizeof(rxbuf);
39
    return c;
40
}
41
42
void uart_putc(u8 c)
43
{
44
    u8 w = (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 :-)

[1] FYI: #define barrier(note) __asm volatile("":::"memory")

von Georg W. (voltaampere)


Angehängte Dateien:

Lesenswert?

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, ...

Gruß

von Oliver S. (oliverso)


Lesenswert?

Georg W. schrieb:
> 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.

Oliver

von Georg W. (voltaampere)


Lesenswert?

"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.

von Wastl (hartundweichware)


Lesenswert?

Georg W. schrieb:
> Hatte ich wohl unglücklich ausgedrückt.

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.

von Sven B. (scummos)


Lesenswert?

Georg W. schrieb:
> 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.

von Oliver S. (oliverso)


Lesenswert?

Sven B. schrieb:
> 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.

Oliver

: Bearbeitet durch User
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.