Forum: Mikrocontroller und Digitale Elektronik C: Assignment discards 'volatile' qualifier.


von Walter T. (nicolas)


Lesenswert?

Guten Morgen,

ich habe den folgenden Quelltext, und frage mich, warum gewarnt wird: 
"warning: assignment discards 'volatile' qualifier from pointer target 
type":
1
#define RINGBUFFER_NBYTE 128
2
3
typedef struct Ringbuffer_s
4
{
5
    uint8_t rawdata[RINGBUFFER_NBYTE+4]; 
6
    uint8_t *data;                       
7
    int16_t first;
8
    int16_t last;
9
}
10
Ringbuffer_t;
11
12
13
volatile Ringbuffer_t Ringbuffer;
14
15
16
void ringbuffer_init(void)
17
{
18
    Ringbuffer_t volatile *Rb = &Ringbuffer;
19
    Rb->first = 0;
20
    Rb->last = 0;
21
    Rb->data = Rb->rawdata+2; // <== Warnung
22
23
    ...
24
}
Ringbuffer ist volatile. *Rb ist Zeiger auf ein volatile struct. 
Rb->rawdata ist ein Zeiger auf ein Array in einem volatilen struct. 
Rb->data ist ein Zeiger auf einen Zeiger in einem volatilen struct. Wo 
wird ein "volatile" verworfen? Wo liegt mein Denkfehler?

von DPA (Gast)


Lesenswert?

Nunja, das
1
uint8_t rawdata[RINGBUFFER_NBYTE+4];
Ist wegen dem "volatile Ringbuffer_t Ringbuffer;" effektiv ein "volatile 
uint8_t[RINGBUFFER_NBYTE+4];" (Die Arrayelemente sind teil des Struct, 
deshalb sind diese auch volatile.). Das "uint8_t*" wird aber zu 
"uint8_t*volatile", nicht zu "volatile uint8_t*" (nur der Pointer ist 
teil des Structs, deshalb volatile.).

von Basic Programmierer (Gast)


Lesenswert?

Das sind Stolperfallen, da kann man einem Compiler (-Bauer)
nur dankbar sein!

von Walter T. (nicolas)


Lesenswert?

Hallo DPA,

danke! Das hatte ich übersehen. Manchmal hilft der zweite Blick.

Basic Programmierer schrieb:
> Das sind Stolperfallen, da kann man einem Compiler (-Bauer)
> nur dankbar sein!

Stimmt. Ohne die Warnung hätte ich das glatt übersehen. In der Hinsicht 
hat sich der GCC echt gut gemacht.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Die Frage ist, ob man so eine komplexe Struktur wie einen Ringpuffer 
wirklich komplett "volatile" machen möchte. Das könnte ziemlich 
ineffizienten Code bewirken. Dass der Compiler die Schreib-und 
Lesezugriffe nicht wegoptimiert hilft dir bei gleichzeitigen Zugriffen 
aus ISR und mainloop sowieso nicht, weil die Variablen zwischendurch in 
inkonsistenten Zuständen sein können - das bringt nur was bei einzelnen 
Variablen, sofern die Plattform sie "atomic" behandelt (bei ARM: 
1/2/4-Byte-Variablen).

Vielleicht wäre es da sinnvoller, die Interrupts während der Zugriffe 
auf die Pointer/Zähler komplett zu sperren, und dann vor der Freigabe 
mit einer Optimization Barrier den Compiler dazu zwingen, sie 
herauszuschreiben:
1
__disable_irq ();
2
... Zeiger/Zähler bearbeiten ...
3
__asm__ volatile ("": : :"memory");
4
__enable_irq ();

Durch clevere Zugriffsalgorithmen kann man das Lesen/Schreiben der 
eigentlichen Daten außerhalb dieser Sperre vornehmen, und die 
geschriebenen/frei gewordenen Bereiche danach mittels Anpassung der 
Zähler/Zeiger (unter Interruptsperre) herausgeben:
1
__disable_irq ();
2
... Bereich zum Schreiben reservieren ...
3
__asm__ volatile ("": : :"memory");
4
__enable_irq ();
5
6
... Daten in Puffer schreiben ...
7
8
__disable_irq ();
9
... Soeben beschriebenen Bereich zum Lesen freigeben ...
10
__asm__ volatile ("": : :"memory");
11
__enable_irq ();

Das funktioniert so natürlich nur auf Single-Core-Controllern zur 
Synchronisation zwischen mainloop und ISR (oder verschachtelten ISR). 
Ansonsten wären noch Cache-Maintenance und/oder Mutexe oder Atomics 
nötig, was man dann das (RT)OS machen lässt.

Wenn die mainloop sowieso leer ist und der ganze Programmcode sich in 
ISRs befindet, welche nicht verschachtelbar sind (bzw. die 
verschachtelten Interrupts nicht auf den Ringpuffer zugreifen), ist das 
sowieso egal und man kann das volatile ganz weglassen; der Compiler 
sorgt dafür dass vor der Rückkehr aus der ISR alles zurückgeschrieben 
wird.

: Bearbeitet durch User
von Nick M. (Gast)


Lesenswert?

Mir kommt die ganze Konstruktion schon äusserst suspekt vor!
Wozu gibt es überhaupt das "uint8_t *data". Eigentlich sollte das doch 
wohl in das array rawdata zeigen. Aber dafür hast du doch wohl das 
"first" als index. Mit dem *data musst du dann zwei Werte synchron 
halten.

Und so völlig suspekt wird es in der Funktion "ringbuffer_init". Warum 
nimmst du von der globalen "Ringbuffer" die Adresse und dereferenzierst 
sie immer wieder?
Prinzipiell ordentlich wäre es, den zu initialisierenden Ringpuffer an 
"ringbuffer_init" zu übergeben. Dann taugt die Funktion auch für einen 
zweiten Ringpuffer (Rx und Tx).

Wenn mir jemand so einen Code vorlegen würde, hätte er erheblichen 
Erklärungsbedarf! Massiv erheblich!

Nick

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.