Forum: Mikrocontroller und Digitale Elektronik c volatile -> wann bracht mans wirklich?


von Ein Anfänger (Gast)


Lesenswert?

Erstmal noch schöne Weihnachten in die Runde!

Ich nutze gerade die freie Zeit zum Programmieren auf einem STM32, 
Programmiersprache c. Prinzipiell ist mir das volatile-Konzept soweit 
klar, aber im Detail kommen dann doch Fragen auf.

Die Situation ist folgende: Ein Struct (typedef struct tFoo tFoo;) wird 
per Pointer (tFoo * foo) im Kontext von main angelegt und im Kontext des 
UART-Interrupts, Timerinterrupts und ggf in main gelesen und Werte darin 
verändert. Die erste Frage ist, ob der Struct-Pointer selber schon 
volatile sein muss (tFoo * volatile foo), obwohl die Adresse nur einmal 
angelegt wird und im folgenden eben nur noch aus verschiedenen Kontexten 
zugegriffen wird (1). Die Adresse ändert sich nach dem Anlegen nicht 
mehr, man könnte aber argumentieren, dass für den Compiler nicht 
ersichtlich ist, ab wann der Wert initialisiert ist und bei besonders 
aggressiver Optimierung könnte er auf die Idee kommen den Initialwert 
Null zu nehmen, anstatt die (gleiche) Adresse aus dem Pointer bei jedem 
Funktionsaufruf neu zu laden.

Als nächstes geht es darum wie mit den Structmembern umzugehen ist. Was 
ich so gefunden habe, kommt es auf das gleiche heraus, das ganze Struct 
volatile zu definieren (volatile tFoo foo) oder die einzelnen 
Membervariablen. Nicht klar ist mir, wie das dann mit Pointern als 
Structmember ist. Überträgt sich die Volatile-Definition dann auf die 
Adresse oder die Speicherstelle, auf die sie zeigen, oder beides (2)? In 
meinem Fall kann sich beides zwischen Kontexten ändern.


Braucht es in meinem Fall "das volle Programm" (3)?
Also:
1
typedef struct tFoo{
2
  volatile uint8_t * volatile SomeArray;
3
  //...
4
} tFoo;
5
6
volatile tFoo * volatile foo;

Mit der Vorgehensweise ergeben sich dann diverse Warnungen bei 
Funktionsaufrufen, denen eine Adresse übergeben wird: Passing argument 
discards volatile qualifier...

Aufruf:
1
somefct(foo+3); //(Der Pointer spannt ein Array auf)
Definition:
1
void somefct(tFoo * aFoo);

Meine Recherche dazu hat ergeben, dass man die in meinem Fall wohl 
ignorieren kann, weil sich die übergebenen Adressen innerhalb der 
Funktionen nicht von außen ändern können, es also reicht, wenn der 
Compiler in der Funktion einmal aus der bepointerten Speicherzelle liest 
oder rein schreibt. Mit einem Typecast somefct((tFoo*)(tFoo+3)) lässt 
sich die Warnung wohl umgehen, die Frage ist aber, ob bei einem 
aggressiven Compiler da irgendwas passiern kann (4)? Das ist ja ein 
Thema, dass man nicht einfach austesten kann, weil dass es heute 
funktioniert heißt ja nicht, dass es sprachlich richtig ist und 
entsprechend auch morgen noch funktioniert.

Ich würde mich über Kommentare freuen!

Vielen dank schonmal!

von Εrnst B. (ernst)


Lesenswert?

Auch wenn das hier gleich wieder einen Flamewar lostritt:

Verzichte auf das volatile, arbeite mit memory/optimization barriers.

von Ein Anfänger (Gast)


Lesenswert?

Εrnst B. schrieb:
> arbeite mit memory/optimization barriers.

Wie sieht das aus?

Beitrag #7298088 wurde von einem Moderator gelöscht.
von Εrnst B. (ernst)


Lesenswert?

Ein Anfänger schrieb:
> Wie sieht das aus?

du willst zwei Probleme lösen:
1) dein main-Programm soll immer die neuesten Werte lesen, die von der 
ISR geschrieben wurden
2) das main-Programm soll währenddessen einen gültigen Zustand der 
Struktur vorfinden, vor allem soll die ISR nicht während des Zugriffs 
die Struktur verändern.

"volatile" löst das erste Problem, in einer "Kanonen auf Spatzen"-Art. 
Aber nicht das zweite.

eine compiler barrier löst das erste Problem auch, du teilst dem 
Compiler damit mit, dass exakt ab dieser Stelle zwischengespeicherte 
Werte verworfen und bei Bedarf wieder frisch aus dem RAM geladen werden 
müssen.

Für das zweite Problem brauchst du eine Interrupt-Sperre, solang es 
nicht nur ein einzelner bool/int ist.

Jetzt der Clou: Die Interrupt-Sperre bringt (oft/meistens) die 
Compiler-Barrier gleich mit, d.H. die Lösung für Problem 2 löst gleich 
im Vorbeigehen Problem 1 mit.

z.B. __disable_irq()/STM32/gcc wird zu
1
 __ASM volatile ("cpsid i" : : : "memory");

Ein Anfänger schrieb:
> Prinzipiell ist mir das volatile-Konzept soweit
> klar

ist es vermutlich nicht.

Das Konzept dahinter ist:
Es ist einfacher, einem Anfänger ein "dann schreib halt volatile davor" 
hinzuwerfen, anstatt ihm einen 10-Seiten-Aufsatz über die Freiheiten, 
die der C-Standard dem Compiler/Optimizer gewährt, und wie man damit 
umgeht, zu schreiben. Den versteht er dann eh nicht.

von Ein Anfänger (Gast)


Lesenswert?

Εrnst B. schrieb:
> du willst zwei Probleme lösen:
> 1) dein main-Programm soll immer die neuesten Werte lesen, die von der
> ISR geschrieben wurden
> 2) das main-Programm soll währenddessen einen gültigen Zustand der
> Struktur vorfinden, vor allem soll die ISR nicht während des Zugriffs
> die Struktur verändern.
Punkt zwei ist hier nicht relevant, weil alles strikt nacheinander 
geschieht, der Zugriff aus einem anderen Kontext findet erst statt, wenn 
die Aufgabe im einen Kontext komplett erledigt ist. Es geht also nur 
darum, dass innerhalb der Interrupts/Funktionen zumindest einmal 
geladen/gespeichert wird.

Das heißt, jeweils eine Memory Barrier am Anfang der ISR müsste schon 
ausreichen? Das hieße aber, dass in Funktionen, die in der ISR 
aufgerufen werden, die Variablen sowieso nachgeladen werden? Weil der 
Compiler "weiß" ja an den Stellen kaum, dass in der ISR schon eine 
Memory Barrier war. Ist das der Fall, bräuchte es aber im Grunde gar 
keine Memory Barrier, wenn ja sowieso immer nachgeladen wird. Oder 
andersrum, benötige ich in jeder Funktion eine Memory Barrier, kann ja 
auch nicht sein?

> Es ist einfacher, einem Anfänger ein "dann schreib halt volatile davor"
> hinzuwerfen, anstatt ihm einen 10-Seiten-Aufsatz über die Freiheiten,
> die der C-Standard dem Compiler/Optimizer gewährt, und wie man damit
> umgeht, zu schreiben. Den versteht er dann eh nicht.

Volatile ist aber wie man hier sieht auch nicht gerade selbsterklärend. 
Das hier mit Pointern auf Pointer kann auch ein Spezialfall sein, aber 
etwas mehr Kommentare zur Volatile-Verwendung hätte ich hier von der 
Forengemeinde schon erwartet.

von Konrad Küstrin (Gast)


Lesenswert?

Volatile ist nur nötig wenn man den Optimierer nicht ausschaltet.

von Εrnst B. (ernst)


Lesenswert?

Ein Anfänger schrieb:
> Das heißt, jeweils eine Memory Barrier am Anfang der ISR müsste schon
> ausreichen?

Nein, vor der Stelle im Main, in der du auf die Datenstruktur zugreifst.

Ein Anfänger schrieb:
> Punkt zwei ist hier nicht relevant

d.H. du hast schon sichergestellt, dass die IRQs abgestellt sind, 
während auf der Datenstruktur gearbeitet wird, im Hauptprogramm 
abgestellt, und keine IRQ-Prioritäten, die nested IRQ-Aufrufe erlauben 
würden?

Dann stehen die Chancen gut, dass du garnix weiter brauchst. Kein 
Volatile, keine expliziten Optimization-Barriers. Weil die 
IRQ-Sperren-Operation das schon implizit mitbringt(*).


*) Hängt natürlich vom Compiler und den verwendeten Frameworks ab. Wenn 
du das "von Hand" durch Schreiben der entsprechenden Register machst 
(also kein "noInterrupts() / __disable_irq() / HAL_NVIC_DisableIRQ() 
usw), dann musst du die Optimization Barrier selber nachrüsten.

von Ein Anfänger (Gast)


Lesenswert?

Εrnst B. schrieb:
> Ein Anfänger schrieb:
>> Das heißt, jeweils eine Memory Barrier am Anfang der ISR müsste schon
>> ausreichen?
>
> Nein, vor der Stelle im Main, in der du auf die Datenstruktur zugreifst.

In der ISR greife ich doch auch darauf zu, wo ist der Unterschied zur 
main?

> Ein Anfänger schrieb:
>> Punkt zwei ist hier nicht relevant
>
> d.H. du hast schon sichergestellt, dass die IRQs abgestellt sind,
> während auf der Datenstruktur gearbeitet wird, im Hauptprogramm
> abgestellt, und keine IRQ-Prioritäten, die nested IRQ-Aufrufe erlauben
> würden?
Nicht abgestellt, aber durch Flags gesichert. Aus main raus wird 
initialisiert, im UART-Interrupt wird ein Flag zur Aufzeichnung gesetzt 
und ab dem nächsten Timerinterrupt läuft dann die ADC-Aufzeichnung 
jeweils aus dem Timerinterrupt heraus. Wenn die vorgegebene Anzahl 
ADC-Messungen in der Struktur abgespeichert ist, wird ein entsprechendes 
Flag gesetzt, das in der main-Loop ausgewertet wird und dann für die 
Datenübertragung an den PC zuständig ist. Ein Flag sichert ab, dass 
während der Übertragung keine neue Messung angestoßen wird. Ich muss 
also eigentlich nie Interrupts sperren.

Ich würde auch gerne auf compiler- oder gar target-spezifische Befehle 
verzichten, ich dachte, dass man da mit volatile noch am universellsten 
unterwegs ist. Aber zumindest Single-Core kann man als gegeben annehmen, 
man muss sich also nicht auch noch um den Cache oder so sorgen.

von avr (Gast)


Lesenswert?

Ein Anfänger schrieb:
> Ich würde auch gerne auf compiler- oder gar target-spezifische Befehle
> verzichten, ich dachte, dass man da mit volatile noch am universellsten
> unterwegs ist.

Dann würde ich ein Blick Richtung C++ empfehlen:

https://en.cppreference.com/w/cpp/header/atomic

von Konrad Küstrin (Gast)


Lesenswert?

Da steht einige zu volatile-Feinheiten im Zusammenhang mit C-20:

https://www.heise.de/blog/volatile-und-andere-kleine-Verbesserungen-in-C-20-4874333.html

von Εrnst B. (ernst)


Lesenswert?

Ein Anfänger schrieb:
> Ein Flag sichert ab, dass
> während der Übertragung keine neue Messung angestoßen wird.

Als "Quick&Dirty" Lösung wäre dann (nur) das flag volatile. Irgendwelche 
Array-Elemente wird der Compiler eher nicht in Registern zwischenhalten.

Die "Saubere" Lösung wäre eine optimization barrier in der main vor dem 
Test des Flags.

ein
1
asm volatile ("" : : : "memory");
 oder das GCC-Builtin
1
__sync_synchronize(); (*)
 würde reichen, und wäre nicht Target- aber Compiler-Abhängig.

Ein Anfänger schrieb:
> In der ISR greife ich doch auch darauf zu, wo ist der Unterschied zur
> main?

Die ISR kann der Compiler nicht irgendwo hin "inlinen", er sieht ja auch 
keinen Aufruf davon. Insofern besteht auch keine Gefahr, dass 
irgendwelche Code-Teile aus der ISR heraus oder hineingeschoben werden.




*) __sync_synchronize macht neben der Compiler/Optimization-Barrier auch 
noch eine Memory-Barrier. Auf Single-CPU-Systemen egal, es wird kein 
Code generiert.

von Peter D. (peda)


Lesenswert?

Volatile wird schon in den h-Files richtig hinzugefügt, wenn man 
IO-Register zugreift.
Z.B. bei der UART sorgt es dafür, daß auch alle Bytes gesendet, gelesen 
werden. Der Compiler würde sonst ohne volatile nur das letzte Byte 
senden.

Weiterhin braucht man es in der Mainloop, wenn Interrupts eine Variable 
auch benutzen, d.h. um mit Interrupts Daten auszutauschen.
Der Interrupt selber braucht es nicht, daher reicht es, auch nur die 
Mainzugriffe zu casten:
1
#define IVAR(x)         (*(volatile typeof(x)*)&(x))

von Ein Anfänger (Gast)


Lesenswert?

Εrnst B. schrieb:
> Die ISR kann der Compiler nicht irgendwo hin "inlinen", er sieht ja auch
> keinen Aufruf davon. Insofern besteht auch keine Gefahr, dass
> irgendwelche Code-Teile aus der ISR heraus oder hineingeschoben werden.
OK, verstanden!



Peter D. schrieb:
> Weiterhin braucht man es in der Mainloop, wenn Interrupts eine Variable
> auch benutzen, d.h. um mit Interrupts Daten auszutauschen.
> Der Interrupt selber braucht es nicht, daher reicht es, auch nur die
> Mainzugriffe zu casten:
>
1
> #define IVAR(x)         (*(volatile typeof(x)*)&(x))
2
>

Das ist interessant, damit kann man quasi sagen, "genau hier neu laden"?

Dann hätte ich noch eine Frage zu folgender Sequenz,
1
//Global
2
typedef struct tFoo tFoo;
3
tFoo * foo; //Pointer auf ein Array des Structs
4
5
//In einer Funktion
6
tFoo * localFoo;
7
8
localFoo= (volatile tFoo*)foo+3; //Lade 3tes Array element [*1]
9
localFoo->SomeMember= 123456; //[*2]

Ich verstehe es jetzt so, dass durch den Volatile-Cast in *1 die Adresse 
vom Array foo definitiv neu geladen wird. Jetzt kann der Compiler auch 
nicht mehr die Adresse der Membervariablen aus einem Register holen 
lassen oder Ähnliches, d.h. das eine Volatile pflanzt sich in gewisser 
Weise über das ganze Struct fort?
Wie ist es dann mit dem Wert in Zeile *2, der Compiler kann durch die 
volatile Adresse ja auch nicht mehr wissen, ob der Wert schonmal an 
diese Stelle geschrieben wurde, darf damit also das Schreiben auch nicht 
wegoptimieren. Ist das so?

Die Zeile *1 sollte dann das gleiche sein wie
1
localFoo= IVAR(foo)+3;

von Chris (Gast)


Lesenswert?

Volatile in einer Struktur (offset) ist verboten oder unnütz, bzw nicht 
vom Standard definiert, da dieser nur Variablen definiert, nicht 
offsets. Deshalb auch die Meldung dass es ignoriert (discard) wird.

von Chris (Gast)


Lesenswert?

Ich meinte innerhalb einer Struktur, die Variable einer Struktur als 
volatile su deklarieren ist OK.

von A. S. (Gast)


Lesenswert?

In einer Schleife im Main auf ein interruptflag warten ist ein Usecase 
für volatile. Zeit abgelaufen, Sendepuffer leer, sowas.

Zwei ganz andere Themen sind Race condition und Daten-Konsistenz. Da 
hilft volatile nicht bzw. nur sehr eingeschränkt mit vielen Fallen.

Das Warten in Main ist oft ein Design-flaw. Und sobald in der Schleife 
auch ein unbekannter Funktionsaufruf ist, ist volatile "um den herum" 
obsolete.

von Ein Anfänger (Gast)


Lesenswert?

Chris schrieb:
> Volatile in einer Struktur (offset) ist verboten oder unnütz, bzw nicht
> vom Standard definiert, da dieser nur Variablen definiert, nicht
> offsets. Deshalb auch die Meldung dass es ignoriert (discard) wird.

Die Meldung kommt ja, wenn man eine volatile Variable an eine Funktion 
übergibt, die nicht volatile ist. So habe ich das zumindest beobachtet. 
Bist du sicher, dass das vom Standard nicht definiert ist? Sinnvoll wäre 
ja die volatile Verwendung eines einzelnen Structmembers schon.


A. S. schrieb:
> Das Warten in Main ist oft ein Design-flaw.

Warten ist vielleicht das falsche Wort, eher ein regelmäßiges pollenn in 
der Main-Loop.

> Und sobald in der Schleife
> auch ein unbekannter Funktionsaufruf ist, ist volatile "um den herum"
> obsolete.

Wie meinst du das?

von db8fs (Gast)


Lesenswert?

Komplexes Thema, mit volatile zu arbeiten. Technisch ist volatile wohl 
als Qualifikator sowas wie const - d.h. alle Sauereien, die für const 
funktionieren, werden vermutlich auch für volatile gehen. Was das 
semantisch bewirkt - da bin ich diesbezüglich auch der Meinung, dass das 
sehr drauf ankommt.

In C++ kannste mit der const-Correctness eben auch volatile-Correctness 
machen. D.h. auf Member einer volatile struct Foo, können dann auch auch 
nur volatile-qualifizierte Funktionen zugreifen.

In C ist das meines Wissens nach nicht so - ich glaub, da macht volatile 
tatsächlich auch nur für gute alte Daten richtig Sinn. Wann ein 
volatiler Pointer (also volatiler Pointer auf nicht-volatile struct) 
überhaupt sinnvoll sein kann, ist auch eher schwierig zu beurteilen, 
eventuell könnte das für ein Array ne Rolle spielen. Aber selbst das 
würde ich beim besten Willen nur sehr defensiv als was Notwendiges 
erachten.

Ich glaub, am ehesten macht volatile für structs Sinn, wenn du damit 
anzeigen willst, dass die Werte einer struct sich beim nächsten Zugriff 
ändern können, ohne dass dein Programm verändernderweise was damit zu 
tun hat.

Würde also glauben wollen, dass 'volatile struct* ptr' dafür ausreichen 
sollte.

von Chris (Gast)


Lesenswert?

Ja, undefined behavior Ist im Standard als solchen beschrieben.
Manche Embedded C erlauben dies explizit auf diversen Branches des 
Compilers, da is es im Manual aber ganz genau beschrieben, dass z.B. das 
ganze struct/union/typedef auf volatile promoviert wird, oder dass dies 
nur fuer integer und nicht fuer bitfields oder sonstige Variablen 
zulaessig ist.
Grundsaetzlich zu GCC:
Gcc does not implement a correct semantics for accesses to volatile 
struct members in non volatile objects. Dies ist bekannt und da gcc 
sowie g++ eine gemeinsame Codebase haben, wird sich daran auch nie was 
aendern, es geht leider nicht.
Dass man header trotzdem sieht mit diesem struct welche ein volatile 
struct element enthalten hat einen anderen Grund. In C++(c11 aber auch 
frueher)
ist dies ein workaround um keinen copy assignment operator zu haben, und
trotzdem einen constant volatile verwenden zu koennen, da c++(c11) keine
optimierungen, sprich keine constant substitution with fixed constant 
value
in Strukturen macht. Damit kann man dann auch readonly register 
spezifizieren, welche ansonsten im neueren c++ nicth moeglich gewesen 
waere.

von Ein Anfänger (Gast)


Lesenswert?

db8fs schrieb:
> Komplexes Thema, mit volatile zu arbeiten. Technisch ist volatile wohl
> als Qualifikator sowas wie const - d.h. alle Sauereien, die für const
> funktionieren, werden vermutlich auch für volatile gehen. Was das
> semantisch bewirkt - da bin ich diesbezüglich auch der Meinung, dass das
> sehr drauf ankommt.
>
> In C++ kannste mit der const-Correctness eben auch volatile-Correctness
> machen. D.h. auf Member einer volatile struct Foo, können dann auch auch
> nur volatile-qualifizierte Funktionen zugreifen.
>

> In C ist das meines Wissens nach nicht so - ich glaub, da macht volatile
> tatsächlich auch nur für gute alte Daten richtig Sinn. Wann ein
> volatiler Pointer (also volatiler Pointer auf nicht-volatile struct)
> überhaupt sinnvoll sein kann, ist auch eher schwierig zu beurteilen,
> eventuell könnte das für ein Array ne Rolle spielen. Aber selbst das
> würde ich beim besten Willen nur sehr defensiv als was Notwendiges
> erachten.

Eigentlich ist es ja so, dass sobald sich die Adresse ändert, auch die 
Werte im Struct andere sein können. D.h. die Volatilität müsste sich 
übertragen, sonst rechnet der optimierte Code mit (Register-) Werten aus 
alten Adressen herum. Anderseits müsste streng genommen bei jedem 
Zugriff auf eine Structmember-Variable der Pointer neu dereferenziert 
werden, schließlich ist er volatile gekennzeichnet. Das wäre aber für 
die Performance schlecht und ich kann mir kein sinnvolles Szenario dafür 
vorstellen.

Eigentlich bin ich der Meinung, dass für den Compiler Adressoperationen 
bei Pointern sowieso schon so Art Memory-Barriers sein müssten, weil zur 
Compile-Zeit die Speicheradresse überhaupt nicht bekannt ist. Der 
Compiler kann also nicht auf Registerdaten zurückgreifen (oder ganze 
Blöcke wegoptimieren), sondern muss nach jeder Adressoperation neu 
laden/speichern.

In dem Fall würde mir dann ein einfacher Volatile-Cast (wie peda 
vorgeschlagen hat) reichen, allerdings für den Adresszugriff.

Vermutlich hat der Compiler durch die Funktionsverschachtelungen und die 
dynamische Datenstruktur sowieso nie eine Chance dahingehend 
Optimierungen durchzuführen, dann müsste es also auch ohne irgend ein 
volatile zuverlässig funktionieren. Deswegen ja auch die Frage hier.

Chris schrieb:
> Grundsaetzlich zu GCC:
> Gcc does not implement a correct semantics for accesses to volatile
> struct members in non volatile objects.
> [...] es geht leider nicht.

Verstanden, also entweder das komplette Struct ist volatile, oder gar 
nichts.

Puh, das Thema ist doch komplizierter wie es auf den ersten Blick 
scheint:)

von A. S. (Gast)


Lesenswert?

Ein Anfänger schrieb:
> Wie meinst du das?

Der Standard-Anwendungsfall ist
1
interrupt Ix(void)
2
{
3
   ...
4
   flg = 0;
5
   ...
6
}
7
8
9
int main(void)
10
{
11
  ....
12
  flg=1;
13
  while(flg);
14
  ...
15
}
hier darf der Compiler annehmen, dass flg sich nicht mehr ändert und 
eine Endlosschleife einbauen.

ändert man die while-Anweisung zu
1
  while(flg) {myWait();}
mit myWait als externe Funktion, so weis der Compiler nicht, ob flg in 
myWait verändert wird. Darum wertet er flg jedes mal aus.

Problematisch wird das nun, wenn er (warum auch immer) doch weiß, dass 
flg in myWait (bzw. während des Aufrufs) nicht verändert wird ;-)

von Εrnst B. (ernst)


Lesenswert?

Ein Anfänger schrieb:
> Puh, das Thema ist doch komplizierter wie es auf den ersten Blick
> scheint:)

Deswegen: Compiler/Optimization Barriers. Damit sagst du dem Compiler: 
Ab hier möchte ich garantiert frische Werte lesen, falls eine ISR was 
geändert hat. Fertig.
Bei einfachen Flags ist deklarieren als "volatile" schon eine brauchbare 
Lösung, aber bei komplexeren Datenstrukturen (Ringbuffer für UART z.B. 
oder sogar 16/32-Bit Variablen auf 8-Bit µC) brauchst du eh ein 
zusätzliches Locking, ATOMIC_BLOCK, IRQ-Sperre etc.
Und genau das bringt die Barrier i.A. schon mit: Problem gelöst.

von Ein Anfänger (Gast)


Lesenswert?

A. S. schrieb:
> mit myWait als externe Funktion, so weis der Compiler nicht, ob flg in
> myWait verändert wird. Darum wertet er flg jedes mal aus.
>
> Problematisch wird das nun, wenn er (warum auch immer) doch weiß, dass
> flg in myWait (bzw. während des Aufrufs) nicht verändert wird ;-)

Bei mir ist alles in Funktionen gekapselt, aber da möchte ich mich nicht 
ganz darauf verlassen, oder sind Funktionsaufrufe als 
Optimierungsgrenzen im Standard definiert? Es werden ja immer die 
Optimierungskünste der Compiler gelobt, da würde ich erwarten, dass eine 
Funktion, die nur an einer Stelle aufgerufen wird, erstmal geinlined 
wird und dann alle Optimierungen darauf angewendet werden, zumindest 
potentiell.


Εrnst B. schrieb:
> Deswegen: Compiler/Optimization Barriers. Damit sagst du dem Compiler:
> Ab hier möchte ich garantiert frische Werte lesen, falls eine ISR was
> geändert hat. Fertig.

Wie gesagt, würde ich gerne compilerspezifische Befehle vermeiden.

Ich denke ich nehme jetzt den Volatile-Cast und wenns mir in Zukunft 
irgendwann um die Ohren fliegt, habe ich offensichtlich was falsch 
gemacht :)

--
Danke an Alle!

von W.S. (Gast)


Lesenswert?

Ein Anfänger schrieb:
> Die Situation ist folgende: Ein Struct (typedef struct tFoo tFoo;) wird
> per Pointer (tFoo * foo) im Kontext von main angelegt

Das hat erstmal nix mit volatile zu tun. Du hast also irgend einen 
Zeiger in main, der ab Start des Programms erstmal ins nirwana oder 
sonstwo hin zeigt. Der soll aber auf deinen Datensatz zeigen, weswegen 
du auf dem Heap den nötigen Platz resevierst und dann deinen Datensatz 
initialisierst. Und dessen Adresse landet in obigem Zeiger. Nebenbei: 
mit typedef kann man Typen umbenennen (genauer: einen Alias erzeugen),
Prinzip:
typedef altername neuername;
Und ich halte es für eine Unart, dabei den gleichen Wortlaut zu nehmen 
und lediglich an der Großschreibung etwas zu schrauben. Aber das nur am 
Rande.

Zu volatile: anders als bei Pascal gibt es bei C kein Modulsystem und 
damit keine Kapselung. Der Compiler kann bei C also nicht selbst 
herausfinden, ob von woanders auf eine Variable zugegriffen werden kann 
oder nicht. Also kann er von selbst auch nicht entscheiden, ob die 
Variable während des Programmablaufes einer Funktion in einer Quelldatei 
(die er gerade übersetzt) von außen geändert werden kann oder nicht. Man 
muß es ihm also sagen und dazu dient das volatile.

Um auf deinen Zeiger zurückzukommen: Der soll ja auf deinen Datensatz 
auf dem Heap zeigen und nicht woanders hin. Also sollte er auch 
gleichbleiben, solange du nicht den Platz für den Datensatz an den Heap 
zurückgibst und einen anderen Platz auf dem Heap belegst, um dort erneut 
deinen Datensatz anzulegen.

Mein Rat wäre: Steigere dich nicht in immer halsbrecherischere 
Konstrukte hinein, sondern formuliere dein Zeug eher bieder und so 
einfach wie möglich.

W.S.

von avr (Gast)


Lesenswert?

A. S. schrieb:
> ändert man die while-Anweisung zu
>
1
>   while(flg) {myWait();}
2
>
> mit myWait als externe Funktion, so weis der Compiler nicht, ob flg in
> myWait verändert wird. Darum wertet er flg jedes mal aus.

Das ist undefiniert, weil der Compiler Modulübergreifend optimieren 
könnte.

Sinnvollerweise benutzt man (in C++) std::atomic<bool> als Variablentyp. 
Volatile mag zwar ein Workarround sein, ist aber streng genommen 
undefined behaviour. Was der Pedant zu atomics in C ist, weiß vielleicht 
jemand anderes.

von loeti2 (Gast)


Lesenswert?

avr schrieb:
> Was der Pedant zu atomics in C ist, weiß vielleicht
> jemand anderes.

C11 Atomics
https://en.cppreference.com/w/c/language/atomic

von db8fs (Gast)


Lesenswert?

Ein Anfänger schrieb:
> Eigentlich ist es ja so, dass sobald sich die Adresse ändert, auch die
> Werte im Struct andere sein können.

Das hat aber mit volatile nix zu tun. Ein volatile Pointer auf 
nicht-volatile Inhalt (int* volatile) - was soll denn das für ein 
Pointer sein bzw. was soll der denn deiner Ansicht nach da passieren? 
Aus meiner Sicht wär das ein gemeinsam genutzter Pointer (z.B. global), 
der durch einen anderen Prozess, Thread oder eine ISR in seinem 
Pointer-Wert verändert wird. Wobei ich das sehr fragwürdig finde, nen 
Pointer, der eh schon die ärmste Sau im C-Dorf ist, weil er alles und 
nix sein kann, dann auch noch mit Nebenläufigkeit zuzumisten -> 
eigentlich sollte, finde ich, doch die Bestrebung sein, 
Nebenläufigkeiten soweit zu reduzieren wie möglich.

Wie gesagt würde ich als sehr esoterisches Konstrukt einstufen sowas.

D.h. die Volatilität müsste sich
> übertragen, sonst rechnet der optimierte Code mit (Register-) Werten aus
> alten Adressen herum. Anderseits müsste streng genommen bei jedem
> Zugriff auf eine Structmember-Variable der Pointer neu dereferenziert
> werden, schließlich ist er volatile gekennzeichnet. Das wäre aber für
> die Performance schlecht und ich kann mir kein sinnvolles Szenario dafür
> vorstellen.

Du haust hier Sachen her, die sich eh beißen - entweder nimmste volatile 
oder nimmst eben die Performance von normalen Pointern. Ein Pointer auf 
ein volatiles Feld macht für genau ein Szenario Sinn: z.B. sowas wie ein 
Flag-Register, dass innerhalb der Hardware geändert wird, wo du bei 2 
Aufrufen unterschiedliche Daten kriegen kannst. Das gehört nicht rein, 
um Threads oder ISRs zu pseudo-'synchronisieren' oder was auch immer - 
sondern nur für den einen Fall. Und weil das so ein tolles Szenario ist, 
sollte deine Funktion, die volatile-Zeug nutzt, das ja auch nur jeweils 
an einer Stelle tun.

> Eigentlich bin ich der Meinung, dass für den Compiler Adressoperationen
> bei Pointern sowieso schon so Art Memory-Barriers sein müssten, weil zur
> Compile-Zeit die Speicheradresse überhaupt nicht bekannt ist. Der
> Compiler kann also nicht auf Registerdaten zurückgreifen (oder ganze
> Blöcke wegoptimieren), sondern muss nach jeder Adressoperation neu
> laden/speichern.

Mal blöd gefragt: was macht eigentlich ein Linker so schönes? Bzw. wie 
entstehen überhaupt die kleinen Adressen? Memory-Barriers - wer soll das 
denn machen, wenn nicht du? Das Betriebssystem oder die C-Runtime? Dann 
kompilier mal C für'n 6510...

> In dem Fall würde mir dann ein einfacher Volatile-Cast (wie peda
> vorgeschlagen hat) reichen, allerdings für den Adresszugriff.

Wie gesagt, Empfehlung: Adress nix volatile, Inhalt flüchtig. 
Weglaufende Adressen nix gut. Sonst eben: viel Spaß beim Debuggen, wenn 
du nicht mal weißt, wer gerade fröhlich deinen Pointer geändert hat.

> Vermutlich hat der Compiler durch die Funktionsverschachtelungen und die
> dynamische Datenstruktur sowieso nie eine Chance dahingehend
> Optimierungen durchzuführen, dann müsste es also auch ohne irgend ein
> volatile zuverlässig funktionieren. Deswegen ja auch die Frage hier.

Guck dir den kleinsten denkbaren Prozessor an, für den es einen 
C-Kompiler gibt und erzeug deinen ASM-Code dafür mit und ohne volatile. 
Aber so wie du glaubst dein Programm mit volatile programmieren zu 
können, glaub ich, wird der Compiler vermutlich so oder so nix mehr groß 
optimieren können - weil da müsste er deine Zugriffe auf die Adresse ja 
irgendwie tracken/referenzieren können.

Wenn du optimieren willst, nimm 'restrict'.

von Oliver S. (oliverso)


Lesenswert?

W.S. schrieb:
> Zu volatile: anders als bei Pascal gibt es bei C kein Modulsystem und
> damit keine Kapselung. Der Compiler kann bei C also nicht selbst
> herausfinden, ob von woanders auf eine Variable zugegriffen werden kann
> oder nicht. Also kann er von selbst auch nicht entscheiden, ob die
> Variable während des Programmablaufes einer Funktion in einer Quelldatei
> (die er gerade übersetzt) von außen geändert werden kann oder nicht. Man
> muß es ihm also sagen und dazu dient das volatile.

Da irrst du (wie so häufig) mal wieder. Der Compiler kann, wie oben 
schon erwähnt wurde, modulübergreifend optimieren und erkennen, ob eine 
Variable in einer anderen Quelldatei geändert werden kann.

volatile braucht es erst, wenn Änderungen auftreten können, die von 
außerhalb der dem Compiler bekannten Welt ausgehen bzw. dorthin 
Seiteneffekte haben. Anders ausgedrückt: wenn die Hardware dem Compiler 
den Boden unterm Arxxx wegzieht.

Und das wäre bei Pascal ganz genauso, wenn das denn überhaupt irgendwie 
mit Hardware interagieren könnte.

Oliver

von db8fs (Gast)


Lesenswert?

Oliver S. schrieb:
> Da irrst du (wie so häufig) mal wieder. Der Compiler kann, wie oben
> schon erwähnt wurde, modulübergreifend optimieren und erkennen, ob eine
> Variable in einer anderen Quelldatei geändert werden kann.

Das macht eher der Linker mit Link-Time-Code.

> volatile braucht es erst, wenn Änderungen auftreten können, die von
> außerhalb der dem Compiler bekannten Welt ausgehen bzw. dorthin
> Seiteneffekte haben. Anders ausgedrückt: wenn die Hardware dem Compiler
> den Boden unterm Arxxx wegzieht.

Dafür ist eben das Szenario volatile struct durchaus auch fragwürdig.

Mal angenommen du hast sowas auf 32-bit-System:
1
struct Foo
2
{
3
   int TollerIntWert;
4
   struct { int c : 16; int d : 16} TollesBitfield;
5
};
6
7
volatile struct Foo* blub = (volatile struct Foo*)(0xADD);

Mal weiter angenommen, 'TollesBitfield' wird eben durch die Hardware im 
Hintergrund an dem Addressoffset verändert, ohne dass der Linker was 
davon mitkriegt. Was er auch nie-niemals mitkriegen kann, weil Inhalte 
vom Speicher halt eben zur Programmlaufzeit erst entstehen (und nicht 
zur Compile/Linking-Time).

Also was passiert beim Zugriff darauf?
-> Erstmal ist klar: cast auf (Foo*) muss her, weil volatile nur 
beschrieben werden darf.

Nun willste aber am Liebsten ne Momentaufnahme der ganzen struct haben.

In C++ könnte man da Foo::operator=() volatile überschreiben und die 
Felder einzeln const_casten und dann auslesen.

-> aber was machste in normalem C zum Kopieren einer struct? Eigentlich 
meistens memcpy:
1
struct Foo kopie;
2
(void) memcpy(&kopie, (void*)blub, sizeof(struct Foo));

Wobei ich mir nicht ganz sicher bin, ob das memcpy hier wirklich in 
jedem Fall gut gehen würde (memcpy weiß ja nix mehr von volatile). Und 
niemand garantiert, dass während des memcpy nicht dein Speicher im 
Hintergrund geändert wird.

Das Problem hätteste aber auch, wenn nur eines der struct-Felder 
volatile wäre.

D.h. das Coding mit volatile und die Sachen, die der Compiler damit 
macht zwingt einen fast dazu, volatile nicht zu benutzen oder wenn, dann 
nur um ganz einfache Daten sich als Wort zu holen, dann zu casten und 
normal zu weiter zu verwenden.

> Und das wäre bei Pascal ganz genauso, wenn das denn überhaupt irgendwie
> mit Hardware interagieren könnte.

Kann's glaube ich. Ich mein mich erinnern zu können, dass es da auch ne 
Art Referenz-Mechanismus gab und sogar auch inline-Asm damit ging.

von Oliver S. (oliverso)


Lesenswert?

db8fs schrieb:
> Oliver S. schrieb:
>> Da irrst du (wie so häufig) mal wieder. Der Compiler kann, wie oben
>> schon erwähnt wurde, modulübergreifend optimieren und erkennen, ob eine
>> Variable in einer anderen Quelldatei geändert werden kann.
>
> Das macht eher der Linker mit Link-Time-Code.

Nee, auch wenn’s link time optimization heißt, ist das doch immer noch 
ein Compiler, der da tätig ist.

Oliver

von db8fs (Gast)


Lesenswert?

Oliver S. schrieb:
> Nee, auch wenn’s link time optimization heißt, ist das doch immer noch
> ein Compiler, der da tätig ist.

Meinste? Ich hatt's irgendwie so in Erinnerung, dass das Visuellste 
aller Studios nur bei vollen Linkerwarnungen unreferenzierte Variablen 
erkennt.

Ist aber auch egal, vermutlich haben wir beide Recht, weil der Compiler 
ja auch irgendeine Instrumentierung da mit reinmehren muss.

von W.S. (Gast)


Lesenswert?

Oliver S. schrieb:
> Da irrst du (wie so häufig) mal wieder. Der Compiler kann, wie oben
> schon erwähnt wurde, modulübergreifend optimieren und erkennen, ob eine
> Variable in einer anderen Quelldatei geändert werden kann.

Ach, du meinst, weil du eine schwammige Ansicht über die Fähigkeiten 
eines C-Compilers (hier zumeist NUR der GCC) hast, würden alle anderen 
irren?

Wenn ein C-Compiler eine Quelldatei übersetzt, dann kann er prinzipiell 
eben nicht wissen, ob es da noch eine andere Quelldatei gibt, die auf 
Zeugs in der aktuellen Datei zugreifen kann oder nicht. Er müßte dazu 
alle anderen (und noch nicht übersetzten) Dateien durchforsten, ob da 
die aktuell zugehörige .h includiert wird oder ob ein 'extern ...' 
irgendwo drinsteht, worüber dann der Zugriff erfolgt. Der Linker hat da 
zwar die Möglichkeit dazu, aber er linkt nur und übersetzt nicht und er 
ist erst dran, wenn die eigentliche Übersetzungsarbeit bereits erledigt 
ist. Schließlich ist er nicht der Compiler. Soviel zu der Realität.

Ich sehe hier immer wieder, daß die C-Fans ihrem GCC geradezu magische 
Fähigkeiten zuschreiben. Nein Oliver, da irrst du dich (wie so häufig) 
mal wieder.

W.S.

von Zeno (Gast)


Lesenswert?

Oliver S. schrieb:
> Und das wäre bei Pascal ganz genauso, wenn das denn überhaupt irgendwie
> mit Hardware interagieren könnte.
Warum sollte Pascal nicht mit Hardware interagieren können? Hier irrst 
offensichtlich Du.

Nur weil bei der µC-Programmierung C der Platzhirsch ist, bedeutet dies 
nicht das andere Programmiersprachen, einschließlich Pascal, nicht mit 
der HW des µC's umgehen können. Ob eine Programmiersprache mit HW 
umgehen kann ist keine Frage der Sprache selbst, sondern es hängt am 
Ende vom Compiler ab, ob er dies entsprechend umsetzen kann.
Free Pascal, RONPAS (basiert auf FPC) oder auch mikroPascal 
(https://www.mikroe.com/mikropascal-avr), um nur mal 3 Beispiele zu 
nennen, können das. Letztere scheinen damit sogar Geld zu verdienen. Es 
gibt sogar Firmen die mit in Pascal geschriebener Software für µC Geld 
verdienen - 
https://www.emotas.de/allgemein/embedded-software-entwicklung-mit-pascal.

Du gehörst ganz offensichtlich zur Gilde derer, die meinen C sei der 
Nabel der Welt und daneben gäbe es nichts Anderes. Gott sei dank ist dem 
nicht so und auch im Bereich der µC-Programmierung ist die Welt bunter 
als manche meinen.

von Thomas Z. (usbman)


Lesenswert?

W.S. schrieb:
> Der Linker hat da
> zwar die Möglichkeit dazu, aber er linkt nur
so ist bei modernen Toolchains eben nicht.
Schon der ziemlich alte Keil C51 hatte die Optimierung zur Linkzeit 
optional zur verfügung. Dann werden die C Files ggv mehrmals übersetzt. 
LTO (LinkTimeOptimization) ist inzwischen ein weitverbreitets Feature.

von Yalu X. (yalu) (Moderator)


Lesenswert?

W.S. schrieb:
> Wenn ein C-Compiler eine Quelldatei übersetzt, dann kann er prinzipiell
> eben nicht wissen, ob es da noch eine andere Quelldatei gibt, die auf
> Zeugs in der aktuellen Datei zugreifen kann oder nicht.

Bei globalen Variablen kann der Compiler – ohne sich alle anderen Module
anzuschauen (also ohne LTO) – nicht wissen, welche dieser Module auf die
Variablen zugreifen. Bei nichtglobalen Variablen weiß der Compiler
sicher, dass keine anderen Module darauf zugreifen. Entsprechendes gilt
für Funktionen.

Das ist in Pascal so, und das ist in C so, da gibt es keinen
Unterschied.

Die "Modulsysteme" von Pascal und C unterscheiden sich im Wesentlichen
nur darin, dass die Interface-Beschreibung bei C in einer vom Menschen
lesbaren .h-Datei und bei Free Pascal in einer binären
.ppu-Datei liegt.

: Bearbeitet durch Moderator
von Zeno (Gast)


Lesenswert?

Yalu X. schrieb:
> Die "Modulsysteme" von Free Pascal und C unterscheiden sich im
> Wesentlichen
> nur darin, dass die Interface-Beschreibung bei C in einer vom Menschen
> lesbaren .h-Datei und bei Free Pascal in einer binären .ppu-Datei liegt.
Die Modulsysteme von Pascal und C unterscheiden sich schon erheblich.
In Pascal reicht es, wenn man die binäre Unit (bei FPC eben die *.ppu) 
in der uses-Liste aufführt, damit der Compiler die exportierten 
Funktionen/Variable der Unit ins Compilat einbinden kann. Voraussetzung 
ist allerdings das die Binärdatei mit der gleichen Compilerversion 
compiliert wurde - das Komponentensystem bei Delphi/Lazarus funktioniert 
genauso, d.h. ich brauche keine Quellen. Die Quellen brauche ich nur, 
wenn ich die Unit neu übersetzen muß, weil sie z.B mit einer anderen 
Compilerversion erzeugt wurde. Dann würde  sie beim ersten Aufruf der 
Unit im Gesamtprojekt neu kompiliert werden. Ich kann die Unit auch in 
mehreren Projektdateien einbinden. Dazu muß ich nich so ein Gehampel wie 
bei den *.h Includedateien machen und im Quelltext solch ein Konstrukt
1
#ifndef __MSP430WARE_ADC10_A_H__
2
#define __MSP430WARE_ADC10_A_H__
3
.
4
.
5
#endif
bemühen, damit ich am Ende nicht den Linker durch mehrfaches Includieren 
ins Schleudern bringe.
Bei C wird eben genau das gemacht was der Bergriff include aussagt. Das 
Bibliothekskonzept bei C ist ein völlig anderes als bei Pascal. Bei C# 
greift man ein ähnliches Konzept wie bei Pascal auf. Aber wen wundert 
es, wenn der Chefentwickler von C#, Anders Hejlsberg, federführend an 
der Entwicklung von Delphi beteiligt war.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Zeno schrieb:
> Die Modulsysteme von Pascal und C unterscheiden sich schon erheblich.
> ...
> Die Quellen brauche ich nur, wenn ich die Unit neu übersetzen muß,
> weil sie z.B mit einer anderen Compilerversion erzeugt wurde.

Wenn der kompilierte Code noch aktuell ist, muss der Quellcode auch in C
nicht neu übersetzt werden. Sowohl in Free Pascal und als auch in C
braucht man in diesem Fall nur das Kompilat (*.o) sowie die
Interface-Beschreibung (*.ppu in Free Pascal bzw. *.h in C). Da sehe ich
keinen relevanten Unterschied.

> Dazu muß ich nich so ein Gehampel wie bei den *.h Includedateien
> machen und im Quelltext solch ein Konstrukt
>
> #ifndef __MSP430WARE_ADC10_A_H__
> #define __MSP430WARE_ADC10_A_H__
> .
> .
> #endif
>
> bemühen, damit ich am Ende nicht den Linker durch mehrfaches Includieren
> ins Schleudern bringe.

Das ist einer der wenigen Fälle, wo man in C tatsächlich mehr tippen
muss als in Pascal. Dieser Nachteil wird aber in vielen anderen
Bereichen wieder mehr als kompensiert. Ganz abgesehen davon ist das
wesentlich kürzere
1
#pragma once

schon seit Langem De-facto-Standard.

> Bei C wird eben genau das gemacht was der Bergriff include aussagt. Das
> Bibliothekskonzept bei C ist ein völlig anderes als bei Pascal.

Das Header-File-Konzept in C ist mächtiger als das PPU-Konzept in
Pascal. Man kann es – wenn man möchte – genau so benutzen wie die PPUs
in Pascal, man kann aber auch ganz andere Dinge damit anstellen, die in
Pascal nicht möglich sind.

Wir schweifen aber langsam immer mehr vom Thread-Thema ab.

von Zeno (Gast)


Lesenswert?

Yalu X. schrieb:
> Das ist einer der wenigen Fälle, wo man in C tatsächlich mehr tippen
Das geht noch nicht einmal um's mehr tippen, das ist einfach nur grottig 
und eine ganz böse Falle für Anfänger, wenn man dieses Konstrukt vergißt 
oder es mangels Erfahrung nicht weiß. Das Üble an der Sache ist das der 
Compilerlauf fehlerfrei ist und der Linker die Grätsche macht. Mit der 
Fehlermeldung "duplicate symbol ..." kann man Anfangs nicht viel 
anfangen, man versteht es schlichtweg nicht.

Yalu X. schrieb:
> Das Header-File-Konzept in C ist mächtiger als das PPU-Konzept in
> Pascal.
Das halte ich für ein Gerücht. Aber das ist ganz gewiß Ansichtssache.

Yalu X. schrieb:
> man kann aber auch ganz andere Dinge damit anstellen, die in
> Pascal nicht möglich sind.
die da wären?

Wenn dieses Headerfilekonzept so toll ist, warum kommt man dann in C# 
davon ab und implementiert ein dem Pascal sehr ähnliches 
Bibliothekskonzept? Die Einbindung der Bibliotheken erfogt dort ähnlich 
wie bei Pascal, sogar mit dem (fast) gleichen Schlüsselwort. Auch der 
interne Aufbau der Bibliotheken ist weit von dem .h/.c-Konzept in C 
entfernt.

von Ein Anfänger (Gast)


Lesenswert?

db8fs schrieb:
> Ein Anfänger schrieb:
>> Eigentlich ist es ja so, dass sobald sich die Adresse ändert, auch die
>> Werte im Struct andere sein können.
>
> Das hat aber mit volatile nix zu tun. Ein volatile Pointer auf
> nicht-volatile Inhalt (int* volatile) - was soll denn das für ein
> Pointer sein bzw. was soll der denn deiner Ansicht nach da passieren?

Ich tu mir ja auch schwer mit dem Thema :-)
Ein kurzes Beispiel, ein Funktionspointer fctPointer wird sowohl in den 
Interrupts als auch in Main einer Funktion zugewiesen und auch genutzt, 
aber durch die Flags garantiert nicht gleichzeitig.
1
int main(void)
2
{
3
//[...]
4
5
  while (1)
6
  {
7
    if(SomeFlag){
8
      fctPointer= &HalloWelt;
9
      fctPointer();
10
    }
11
  }
12
}

Der Compiler könnte jetzt sagen, "aha, der Funktionspointer ändert sich 
ja zwischen den Schleifen gar nicht". Und die Zuweisung vor die 
While-Schleife setzen und nur noch den Aufruf in der Schleife belassen. 
In den Interrupts hat der Pointer aber eine andere Funktion, die 
Optimierung würde also die Programmlogik zerstören. Jetzt ist das sicher 
nicht ideal, den Pointer wieder zu verwenden, aber das soll nur mal ein 
vereinfachtes Beispiel sein.

Ich denke mir einfach, wenn der Compiler eine einfache 
Boolean/Int-Variable wegoptimieren kann, warum dann nicht auch ein 
ganzes verpointertes Structarray, in dem eine Datenstruktur und 
Funtionspointer hinterlegt sind.

> Aus meiner Sicht wär das ein gemeinsam genutzter Pointer (z.B. global),
> der durch einen anderen Prozess, Thread oder eine ISR in seinem
> Pointer-Wert verändert wird. Wobei ich das sehr fragwürdig finde, nen
> Pointer, der eh schon die ärmste Sau im C-Dorf ist, weil er alles und
> nix sein kann, dann auch noch mit Nebenläufigkeit zuzumisten ->
> eigentlich sollte, finde ich, doch die Bestrebung sein,
> Nebenläufigkeiten soweit zu reduzieren wie möglich.

Es geht um eine Loggerfunktionalität, der ein statischer Speicherbereich 
zugewiesen wird. Die Loggerfunktionalität muss dann mit dem Speicher 
auskommen, und auch darin die Konfiguration der unterschiedlichen 
Loggingkanäle. Da die Kanalanzahl und der -typ benutzerspezifisch ist 
und sich auch im Programmablauf ändern könnte (typischerweise im 
UART-Interrupt), muss also auf dem Speicherbereich mit Pointern operiert 
werden. Und dieses Konstrukt gilt es abzusichern.

Wie gesagt, glaube ich mittlerweile, dass sich der Compiler bei Pointern 
sowieso sehr zurückhält mit Optimierungen, einfach weil er die Adressen 
beim Compilieren noch nicht kennt. Dürfte also von vornherein "safe" 
sein.

Yalu X. schrieb:
> W.S. schrieb:
>> Wenn ein C-Compiler eine Quelldatei übersetzt, dann kann er prinzipiell
>> eben nicht wissen, ob es da noch eine andere Quelldatei gibt, die auf
>> Zeugs in der aktuellen Datei zugreifen kann oder nicht.
>
> Bei globalen Variablen kann der Compiler – ohne sich alle anderen Module
> anzuschauen (also ohne LTO) – nicht wissen, welche dieser Module auf die
> Variablen zugreifen. Bei nichtglobalen Variablen weiß der Compiler
> sicher, dass keine anderen Module darauf zugreifen. Entsprechendes gilt
> für Funktionen.
>
> Das ist in Pascal so, und das ist in C so, da gibt es keinen
> Unterschied.

Der Grund, dass man in Pascal kein volatile braucht/kennt, ist, weil 
dort nur lokale Variablen überhaupt optimiert werden. Das kostet ein 
wenig Performance aber vereinfacht Vieles. (Bin nur "ein Anfänger" in c, 
nicht in Pascal).

> Die "Modulsysteme" von Pascal und C unterscheiden sich im Wesentlichen
> nur darin, dass die Interface-Beschreibung bei C in einer vom Menschen
> lesbaren .h-Datei und bei Free Pascal in einer binären
> .ppu-Datei liegt.
Die ppu ist letztlich nur ein Compilerinterna, das braucht den 
Programmierer gar nicht zu interessieren. Die Interface-Beschreibung in 
Pascal-Units liegt in der Quellcode Datei in der (Trommelwirbel..) 
Interface-Section. Eine Unit ist halt einfach die Kombi aus Source und 
Header. Der eigentliche Unterschied ist, dass die c-header in beliebig 
viele Quelldateien eingebunden werden und man dadurch zwischen 
Deklaration und Definition unterscheiden muss und das zu "extern" Orgien 
führt. Bei Pascal-Units kümmert sich der Kompiler darum. Zweitens führt 
der Compiler ein Namespacing durch, man hat also kein Problem mit 
gleichlautenden nicht veröffentlichten Variablen.

Außerdem, dass sich der Pascal-Compiler selbst alle erforderlichen Units 
zusammen sucht und kompiliert, während man dem c-Compiler erstmal sagen 
muss, was er überhaupt vorkompilieren soll. Letzteres ist vielleicht der 
Punkt, auf den W.S. heraus will. Ich verstehe ehrlich gesagt nicht, 
warum c nicht auch ein Unit-System verwendet, kann ja gerne für volle 
Rückwärtskompatibilität parallel zu Headerdateien funktionieren. Aber 
der Komfort ist einfach um so viel größer.

von Oliver S. (oliverso)


Lesenswert?

W.S. schrieb:
> Oliver S. schrieb:
>> Da irrst du (wie so häufig) mal wieder. Der Compiler kann, wie oben
>> schon erwähnt wurde, modulübergreifend optimieren und erkennen, ob eine
>> Variable in einer anderen Quelldatei geändert werden kann.
>
> Ach, du meinst, weil du eine schwammige Ansicht über die Fähigkeiten
> eines C-Compilers (hier zumeist NUR der GCC) hast, würden alle anderen
> irren?

Nein. Ein C-Compiler kann es prinzipiell. Das es nicht alle können, 
spielt dabei keine Rolle.

Was ich aber eigentlich aussagen wollte: volatile hat mit der Fähigkeit 
des Compilers, über die Grenzen von Übersetzungseinheiten hinaus schauen 
zu können, überhaupt nichts zu tun. Auch wenn er es nicht kann, braucht 
es kein volatile, solange die Hardware es nicht erfordert.

Daher geht dein ganzes Geschreibsel über Module und 
Übersetzungseinheiten völlig am Thema vorbei. Du irrst, und das wie 
üblich sowas von gründlich, gründlicher geht gar nicht.

Oliver

von db8fs (Gast)


Lesenswert?

Ein Anfänger schrieb:
> Ich tu mir ja auch schwer mit dem Thema :-)

> Ein kurzes Beispiel, ein Funktionspointer fctPointer wird sowohl in den
> Interrupts als auch in Main einer Funktion zugewiesen und auch genutzt,
> aber durch die Flags garantiert nicht gleichzeitig.
> int main(void)
> {
> //[...]
>   while (1)
>   {
>     if(SomeFlag){
>       fctPointer= &HalloWelt;
>       fctPointer();
>     }
>   }
> }
>
> Der Compiler könnte jetzt sagen, "aha, der Funktionspointer ändert sich
> ja zwischen den Schleifen gar nicht". Und die Zuweisung vor die
> While-Schleife setzen und nur noch den Aufruf in der Schleife belassen.
> In den Interrupts hat der Pointer aber eine andere Funktion, die
> Optimierung würde also die Programmlogik zerstören. Jetzt ist das sicher
> nicht ideal, den Pointer wieder zu verwenden, aber das soll nur mal ein
> vereinfachtes Beispiel sein.

Dein Beispiel ist, glaube ich, weit hergeholt und zeigt ein wenig die 
Herausforderungen bei parallelen Abläufen. Das problematische an deinem 
Beispiel ist, dass dein Kontrollfluss so sogar randomisiert werden kann 
(wenn du nem Pointer zugestehst, sich quasi auch beliebig außerhalb der 
Programmgrenzen ändern zu dürfen...

Was nämlich de facto in deinem Beispiel passiert: Du willst einer ISR 
eine Dependency Injection per Callback/Funktionspointer verpassen. 
Grundsätzlich kann man das machen, auch wenn ISRs "eigentlich" (tm) so 
kurz und so zustandsfrei wie möglich sein sollten. Heißt: idealerweise 
möglichst wenig gemeinsamer Code zwischen ISR und Hauptprogramm.

Selbst dafür brauchste keinen Pointer der volatile ist. Dein 
Funktionspointer-Type wäre ja in deinem Beispiel glaube sowas wie "void 
(* volatile)()" (Argumente fürn Stack jetzt mal weggelassen).

Das ist nicht nötig, denn wenn deine ISR ausgeführt wird, ist ja idR 
irgendein Global-Interrupt-Flag aktiv, was dazu führt, dass über deinen 
Interruptvektor an die Stelle deiner ISR gebrancht wird - das 
Hauptprogramm wird erst am Ende der ISR wieder erreicht.

Heißt: aus Prozessorsicht pseudoserielle Abarbeitung - es wird nicht 
passieren, dass dein Funktions-Pointer zeitgleich von mehreren 
"Threads", "ISRs" oder was auch immer verändert wird.

> Ich denke mir einfach, wenn der Compiler eine einfache
> Boolean/Int-Variable wegoptimieren kann, warum dann nicht auch ein
> ganzes verpointertes Structarray, in dem eine Datenstruktur und
> Funtionspointer hinterlegt sind.

Der Compiler optimiert in erster Linie weg, sofern es die 
Referenzierungen der Variable zulassen - so ist mein Verständnis davon. 
Wenn du ein Array von Structs anlegst und irgendwo auch darauf 
zugreifst, wird da keiner was wegoptimieren.

> Es geht um eine Loggerfunktionalität, der ein statischer Speicherbereich
> zugewiesen wird. Die Loggerfunktionalität muss dann mit dem Speicher
> auskommen, und auch darin die Konfiguration der unterschiedlichen
> Loggingkanäle. Da die Kanalanzahl und der -typ benutzerspezifisch ist
> und sich auch im Programmablauf ändern könnte (typischerweise im
> UART-Interrupt), muss also auf dem Speicherbereich mit Pointern operiert
> werden. Und dieses Konstrukt gilt es abzusichern.

Hmm, ok. Logging ist ja ne querschnittliche Sache, die theoretisch 
überall vom Programm aus genutzt werden kann. Machs nicht zu 
kompliziert, lieber einen Kanal und da nen String-Präfix vor jede Zeile 
der Textausgabe. Das Logging kannste mit Skripten außerhalb deiner 
Appliance vermutlich besser auswerten. Ist zumindest meine Erfahrung mit 
Logging.

Ansonsten würde für dein Logging eigentlich sowas als Funktionspointer 
reichen:
1
void meinTollerLogger(char* const text)
2
{
3
  printf(text);
4
}
5
6
void (*fnLogger)(char* const text) = &meinTollerLogger;
7
8
void main()
9
{
10
  fnLogger("foobar");
11
}

> Wie gesagt, glaube ich mittlerweile, dass sich der Compiler bei Pointern
> sowieso sehr zurückhält mit Optimierungen, einfach weil er die Adressen
> beim Compilieren noch nicht kennt. Dürfte also von vornherein "safe"
> sein.

Optimierungen sind erst relevant, wenn's Probleme gibt. Vorher lohnt 
sichs nicht.

von Wilhelm M. (wimalopaan)


Lesenswert?

Die `volatile`-Qualifizierung wird nur für sog. besondere 
Speicherzellen benötigt.
Sie wird nie benötigt und ist auch falsch für normale Speicherzellen 
im Sinne
des C/C++-Speichermodells: also auch nicht für Variablen mit 
nebenläufigem Zugriff.
Alle Howtos und Tutorials, die derartiges behaupten, sind schlicht 
falsch.

In C/C++ ist es grundsätzlich UB, wenn nebenläufig (mit zwei oder mehr 
Aktivitätsträger oder
auch `main()` und `ISR`) auf dieselben Speicherzellen zugegriffen 
wird, sofern nicht

* atomare Operationen, oder
* eine strenge happens-before Beziehung

garantiert wird.

`volatile` bedeutet nicht atomar!

'volatile' etabliert auch keine strenge happens-before Relation 
zwischen konkurrierenden
Zugriffen auf dieselbe Spiecherzelle. Dies man man nur mit geeigneten 
Synchronisationsprimitiven
wie `mutex` erreichen (zwei oder mehr Aktivitätsträger), oder aber mit 
mit anderen ausreichenden
Mitteln wie Interrupt-Sperre zusammen mit einer Memory-Barrier (sowohl 
Compiler- als auch
CPU-Memory-Barrier). Das letztere ist dann eine implementation-defined 
Variante.

Daher: `volatile` ist nicht-geeignet und - allein eingesetzt - falsch 
für nebenfäufigen Zugriff auf
dieselben Objekte. In C/C++ heisst das dann ein conflict, der wie oben 
gelöst werden muss, um nicht
in UB zu enden.

`volatile` hat die folgenden Eigenschaften:

* kein atomarer Zugriff,
* Verschiebung einer Lese-Operation einer normalen Speicherzelle bzgl. 
`volatile` ist möglich,
* Verschiebung von Operationen auf `volatile` untereinander ist nicht 
möglich.

Damit eignet sich `volatile` nur für Operationen auf memory-mapped 
HW-Registern (und ist auch nur
genau dafür erfunden worden). Denn diese besonderen Speicherzellen 
haben folgende Eigenschaften:

* sie haben einen Seiteneffekt, und
* ihre Werte erscheinen nicht stabil: ein Lesen nach einem Schreiben 
muss nicht denselben Wert ergeben
wie auch zwei aufeinander folgende Lese-Operationen nicht denselben Wert 
ergeben müssen, und
* sie haben eine semantische Abhängigkeit: das Lesen/Schreiben eines 
HW-Registers beeinflusst das
Lesen/Schreiben eines anderen HW-Registers.

(Achtung: in anderen Sprachen als C/C++ wie etwa Java hat `volatile` 
eine andere Bedeutung.)

Für den nebenläufigen Zugriff auf dieselben Variablen / Datenstrukturen 
bleiben also nur

* atomare Datentypen (`_Atomic` bzw. `std::atomic<>`), oder
* explizite Synchronisation durch:
- `pthread_mutex_lock()`/ `pthread_mutex_unlock()` oder `std::mutex` 
oder ähnliche, oder
- explizites Abschalten der Nebenläufigkeit zusammen mit einer 
Memory-Barrier

Für die Kommunikation zwischen `ISR` und `main()` bzw. weiteren `ISR` 
benutzt man daher
eine geeignete Interrupt-Sperre (Abschalten der Nebenläufigkeit) und 
eine Memory-Barrier (immer
eine Compiler-Barrier und falls nötig eine CPU-Barrier). `volatile` ist 
aus den o.g. Gründen hier
falsch.

Diese ganzen Betrachtungen gelten generell und haben erstmal gar nichts 
mit heftigen Optimierungen
eines Compilers zu tun. Aber natürlich werden bestimmte Effekte bei der 
Optimierung und damit
der Ausnutzung der Regeln für normale Speicherzellen besonders 
sichtbar.

Im übrigen bedeutet `_Atomic` oder `std::atomic<>` nicht, dass Operation 
nicht optimiert werden:
Auch manche Operationen bzgl. atomarer Datentypen können zusammengefasst 
werden wie etwa
aufeinanderfolgende Schreiboperationen. Nur tun das die meisten Compiler 
(derzeit) noch nicht.

von A. S. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> In C/C++ ist es grundsätzlich UB, wenn nebenläufig (mit zwei oder mehr
> Aktivitätsträger oder
> auch `main()` und `ISR`) auf dieselben Speicherzellen zugegriffen
> wird, sofern nicht
>
> * atomare Operationen, oder
> * eine strenge happens-before Beziehung
>
> garantiert wird.

Danke für die Ausführung. M.E. wäre sie wert, ein eigener Wiki-Artikel 
zu volatile hier zu werden. Mit 2-3 Quellenangaben/Links, 
(happens-before, UB wenn nebenläufig ...)

von Ein Anfänger (Gast)


Lesenswert?

db8fs schrieb:
> Dein Beispiel ist, glaube ich, weit hergeholt und zeigt ein wenig die
> Herausforderungen bei parallelen Abläufen.
Ich beschreibe es weiter unten nochmal genauer.

> Heißt: aus Prozessorsicht pseudoserielle Abarbeitung - es wird nicht
> passieren, dass dein Funktions-Pointer zeitgleich von mehreren
> "Threads", "ISRs" oder was auch immer verändert wird.

Wie gesagt, finden die Zugriffe in meinem Fall sowieso flaggeschützt 
streng nacheinander statt, meine einzige Sorge ist also das 
Wegoptimieren.

> Wenn du ein Array von Structs anlegst und irgendwo auch darauf
> zugreifst, wird da keiner was wegoptimieren.
Dann müstte es ja passen. Tatsächlich hat auch alles von Anfang an ohne 
volatile funktioniert, aber ich wollte halt sicher gehen.

> Machs nicht zu
> kompliziert, lieber einen Kanal und da nen String-Präfix vor jede Zeile
> der Textausgabe.

Jetzt beschreib ich doch noch mal genauer um was es geht, ich wollte 
hier halt die Diskussion nicht noch mit Details verkomplizieren:
Der Logger ist um Daten zeitlich zu plotten. Der Logger-Unit weist man 
den Speicherbereich zu (sinngemäß loggerInit(&Buffer,BufferLen);) 
anschließend definiert man Kanäle für jeweils einen bestimmten Datentyp 
und übergibt einen Pointer auf die Variable, die zyklisch abgespeichert 
werden soll, sinngemäß loggerAddUInt16Channel(&ADCValue1); 
loggerAddDigitalChannel(&buttonPressed);
Man kann also mehrere Kanäle mit verschiedenen Datentypen definieren, 
der Logger berechnet dann wieviele der Werte (aller Kanäle) in den 
Buffer passen. Die Logger-Unit hat eine Funktion, die man aus dem 
Timerinterrupt zyklisch aufrufen muss. Mit loggerStart(); (was 
typischerweise aus dem UART-Interrupt aufgerufen wird) wird dann die 
Aufzeichnung gestartet, d.h. die Variable, die mit dem Pointer 
referenziert wurde, wird bei jedem Timeraufruf ausgelesen und in das 
Datenarray abgespeichert. Ist der Buffer voll, dann werden die Daten zur 
Anzeige an den PC geschickt.

Die Implementierung ist so, dass in den Anfang des Buffers die Daten für 
die Kanalverwaltung geschrieben werden, das ist ein Struct-Array mit 
Struct für jeden Kanal.
In dem Struct steht 1. der Pointer auf die Zielvariable, 2. die Adresse, 
ab der im Buffer die Daten für diesen Kanal anfangen, 3. ein 
Funktionspointer auf die Loggingfunktion für diesen Datentyp (ein 
Bool-Wert muss ja anders gelesen und abgespeichert werden, wie ein 
32bit-Wert oder ein 8bit-Wert), und 4. eine Hilfsvariable in die 
Bool-Werte reingeshiftet werden und die beim Auslesen als 
Funktionspointer für die verschiedenen Typen verwendet wird.
Hinter dem Structarray fängt dann der Datenbereich für die ganzen Kanäle 
an.
Zum Loggen wird in der Timerfunktion das Kanalarray durchlaufen und für 
jeden Kanal der typspezifische Loggingfunktionspointer aufgerufen.

Also vom Prinzip her nicht übertrieben kompliziert, bzw. wüsste ich 
nicht, wie ich es einfacher machen könnte.

von Wilhelm M. (wimalopaan)


Lesenswert?

Ein Anfänger schrieb:
> Wie gesagt, finden die Zugriffe in meinem Fall sowieso flaggeschützt
> streng nacheinander statt, meine einzige Sorge ist also das
> Wegoptimieren.

Dann muss das Flag ein _Atomic bool bzw. std::atomic<bool> sein (oder 
der entsprechende Typ-Alias), und natürlich muss Dein Algorithmus damit 
den nebenläufigen Zugriff auf die gemeinsamen Datenstrukturen 
verhindern. Und wie oben schon gesagt: `volatile` ist falsch.

Wenn Du alles richtig machst, brauchst Du Dir über das Optimieren keine 
sorgen zu machen ;-) (s.a. 
Beitrag "Re: c volatile -> wann bracht mans wirklich?")

: Bearbeitet durch User
von Εrnst B. (ernst)


Lesenswert?

Wilhelm M. schrieb:
> Und wie oben schon gesagt: `volatile` ist falsch.

Sind wir schon bei 10 Seiten?

Εrnst B. schrieb:
> Es ist einfacher, einem Anfänger ein "dann schreib halt volatile davor"
> hinzuwerfen, anstatt ihm einen 10-Seiten-Aufsatz über die Freiheiten,
> die der C-Standard dem Compiler/Optimizer gewährt, und wie man damit
> umgeht, zu schreiben. Den versteht er dann eh nicht.

von Wilhelm M. (wimalopaan)


Lesenswert?

Εrnst B. schrieb:
> Wilhelm M. schrieb:
>> Und wie oben schon gesagt: `volatile` ist falsch.
>
> Sind wir schon bei 10 Seiten?

Ich denke, das

Beitrag "Re: c volatile -> wann bracht mans wirklich?"

sind weniger als 10 Seiten. Und so schwierig ist es ja auch nicht ...

von Εrnst B. (ernst)


Lesenswert?

Wilhelm M. schrieb:
> Und so schwierig ist es ja auch nicht

Schwierig ist es, gegen die zehntausenden Stunden Video anzukommen, die 
Youtube nur für "Arduino volatile" ausspuckt.

Es ist vermutlich einfacher, das C-Std-Komitee davon zu überzeugen die 
Bedeutung von "volatile" zu ändern, als den Anfängern und 
Arduino-Jüngern ihr Lieblings-Keyword auszutreiben.

Siehst ja, was das ganze Hinreden im Endeffekt beim TE bewirkt hat...
Er bleibt bei "volatile", weil das portabel und bei allen denkbaren 
C-Compilern gleichermaßen falsch ist.

von Wilhelm M. (wimalopaan)


Lesenswert?

Εrnst B. schrieb:

> Siehst ja, was das ganze Hinreden im Endeffekt beim TE bewirkt hat...
> Er bleibt bei "volatile", weil das portabel und bei allen denkbaren
> C-Compilern gleichermaßen falsch ist.

... und er hat vermutlich das `_Atomic` bei seinem flag vergessen ;-)

von Oliver S. (oliverso)


Lesenswert?

Εrnst B. schrieb:
> Er bleibt bei "volatile", weil das portabel und bei allen denkbaren
> C-Compilern gleichermaßen falsch ist.

Mancher bleibt auch bei volatile, weil _Atomic erst ab C11 verfügbar 
ist, und es da erst mal C99 in den produktiven Einsatz schaffen muß.

Oliver

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Wilhelm M. schrieb:
> Und wie oben schon gesagt: `volatile` ist falsch.

Nur gab es für den AVR-GCC lange Zeit nichts anderes. Die atomic.h ist 
erst später hinzugekommen und für Memory-Barrier gibt es gar keine 
anwenderfreundliche Lib.

Die atomic.h ist außerdem falsch, wenn z.B. für eine UART-FIFO kein 
atomarer Zugriff nötig ist, weil Puffer <256Byte. Da reicht die 
Nichtoptimierung in der Main völlig aus. Ich hab mir dafür ein Macro 
geschrieben, was einen Zugriff nach volatile castet:
1
#define IVAR(x)         (*(volatile typeof(x)*)&(x))

Ich wäre auch bereit, ein gleichwertiges Memory-Barrier Macro zu 
benutzen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Εrnst B. schrieb:
>> Er bleibt bei "volatile", weil das portabel und bei allen denkbaren
>> C-Compilern gleichermaßen falsch ist.
>
> Mancher bleibt auch bei volatile, weil _Atomic erst ab C11 verfügbar
> ist, und es da erst mal C99 in den produktiven Einsatz schaffen muß.

Das Problem ist nur, dass `volatile` und `atomic` zwei unterschiedliche 
Dinge sind.

von Wilhelm M. (wimalopaan)


Lesenswert?

Peter D. schrieb:
> Wilhelm M. schrieb:
>> Und wie oben schon gesagt: `volatile` ist falsch.
>
> Nur gab es für den AVR-GCC lange Zeit nichts anderes. Die atomic.h ist
> erst später hinzugekommen und für Memory-Barrier gibt es gar keine
> anwenderfreundliche Lib.

Mag sein: trotzdem ist 'volatile' an der Stelle falsch.

Wenn es kein Macro für die MemBarrier gab, dass muss man es eben zu Fuss 
hinschreiben. sei()/cli() tun das ja im übrigen.

>
> Die atomic.h ist außerdem falsch, wenn z.B. für eine UART-FIFO kein
> atomarer Zugriff nötig ist, weil Puffer <256Byte. Da reicht die
> Nichtoptimierung in der Main völlig aus. Ich hab mir dafür ein Macro
> geschrieben, was einen Zugriff nach volatile castet:
>
1
> #define IVAR(x)         (*(volatile typeof(x)*)&(x))
2
>
>
> Ich wäre auch bereit, ein gleichwertiges Memory-Barrier Macro zu
> benutzen.

s.a. _MemoryBarrier()

von Peter D. (peda)


Lesenswert?

Wilhelm M. schrieb:
> s.a. _MemoryBarrier()

Schön für Dich, daß wenigstens Du weißt, wie diese Funktion definiert 
ist.
Bei mir gibt das eine Fehlermeldung.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Das Problem ist nur, dass `volatile` und `atomic` zwei unterschiedliche
> Dinge sind.

Das ist so. Es gibt ja noch viel mehr falsche Anwendungen für volatile, 
die letzendlich u.a. das C++-Standard-Komite auf die Idee gebracht 
haben, das mehr oder weniger ganz abzuschaffen.

Wilhelm M. schrieb:
> Wenn es kein Macro für die MemBarrier gab, dass muss man es eben zu Fuss
> hinschreiben.

So ist es.

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Peter D. schrieb:
> Wilhelm M. schrieb:
>> s.a. _MemoryBarrier()
>
> Schön für Dich, daß wenigstens Du weißt, wie diese Funktion definiert
> ist.

Du weißt bestimmt auch, dass das kein Funktion ist (sein kann). Es ist 
ein Macro.

> Bei mir gibt das eine Fehlermeldung.

Dann hast Du das include vergessen:

#include <avr/cpufunc.h>

von Zeno (Gast)


Lesenswert?

Ein Anfänger schrieb:
> Ich verstehe ehrlich gesagt nicht,
> warum c nicht auch ein Unit-System verwendet, ...
Weil man es seinerzeit nicht so vorgesehen hatte, aus welchen Gründen 
auch immer.

Eine grundlegende Reform des des C-Dialektes hat eigentlich erst mit C# 
statt gefunden, wenn auch an vielen Stellen nur halbherzig. Es gäbe 
einiges was man in C mal grundlegend aufräumen sollte, um einige 
Stolpersteine mal zu eliminieren. Allerdings schein diesbezüglich der 
Schmerz nicht groß genug zu sein und die C-Programmierer haben sich halt 
mit den Tücken arrangiert und leben damit.

von Zeno (Gast)


Lesenswert?

Oliver S. schrieb:
> Auch wenn er es nicht kann, braucht
> es kein volatile, solange die Hardware es nicht erfordert.
Das hat mit der Hardware nun mal rein gar nichts zu tun.
Das volatile verhindert, einfach ausgedrückt, schlichtweg das der 
Compiler eine Variable einfach wegoptimiert, von der er meint das sie 
überflüssig sei - aus welchen Gründen auch immer.
Der Oliver lese mal hier 
https://de.wikipedia.org/wiki/Volatile_(Informatik) nach. Dort sind auch 
zwei kleine Beispiele aufgeführt, was ein Weglassen von volatile 
bewirken könnte. Mit HW hat das aber alles nichts zu tun.

von Wilhelm M. (wimalopaan)


Lesenswert?

Zeno schrieb:
> Der Oliver lese mal hier
> https://de.wikipedia.org/wiki/Volatile_(Informatik) nach. Dort sind auch
> zwei kleine Beispiele aufgeführt, was ein Weglassen von volatile
> bewirken könnte. Mit HW hat das aber alles nichts zu tun.

Lies Du das hier:

Beitrag "Re: c volatile -> wann bracht mans wirklich?"

von Oliver S. (oliverso)


Lesenswert?

Zeno schrieb:
> Das volatile verhindert, einfach ausgedrückt, schlichtweg das der
> Compiler eine Variable einfach wegoptimiert, von der er meint das sie
> überflüssig sei - aus welchen Gründen auch immer.

Mit wegoptimieren hat das nichts zu tun.

Der Wortlaut aus dem Standard hilft da, wie immer, am besten weiter:
1
"An object that has volatile-qualified type may be modified in ways unknown to the
2
implementation or have other unknown side effects. Therefore any expression referring
3
to such an object shall be evaluated strictly according to the rules of the abstract machine,
4
as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the
5
object shall agree with that prescribed by the abstract machine, except as modified by the
6
unknown factors mentioned previously.134) What constitutes an access to an object that
7
has volatile-qualified type is implementation-defined."

Zeno schrieb:
> Das hat mit der Hardware nun mal rein gar nichts zu tun.

Doch, hat es praktisch immer. "in ways unknown to the
implementation" passiert eigentlich nur bei durch die Hardware 
hervorgerufene Effekte (wie Zählerregister, Multithreading, ISRs, ...)

Oliver

: Bearbeitet durch User
von avr (Gast)


Lesenswert?

Zeno schrieb:
> https://de.wikipedia.org/wiki/Volatile_(Informatik) nach. Dort sind auch
> zwei kleine Beispiele aufgeführt, was ein Weglassen von volatile
> bewirken könnte. Mit HW hat das aber alles nichts zu tun.

Sehr schlechter Wikipediaartikel. Das erste Beispiel ist direkt 
Undefined Behaviour. Volatile führt nicht zu einer Memory Barrier. Je 
nach Architektur wird diese aber für Cachesynchronisation benötigt.

Oliver S. schrieb:
> Das ist so. Es gibt ja noch viel mehr falsche Anwendungen für volatile,
> die letzendlich u.a. das C++-Standard-Komite auf die Idee gebracht
> haben, das mehr oder weniger ganz abzuschaffen.

Deswegen wird langfristig wohl auch was bei Arduino geschehen. 
Read-Modify-Write Ausdrücke sind Deprecated und geben eine entsprechende 
Warnung zurück (https://godbolt.org/z/TKq4rqzjY) In ein paar Jahren gibt 
es Fehlermeldungen und man ist gezwungen einen älteren C++-Standard zu 
verwenden oder eben Atomics zu nutzen.

von Oliver S. (oliverso)


Lesenswert?

avr schrieb:
> Deswegen wird langfristig wohl auch was bei Arduino geschehen.

Das Standardkomitee ist ja wohl doch erst mal zurückgerudert, und denkt 
nochmals drüber nach. Mal schauen, was draus wird.

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Zeno schrieb:
>> https://de.wikipedia.org/wiki/Volatile_(Informatik) nach. Dort sind auch
>> zwei kleine Beispiele aufgeführt, was ein Weglassen von volatile
>> bewirken könnte. Mit HW hat das aber alles nichts zu tun.
>
> Sehr schlechter Wikipediaartikel. Das erste Beispiel ist direkt
> Undefined Behaviour. Volatile führt nicht zu einer Memory Barrier. Je
> nach Architektur wird diese aber für Cachesynchronisation benötigt.

Der Artikel ist zwar schlecht, weil er den Kern nicht erfasst.

Aber das erste Beispiel ist kein UB, so wie es da steht. Einfach weil da 
kein nebenläufiger Zugriff stattfindet bzw. erkennbar ist.

: Bearbeitet durch User
von avr (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Aber das erste Beispiel ist kein UB, so wie es da steht. Einfach weil da
> kein nebenläufiger Zugriff stattfindet bzw. erkennbar ist.

Doch, der wird ja im Text beschrieben. Es soll keine Endlosschleife 
erzeugt werden, weil [aus einem anderen Kontext] status geändert werden 
könnte. Und da keine atomics verwendet werden, ist da ganz klar eine 
Race condition aka UB.

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Wilhelm M. schrieb:
>> Aber das erste Beispiel ist kein UB, so wie es da steht. Einfach weil da
>> kein nebenläufiger Zugriff stattfindet bzw. erkennbar ist.
>
> Doch, der wird ja im Text beschrieben.

Sehe ich nicht. Welchen Satz meinst Du? Wo?

M.E. will der Artikel nur zeigen, dass in diesem Fall die Funktion nicht 
zu einem Noop verkürzt wird. Letztlich genau das, was mein bei 
HW-Registern braucht ...

: Bearbeitet durch User
von Ein Anfänger (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Εrnst B. schrieb:
>
>> Siehst ja, was das ganze Hinreden im Endeffekt beim TE bewirkt hat...
>> Er bleibt bei "volatile", weil das portabel und bei allen denkbaren
>> C-Compilern gleichermaßen falsch ist.
>
> ... und er hat vermutlich das `_Atomic` bei seinem flag vergessen ;-)

Ist denn c da nicht rückwärtskompatibel? Also ein Code mit volatile Flag 
geschrieben vor Einführung des _Atomic wird nach Einführung plötzlich 
speicheroptimiert mit anderen Variablen in eine Speicherstelle 
geschrieben und damit nicht mehr atomar? Ich denke, _Atomic ist auf 
einem höheren Abstraktionslevel angesiedelt, nämlich je nach 
Hardwareunterstützung kann der Compiler auf passende "Datentypen" 
zurückgreifen, spezielle Assembler-Befehle verwenden, oder muss 
Interruts deaktivieren. Zusätzlich muss er die Aufrufe wie auch bei 
volatile in seinen Optimierungen berücksichtigen. Bei Mehrkernen mit 
Cache muss er je ggf. auch noch Hardware-Memory-Barrieren mit 
Cache-Flushes einfügen.

Volatile dagegen scheint nur die Anweisung an den Compiler zu sein: 
Lade/Schreibe wie es da steht. Bei bekannter 
Einkernmikrocontrollerarchitektur sollte das ja reichen.

Εrnst B. schrieb:
> Siehst ja, was das ganze Hinreden im Endeffekt beim TE bewirkt hat...
> Er bleibt bei "volatile", weil das portabel und bei allen denkbaren
> C-Compilern gleichermaßen falsch ist.
Mit _Atomic könnte ich mich schon anfreunden, Kompatibilität ist mir 
aber wichtig, weil ich den Code wenn er fertig ist auch ins Netz stellen 
will. Wie ist denn die Verbreitung von C11? Verwenden die aktuellen 
Compiler c11? Beim gcc hab ich gerade geschaut, da ist das wohl so.
Allerdings unterstützt _Atomic wohl keine Structs. Naja, wenn mans bei 
Pointern sowieso nicht braucht...

von Peter D. (peda)


Lesenswert?

Wilhelm M. schrieb:
> Dann hast Du das include vergessen:
>
> #include <avr/cpufunc.h>

Naja, eigentlich gehört es sich, die nötigen Include mit zu nennen und 
nicht nur die nackte Funktion dem Leser vor den Latz zu knallen.

Wenn ich das richtig verstehe, ist das aber nicht gleichbedeutend mit 
einem volatile cast einer Variablen. Es werden alle Variablen in dem 
Kontext verworfen, also nicht nur die den Interrupt betreffende. Die 
Memory Barrier kann also einen deutlich höheren Codeverbrauch bewirken.

von db8fs (Gast)


Lesenswert?

Oliver S. schrieb:
> Zeno schrieb:
>> Das volatile verhindert, einfach ausgedrückt, schlichtweg das der
>> Compiler eine Variable einfach wegoptimiert, von der er meint das sie
>> überflüssig sei - aus welchen Gründen auch immer.
>
> Mit wegoptimieren hat das nichts zu tun.

Na ja, ganz nichts glaub ich auch wieder nicht. Es ist ja auf jeden Fall 
so, dass Schreiben auf volatile-Variablen billig, auslesen teuer ist 
(teuer im Sinne von expliziter Cast notwendig).

Und das ist glaube ich die ganze Magie dahinter. Was die Optimierung 
betrifft geht es ja meist eher um solche Konstrukte, wo der Compiler 
halt irgendne Heuristik drauf anwendet, um den Code schneller zu machen.

Wenn du halt ne Initialbelegung mit 0 für ne nicht-volatile Variable 
hast und die in nem Schleifenkopf ausführst, könnte z.B. der GCC bei -O3 
eventuell auf die Idee kommen, dass die Schleife gar nicht ausgeführt zu 
werden braucht. Siehe oben erwähnten Artikel das setjmp-Beispiel: 
https://de.wikipedia.org/wiki/Volatile_(Informatik)

Dort ist, soweit ich's versteh, die Notwendigkeit für volatile daher 
gegeben, weil sich durch das Asm-ähnliche Gejumpe eben außerhalb des 
Compilermodells für C bewegt wird. Soll heißen: der C-Compiler kann 
nicht wissen, was da 'schönes' gemacht wird. Ähnlich wirds bei 
inline-Assembly sein. Wenn der Compiler das nicht sehen kann, weils halt 
nicht in sein eigenes Modell für Ausführungen passt - dann ist wohl die 
Notwendigkeit für volatile auch mit gegeben.

> Der Wortlaut aus dem Standard hilft da, wie immer, am besten weiter:
> "An object that has volatile-qualified type may be modified in ways
> unknown to the
> implementation or have other unknown side effects. Therefore any
> expression referring
> to such an object shall be evaluated strictly according to the rules of
> the abstract machine,
> as described in 5.1.2.3. Furthermore, at every sequence point the value
> last stored in the
> object shall agree with that prescribed by the abstract machine, except
> as modified by the
> unknown factors mentioned previously.134) What constitutes an access to
> an object that
> has volatile-qualified type is implementation-defined."


> Zeno schrieb:
>> Das hat mit der Hardware nun mal rein gar nichts zu tun.
>
> Doch, hat es praktisch immer. "in ways unknown to the
> implementation" passiert eigentlich nur bei durch die Hardware
> hervorgerufene Effekte (wie Zählerregister, Multithreading, ISRs, ...)

Na ja, wie beschrieben, eigenes inline-Assembly ist auch SW-seitige 
Programmsteuerung, wo die Hardware erstmal nix für kann - und dennoch 
isses außerhalb des C-Sprachmodells.

von Wilhelm M. (wimalopaan)


Lesenswert?

Peter D. schrieb:
> Wilhelm M. schrieb:
>> Dann hast Du das include vergessen:
>>
>> #include <avr/cpufunc.h>
>
> Naja, eigentlich gehört es sich, die nötigen Include mit zu nennen und
> nicht nur die nackte Funktion dem Leser vor den Latz zu knallen.

Ach Peter, Du kennst Dich doch ganz gut aus, deswegen ...

> Wenn ich das richtig verstehe, ist das aber nicht gleichbedeutend mit
> einem volatile cast einer Variablen.

Genau!

> Es werden alle Variablen in dem
> Kontext verworfen, also nicht nur die den Interrupt betreffende.

Es werden alle Load/Stores von Objekten materialisiert, der Adresse aus 
dem Block entweichen können (s.a. escape analysis eines Compilers). 
Typischerweise in diesem Anwendungsfall also genau die globalen 
Variablen, die in ISR und e.g. main() verwendet werden.

: Bearbeitet durch User
von Ein Anfänger (Gast)


Lesenswert?

avr schrieb:
> Sehr schlechter Wikipediaartikel. Das erste Beispiel ist direkt
> Undefined Behaviour. Volatile führt nicht zu einer Memory Barrier. Je
> nach Architektur wird diese aber für Cachesynchronisation benötigt.

avr schrieb:
> Und da keine atomics verwendet werden, ist da ganz klar eine
> Race condition aka UB.

Die Architektur kann dem Programmierer doch bekannt sein!? Bei 
Einkernern ist dann auch der Cache egal. Und selbst bei Mehrkernern mit 
Cache gäbe es keine Race Condition, weil der zeitliche Ablauf gewahrt 
ist und man von regelmäßigen automatischen Cache-Synchronisationen 
ausgehen kann.

von Oliver S. (oliverso)


Lesenswert?

db8fs schrieb:
> Na ja, ganz nichts glaub ich auch wieder nicht. Es ist ja auf jeden Fall
> so, dass Schreiben auf volatile-Variablen billig, auslesen teuer ist
> (teuer im Sinne von expliziter Cast notwendig).

Warum das?

db8fs schrieb:
> Soll heißen: der C-Compiler kann
> nicht wissen, was da 'schönes' gemacht wird.

Genau das ist alles, was dahintersteckt. Und daher:
"such an object shall be evaluated strictly according to the rules of 
the abstract machine".

Nicht mehr und nicht weniger.

db8fs schrieb:
> Na ja, wie beschrieben, eigenes inline-Assembly ist auch SW-seitige
> Programmsteuerung, wo die Hardware erstmal nix für kann - und dennoch
> isses außerhalb des C-Sprachmodells.

Das stimmt natürlich.

Oliver

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Ein Anfänger schrieb:
> Ist denn c da nicht rückwärtskompatibel? Also ein Code mit volatile Flag
> geschrieben vor Einführung des _Atomic wird nach Einführung plötzlich
> speicheroptimiert mit anderen Variablen in eine Speicherstelle
> geschrieben und damit nicht mehr atomar?

Hast Du

Beitrag "Re: c volatile -> wann bracht mans wirklich?"

gelesen?

`volatile` und `atomar` sind zwei orthogonale Konzepte, d.h. sie haben 
nichts miteinander zu tun.

von Wilhelm M. (wimalopaan)


Lesenswert?

Ein Anfänger schrieb:
> Volatile dagegen scheint nur die Anweisung an den Compiler zu sein:
> Lade/Schreibe wie es da steht.

Ja, im wesentlichen.

> Bei bekannter
> Einkernmikrocontrollerarchitektur sollte das ja reichen.

Garantiert Dir aber kein Atomarität, liefert bei konkurrierendem Zugriff 
trotzdem UB, und verhindert mögliche Optimierungen, die sonst (bei 
normalen Speicherzellen, kein HW-Register) sinnvoll wären.

von Ein Anfänger (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Ein Anfänger schrieb:
>> Ist denn c da nicht rückwärtskompatibel? Also ein Code mit volatile Flag
>> geschrieben vor Einführung des _Atomic wird nach Einführung plötzlich
>> speicheroptimiert mit anderen Variablen in eine Speicherstelle
>> geschrieben und damit nicht mehr atomar?
>
> Hast Du
>
> Beitrag "Re: c volatile -> wann bracht mans wirklich?"
>
> gelesen?
>
> `volatile` und `atomar` sind zwei orthogonale Konzepte, d.h. sie haben
> nichts miteinander zu tun.

Auch 'atomar' geht doch von Nebenläufigkeit aus, sonst bräuchte man den 
atomaren Zugriff ja gar nicht. Und vor 'atomar' konnte man nach Standard 
scheinbar nur auf 'volatile' in Kombination mit anderen Konzepten (z.B. 
target-passenden Datentypen) zurückgreifen, um atomaren garantierten 
Zugriff zu gewährleisten. Wenn Rückwärtskompatibilität zu altem Code 
gegeben sein soll, müsste also volatile für ein Flag weiterhin reichen?

(Ja, hab ich gelesen.)

von Wilhelm M. (wimalopaan)


Lesenswert?

Ein Anfänger schrieb:

> Auch 'atomar' geht doch von Nebenläufigkeit aus, sonst bräuchte man den
> atomaren Zugriff ja gar nicht.

Na klar.
Nur das "auch" ist falsch: `volatile` hat nichts mit Nebenläufigkeit zu 
tun und erfüllt auch keine Garantien dafür.

> Und vor 'atomar' konnte man nach Standard
> scheinbar nur auf 'volatile' in Kombination mit anderen Konzepten (z.B.
> target-passenden Datentypen) zurückgreifen, um atomaren garantierten
> Zugriff zu gewährleisten. Wenn Rückwärtskompatibilität zu altem Code
> gegeben sein soll, müsste also volatile für ein Flag weiterhin reichen?

Wenn kein _Atomic oder std::atomic<> verfügbar ist, dann (wie oben 
beschrieben) explizites Herstellen einer happens-before Relation, also 
hier Abschalten der Nebenläufigkeit zusammen mit Memory-Barrier 
(Compiler und ggf. CPU).

von Wilhelm M. (wimalopaan)


Lesenswert?

Ein Anfänger schrieb:
> (Ja, hab ich gelesen.)

Mmmh, ok, dann hast Du es nicht verstanden.

von avr (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Sehe ich nicht. Welchen Satz meinst Du? Wo?

der Wert der Variable jederzeit ohne expliziten Zugriff im Quelltext 
ändern kann, etwa durch externe Hardware oder **asynchron ausgeführte 
ISRs**

Letzeres ist Nebenläufigkeit. Wenn bei der Variable explizit stehen 
würde, dass sie ausschließlich für ein Statusregister stehen soll, wäre 
ich bei dir.

Ein Anfänger schrieb:
> Die Architektur kann dem Programmierer doch bekannt sein!? Bei
> Einkernern ist dann auch der Cache egal. Und selbst bei Mehrkernern mit
> Cache gäbe es keine Race Condition, weil der zeitliche Ablauf gewahrt
> ist und man von regelmäßigen automatischen Cache-Synchronisationen
> ausgehen kann.

Das ist nicht der Gedanke von Hochsprachen. Die Architektur soll dem 
Programmierer in den meisten Fällen gar nicht interessieren. Man möchte 
portablen Code schreiben, der auch noch funktioniert, wenn man die 
Architektur wechselt. Darum hält man sich sinnvollerweise auch an den 
Standard, der garantiert, dass der Code dann auch noch funktioniert.

Von regelmäßigen Cache-Sychronisationen kann man übrigens nicht 
ausgehen. Ich könnte eine Architektur entwerfen, die dies nur explizit 
macht. Das wäre aus Sicht des Standards vollkommen in Ordnung. Ich werde 
so eine Architektur nicht entwerfen, aber wer garantiert dir, dass das 
nicht irgendjemand in Zukunft macht? Code, der sich nicht an den 
Standard hält, ist dann schlicht nicht portabel.

Nebenbei, Undefined Behaviour bedeutet, dass der Compiler alles machen 
darf. Alles heißt, wenn es im Controller ein Selbstzerstörungsbit gäbe, 
dürfte der Compiler dies bei Undefined Bahaviour setzen. Er dürfte den 
kompletten Ram löschen usw.
Du kannst natürlich sagen, auf meiner Architektur mit diesem Compiler 
scheint es trotzdem zu funktionieren. Aber es bleibt eben ein 
Glücksspiel mit jedem Architektur und Compilerwechsel. Es wird meist 
funktionieren, aber ist das ein Grund auf die einfachen in C11 und C++11 
verfügbaren Mittel zu verzichten, die es garantieren würden?

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Wilhelm M. schrieb:
>> Sehe ich nicht. Welchen Satz meinst Du? Wo?
>
> der Wert der Variable jederzeit ohne expliziten Zugriff im Quelltext
> ändern kann, etwa durch externe Hardware oder **asynchron ausgeführte
> ISRs**

Mag sein, dass der Autor das gemeint hat ...

Der Quelltext allein in dem Beispiel ist aber noch kein UB. Hier fehlt 
schlicht die Deklaration einer ISR (bspw. als Presudo-Code), die das 
erkennen ließe.

von Ein Anfänger (Gast)


Lesenswert?

avr schrieb:
> Ein Anfänger schrieb:
>> Die Architektur kann dem Programmierer doch bekannt sein!? Bei
>> Einkernern ist dann auch der Cache egal. Und selbst bei Mehrkernern mit
>> Cache gäbe es keine Race Condition, weil der zeitliche Ablauf gewahrt
>> ist und man von regelmäßigen automatischen Cache-Synchronisationen
>> ausgehen kann.
>
> Das ist nicht der Gedanke von Hochsprachen. Die Architektur soll dem
> Programmierer in den meisten Fällen gar nicht interessieren.

Ja, stimmt. Nur kann man sich durch global deaktivierte Interrupts (je 
nach Datentyp/Zugriff) auch Probleme einfangen, mit denen man bei einem 
einfachen atomic gar nicht gerechnet hat. c ist ja eine 
Low-Level-Sprache, da könnte es ja noch was 'hardwarenäheres' geben.

> Nebenbei, Undefined Behaviour bedeutet, dass der Compiler alles machen
> darf.
Ja, das versteh ich. War es auch vor c11 schon undefined behaviour?

von Norbert (Gast)


Lesenswert?

Grundsätzlich finde ich diese Diskussion recht interessant.

Ich stelle mir jedoch folgende Frage: Ist volatile (einer einzelnen 
globalen  Variablen) nicht unter Umständen deutlich performanter als der 
Einsatz einer Memory-Barrier?

Bei ›volatile‹ wird ja nur genau diese eine Variable frisch gelesen.
Bei Einsatz einer Memory-Barrier müssen ja alle Variablen welche in 
CPU-Registern ›gecacht‹ sind, neu geladen werden. Gerade bei Prozessoren 
mit einem ganzen Sack voller Register eher unschön und bremsend.

Oder habe ich da irgendwo einen Gedankenfehler?

von Wilhelm M. (wimalopaan)


Lesenswert?

Norbert schrieb:
> Grundsätzlich finde ich diese Diskussion recht interessant.
>
> Ich stelle mir jedoch folgende Frage: Ist volatile (einer einzelnen
> globalen  Variablen) nicht unter Umständen deutlich performanter als der
> Einsatz einer Memory-Barrier?

Nein. Meistens ist die Memory-Barrier besser, weil damit noch 
Optimierungen möglich sind, bei `volatile` aber jeder Zugriff (auch 
Lesen) immer ausgeführt werden muss, weil der Compiler die Werte des 
Objektes nicht als stabil ansehen kann.

> Bei ›volatile‹ wird ja nur genau diese eine Variable frisch gelesen.
> Bei Einsatz einer Memory-Barrier müssen ja alle Variablen welche in
> CPU-Registern ›gecacht‹ sind, neu geladen werden.

Nein.
Die Memory-Barrier erzwingt nur ein load / store eines Objektes, dessen 
Adresse den lokalen Block verlassen haben könnte. Sie kommt damit dem 
Aufruf einer non-inline Funktion gleich (s.a. escape analysis, hatte ich 
oben schon erwähnt).

von db8fs (Gast)


Lesenswert?

Oliver S. schrieb:
> db8fs schrieb:
>> Na ja, ganz nichts glaub ich auch wieder nicht. Es ist ja auf jeden Fall
>> so, dass Schreiben auf volatile-Variablen billig, auslesen teuer ist
>> (teuer im Sinne von expliziter Cast notwendig).
>
> Warum das?

Weil die Werte bei volatile eben nicht stabil sind. Du darfst volatile 
Variablen immer beschreiben, aber grundsätzlich nicht ohne cast einer 
nicht-volatile Variablen zuweisen. Teuer ist das in dem Sinne, dass dein 
Programm damit Nichtdeterminismus zulässt, d.h. es ist in einem 
besonderen Maße von externen Umständen abhängig. Deswegen auch der cast, 
weil damit der Programmierer ja markieren soll, dass er weiß, was er 
damit tut.

Mal paar Beispiele von Abhängigkeiten, von lose gekoppelt zu fester 
gedongelt:
1
// zustandsfreie Funktion (reentrant)
2
int add(int a, int b)
3
{
4
  return a+b;
5
}
6
7
// zustandsbehafte Funktion (nicht-reentrant)
8
int accumulate(int a)
9
{
10
  static int accumulator=0;
11
  accumulator += a;
12
  return accumulator;
13
}

Abstrakt gesehen verhält sich dein volatile ähnlich wie die accumulate: 
2 Aufrufe mit den selben Parametern !=0 kommen unterschiedliche 
Ergebnisse zurück. Ist aber immer noch testbar und deterministisch.

Das Lesen von volatile ist zustandsbehaftet und nichtdeterministisch - 
also gleich 2 Sachen, die man in der Regel nicht unbedingt in gutem Code 
haben will und zur Laufzeit potentiell problematisch sein können.

Norbert schrieb:
> Ich stelle mir jedoch folgende Frage: Ist volatile (einer einzelnen
> globalen  Variablen) nicht unter Umständen deutlich performanter als der
> Einsatz einer Memory-Barrier?

Nein, keine Barrieren (lock-free-programming) ist immer performanter als 
auf irgendwas warten zu müssen. volatile kann das aber nicht leisten.

von avr (Gast)


Lesenswert?

Ein Anfänger schrieb:
> Ja, stimmt. Nur kann man sich durch global deaktivierte Interrupts (je
> nach Datentyp/Zugriff) auch Probleme einfangen, mit denen man bei einem
> einfachen atomic gar nicht gerechnet hat. c ist ja eine
> Low-Level-Sprache, da könnte es ja noch was 'hardwarenäheres' geben.

Ein atomic heißt nicht direkt, dass Interrupts Global deaktiviert 
werden. Das hängt tatsächlich stark von der Architektur ab und welche 
Möglichkeiten der Compiler hat. Aber da bewegen wir uns schon in den 
Bereich der Mikrooptimierung. Wenn das tatsächlich notwendig ist, kann 
man durchaus schauen, mit welchen Mitteln man mehr Performanz erreicht, 
z.B auch mit Inline assembly, aber man sollte sich immer bewusst sein, 
was man tut. In den meisten Fällen kommt es nicht auf die letzte 
Mikrosekunde an und da schreibe ich ich lieber standardkonformen, 
portablen Code. Der ist wiederverwendbar, ich kann ihn auch am PC testen 
und verbringe weniger Zeit mit Debugging.

Als Beispiel: in der ARM Architektur gibt es spezielle Befehle für 
atomare read modify write Zugriffe und der Compiler nutzt diese auch in 
Kombination mit atomics. Wenn man dagegen nur auf ein atomic schreibt 
und dies mit einem Befehl abbildbar ist, dann wird das auch vom Compiler 
so übersetzt. Der einzige Unterschied zu volatile sind in dem Fall die 
zusätzlichen memory barriers, die der Compiler zusätzlich generiert.

>> Nebenbei, Undefined Behaviour bedeutet, dass der Compiler alles machen
>> darf.
> Ja, das versteh ich. War es auch vor c11 schon undefined behaviour?

Volatile war nie für Nebenläufigkeit gedacht, also ja. Ich meine aber in 
C gab es schon vorher pthreads, mit denen man korrekt programmieren 
konnte. In C++ war das dagegen ein Problem, denn es gab vor C++11 kein 
Speichermodell und multithreading war somit allgemein problematisch.

von Ein Anfänger (Gast)


Lesenswert?

avr schrieb:
>>> Nebenbei, Undefined Behaviour bedeutet, dass der Compiler alles machen
>>> darf.
>> Ja, das versteh ich. War es auch vor c11 schon undefined behaviour?
>
> Volatile war nie für Nebenläufigkeit gedacht, also ja. Ich meine aber in
> C gab es schon vorher pthreads, mit denen man korrekt programmieren
> konnte. In C++ war das dagegen ein Problem, denn es gab vor C++11 kein
> Speichermodell und multithreading war somit allgemein problematisch.

Noch mal zum Undefined Behaviour, wo ist denn das undefinierte Verhalten 
definiert? (Unabhängig davon, dass atomic für ein Flag besser geeignet 
ist.)

Wie hier schon mal gepostet wurde heißt es zu volatile im Standard:
"Therefore any expression referring to such an object shall be evaluated 
strictly according to the rules of the abstract machine, as described in 
5.1.2.3."
und in 5.1.2.3.
"In the abstract machine, all expressions are evaluated as specified by 
the semantics."
Ich versteh das so, dass der Compiler genau das tun muss, was im Code 
dasteht, d.h. mit dem gegebenen Datentyp (!) in die Speicherstelle 
schreiben, bzw. an anderer Codestelle diese auslesen. Und dann ist es 
doch egal, ob das ein Interruptflagregister ist, dass sich 
hardwareseitig verändern kann oder ob es sich um eine Speicherstelle im 
RAM handelt. Selbst wenn der Compiler den volatile-'Missbrauch' erkennen 
würde, müsste er gemäß Spezifikation die Speicherstelle auslesen, bzw. 
an der anderen Stelle schreiben. Und so wäre es auch wenn es sprachlich 
im Hinblick auf die Atomarität nicht definiert ist, im Hinblick auf den 
Speicherzugriff doch wohl definiert und mit Kenntnis der Hardware dann 
ein legitimes Konstrukt?

avr schrieb:
> Ein Anfänger schrieb:
>> Ja, stimmt. Nur kann man sich durch global deaktivierte Interrupts (je
>> nach Datentyp/Zugriff) auch Probleme einfangen, mit denen man bei einem
>> einfachen atomic gar nicht gerechnet hat. c ist ja eine
>> Low-Level-Sprache, da könnte es ja noch was 'hardwarenäheres' geben.
>
> Ein atomic heißt nicht direkt, dass Interrupts Global deaktiviert
> werden. Das hängt tatsächlich stark von der Architektur ab und welche
> Möglichkeiten der Compiler hat. Aber da bewegen wir uns schon in den
> Bereich der Mikrooptimierung.

Mir ist klar, dass der Compiler nutzt was er kann. Ich meinte nur, dass 
die Verwendung von atomic ein Rattenschwanz hinter sich herschleifen 
kann (kann), mit der der Programmierer gar nicht rechnet. Wie bspw. die 
Deaktivierug von Interrupts (wenn nötig) und plötzlich stimmen die 
Zeiten einer Port-Messung nicht mehr.

(Ich sprech hier immer vom Einsatz auf einem Mikrocontroller, dass auf 
dem PC Vieles anders ist, ist klar.)

von db8fs (Gast)


Lesenswert?

Ein Anfänger schrieb:
> Ich versteh das so, dass der Compiler genau das tun muss, was im Code
> dasteht, d.h. mit dem gegebenen Datentyp (!) in die Speicherstelle
> schreiben, bzw. an anderer Codestelle diese auslesen.

> Und dann ist es
> doch egal, ob das ein Interruptflagregister ist, dass sich
> hardwareseitig verändern kann oder ob es sich um eine Speicherstelle im
> RAM handelt. Selbst wenn der Compiler den volatile-'Missbrauch' erkennen
> würde, müsste er gemäß Spezifikation die Speicherstelle auslesen, bzw.
> an der anderen Stelle schreiben.

Im Prinzip ja, es ist egal - es ist allerdings auch unnötig bei normalen 
Speicheraddressen und wäre daher zuviel des Guten (DOWN - do only what's 
necessary).

Denn wenn du an der Stelle auf die volatile-Semantik bestehst, 
überdeckst du ja, dass es auch ohne funktionieren würde. Jemand, der 
deinen Code liest - im Zweifelsfall du selber nach x Monaten - könnte 
dann schon dezent stutzig werden und sich leise 'wtf' fragen. Weil 
eigentlich willste ja keine Seiteneffekte im Code haben und 
Missverständlichkeiten sind auch doof, die kriegste selbst bei sauberen 
Code schon teilweise aufgrund blöder Benamung, ungünstiger 
Strukturierung oder Einarbeitungsstand. Daher, immer besser weglassen 
bis auf den Fall der direkten HW-Interaktion.

Btw. grundsätzlich hab ich gegen volatile gar nicht so sehr viel. Ich 
glaub es gab mal nen Alexandrescu-Artikel drüber, wo der das - 
allerdings für C++ - quasi wie const verwendet hat, um damit 
multithreaded Locks zu machen. Ist aber schon "hornbeinalt" der Artikel, 
da gabs noch keinen scoped_lock, keinen std::mutex usw.

Man konnte da volatile-Variablen von ner Klasse anlegen, die waren dann 
unsynchronisiert. Hat man eine normale Instanz der Klasse gebildet (ohne 
volatile), hat die vorher nen Mutex gelocked und unter dem dann die als 
volatile qualifizierten Methoden aufgerufen. Sollte wohl ne Art 
Compile-Time-Erkennung für RaceConditions sein.

Stand glaube bei DrDobbs mal drin.

von Peter D. (peda)


Lesenswert?

Ein Anfänger schrieb:
> Nur kann man sich durch global deaktivierte Interrupts (je
> nach Datentyp/Zugriff) auch Probleme einfangen, mit denen man bei einem
> einfachen atomic gar nicht gerechnet hat.

Eine globale Sperre garantiert die geringste Zeit der Sperre und hat 
daher die geringsten Nebenwirkungen.

Ganz anders dagegen, wenn man nur den betroffenen Interrupt sperrt. Dann 
kann der langsamste Interrupt das Timing vollkommen durcheinander 
bringen und sogar eine Prioritätsinversion erfolgen. Die Nebenwirkungen 
sind daher erheblich und nicht mehr überschaubar.

Einige ARM haben einen recht eleganten Weg der globalen Sperre, man kann 
temporär den Level der aktuellen Task auf den höchsten Wert setzen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Ein Anfänger schrieb:
> Noch mal zum Undefined Behaviour, wo ist denn das undefinierte Verhalten
> definiert? (Unabhängig davon, dass atomic für ein Flag besser geeignet
> ist.)

Zu UB wird oft gesagt, dass alles passieren kann, z.B. auch die HD 
formatieren. DAS ist natürlich Quatsch. UB bedeutet generell einfach, 
dass das Modell der abstrakten Maschine zusammenbricht.

Aber: in diesem Fall des konkurrierenden Lesens (ISR / main()) kann beim 
Lesen / Schreiben der Variablen jeder Zustand entstehen, den man durch 
Permutation der generieren Maschineninstruktionen erhalten kann. Und das 
eben scheinbar(!) zufällig, weil der Interrupt asynchron erfolgt.

von Wilhelm M. (wimalopaan)


Lesenswert?

Peter D. schrieb:
> Einige ARM haben einen recht eleganten Weg der globalen Sperre, man kann
> temporär den Level der aktuellen Task auf den höchsten Wert setzen.

Nennt sich "priority inheritance" bzw. "priority ceiling". Dies braucht 
auch jedes OS, das mit fixen Prioritäten arbeiten kann, um unbegrenzte 
Prioritätsinversion zu vermeiden.

von Wilhelm M. (wimalopaan)


Lesenswert?

Ein Anfänger schrieb:
> Ich versteh das so, dass der Compiler genau das tun muss, was im Code
> dasteht, d.h. mit dem gegebenen Datentyp (!) in die Speicherstelle
> schreiben, bzw. an anderer Codestelle diese auslesen.

Ja, die load/stores werden so in der abtrakten Maschine ausgeführt. In 
der echten Maschine können das eben nicht-atomare Anweisungsfolgen sein. 
Ein reorder volatile-volatile ist nicht möglich. Andere reorder sind 
möglich (z.B. normaales load, bei normalen writes ist das tatsächlich 
IB). (s.a. 
Beitrag "Re: c volatile -> wann bracht mans wirklich?")

> Und dann ist es
> doch egal, ob das ein Interruptflagregister ist, dass sich
> hardwareseitig verändern kann oder ob es sich um eine Speicherstelle im
> RAM handelt.

Nein. Die Zugriffe auf normale Speicherzellen können - und sollten - 
nach der as-if Regel optimiert werden.

> Und so wäre es auch wenn es sprachlich
> im Hinblick auf die Atomarität nicht definiert ist,

das ist so definiert, das eben keine Atomarität gerantiert ist.

> im Hinblick auf den
> Speicherzugriff doch wohl definiert und mit Kenntnis der Hardware dann
> ein legitimes Konstrukt?

Nein. Der Standard sagt ganz klar, das ein konfliktbehafteter, 
konkurrierender Zugriff (read-write, write-read, write-write) UB ist.

von avr (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Zu UB wird oft gesagt, dass alles passieren kann, z.B. auch die HD
> formatieren. DAS ist natürlich Quatsch.

Ich würde das nicht verharmlosen. Ich hatte vor zwei Jahren einen GCC, 
der bei fehlendem return statement kein Rücksprung erzeugte - der 
Programmcounter lief einfach weiter. Da kann dann wirklich alles 
passieren, je nach dem was in den Daten nach der Funktion steht. 
Schlimme Dinge sind extrem unwahrscheinlich, aber nicht unmöglich.

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Wilhelm M. schrieb:
>> Zu UB wird oft gesagt, dass alles passieren kann, z.B. auch die HD
>> formatieren. DAS ist natürlich Quatsch.
>
> Ich würde das nicht verharmlosen.

Das DAS bezog sich doch auf das Formatieren der HD ;-)

> Ich hatte vor zwei Jahren einen GCC,
> der bei fehlendem return statement kein Rücksprung erzeugte - der
> Programmcounter lief einfach weiter.

Sehr schöner und gern genommener Fall von UB ... und ein guter Grund für 
-Werror ;-)

von Wilhelm M. (wimalopaan)


Lesenswert?

db8fs schrieb:
> Ich
> glaub es gab mal nen Alexandrescu-Artikel drüber, wo der das -
> allerdings für C++ - quasi wie const verwendet hat, um damit
> multithreaded Locks zu machen.

Nein, s.u.

> Ist aber schon "hornbeinalt" der Artikel,
> da gabs noch keinen scoped_lock, keinen std::mutex usw.

Nein, hat damit nichts zu tun, RAII-style Locker konnte man sich schon 
immer schreiben.

Der Hintergrund von diesem (m.E. sehr missverständlichem)

https://erdani.org/publications/cuj-02-2001.php.html

Artikel ist einzig der "Missbrauch" von `volatile` als Kennzeichnung
für Datentypen, die geeignet synchronisiert sind und sich damit als
gemeinsame Datenstrukturen für die Verwendung zwischen Threads eignen.

Die Argumentation darin geht dann folgendermaßen:

1) Niemals `volatile` für primitive DT normaler Variablen (solange kein 
MMIO)
verwenden (korrekt!)

2) Da C++ kein syntaktisches Konstrukt hat, um DT für die Verwendung
zwischen Threads auszuzeichnen, braucht es Dokumentation bzw. Expertise.

3) Dokumentation wird nicht gelesen und Expertise ist rar.

Daraus folgert er, dass man das Schlüsselwort `volatile` "missbrauchen" 
kann,
um UDT zu kennzeichnen, die nebenläufig verwendet werden können.

Und zwar:

a) Objekte von UDT, die nebenläufig benutzt werden sollen,
werden `volatile` qualifiziert.

b) Nur die Elementfunktionen, die geeignet synchronisiert sind,
werden `volatile` qualifiziert.

c) In der Klasse werden keine Datenelemente primitiver DT `volatile` 
qualifiziert.

d) Damit können nur die `volatile` Funktionen von den Threads aufgerufen
werden, die non-`volatile` eben nicht.

e) Wird dieser UDT als non-volatile verwendet (eben dann nicht zwischen 
Threads,
siehe a)), können alle Elementfunktionen verwendet werden.

Das Schlimme daran: Regel a) widerspricht der Zielsetzung von 
`volatile`:
denn `volatile` hat keine Garantien, die man für Nebenläufigkeit 
gebrauchen könnte und darf nur für MMIO eingesetzt werden.

Es handelt sich also um den Missbrauch von `volatile` bei UDT!

Die notwendigen Memory-Barrier werden automatisch durch die notwendigen
Mutex-Operationen (lock(), unlock()) in den Elementfunktionen erledigt. 
Die
Atomarität erzeugen die Mutexe.

Die Idee ist nach langer Diskussion ad-acta gelegt worden, weil sie 
einfach
Mist ist. Wie man sieht, wurde sie auch nicht in die stdlib aufgenommen. 
Die
UDT e.g. std::vector<> der stdlib können nicht `volatile` deklariert 
werden,
weil sie keine solchen `volatile` Elementfunktionen haben.

Denn (zur Wiederholung): `volatile` ist nur für MMIO!!!

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

avr schrieb:
> Ich hatte vor zwei Jahren einen GCC,
> der bei fehlendem return statement kein Rücksprung erzeugte - der
> Programmcounter lief einfach weiter.

Dann war das kein C-Compiler laut Standard. Die schließende Klammer 
einer Funktion ist implizit ein return.
Das return darf nur entfallen, wenn es eine Endlosschleife ist 
(unerreichbarer Code).
Vermutlich hast Du die Funktion als naked definiert und somit der 
Verantwortung des Compilers entzogen.

: Bearbeitet durch User
von avr (Gast)


Lesenswert?

Nein, es war eine ganz normale Funktion ohne irgendwelchen 
compilerspezifischen Attribute. Es gab eine Warnung Missing return 
statement und ich dachte auch erst, das kann daran nicht liegen. Im 
assembly hat klar der Rücksprung gefehlt. Ich weiß leider nicht mehr 
welche gcc Version es war - nachdem ich gelesen hatte, dass fehlende 
return statements UB sind, habe ich von einem Bugreport abgesehen.

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Nein, es war eine ganz normale Funktion ohne irgendwelchen
> compilerspezifischen Attribute. Es gab eine Warnung Missing return
> statement und ich dachte auch erst, das kann daran nicht liegen. Im
> assembly hat klar der Rücksprung gefehlt.

Deswegen: -Werror

Und: in C darf in einer non-void Funktion das return fehlen, wenn der 
(nicht vorhandene) Wert im Aufrufer nicht benutzt wird. Andernfalls ist 
es UB und der Code darf Amok laufen (UB). In C++ ist es immer UB.

von Peter D. (peda)


Lesenswert?

avr schrieb:
> Es gab eine Warnung Missing return
> statement und ich dachte auch erst, das kann daran nicht liegen.

Ein Compiler warnt nie grundlos. Poste mal einen compilierbaren Code mit 
dieser Funktion und die vollständige Warnung.

von Oliver S. (oliverso)


Lesenswert?

Peter D. schrieb:
> Dann war das kein C-Compiler laut Standard. Die schließende Klammer
> einer Funktion ist implizit ein return.

Bei einer void Funktion. Wenn die aber einen Wert zurückgibt, gibt es 
kein implizites return.

avr schrieb:
> Ich hatte vor zwei Jahren einen GCC,
> der bei fehlendem return statement kein Rücksprung erzeugte

https://pvs-studio.com/en/blog/posts/cpp/0917/

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Peter D. schrieb:
>> Dann war das kein C-Compiler laut Standard. Die schließende Klammer
>> einer Funktion ist implizit ein return.
>
> Bei einer void Funktion. Wenn die aber einen Wert zurückgibt, gibt es
> kein implizites return.

Es geht um C, nicht um C++:

Beitrag "Re: c volatile -> wann bracht mans wirklich?"

> avr schrieb:
>> Ich hatte vor zwei Jahren einen GCC,
>> der bei fehlendem return statement kein Rücksprung erzeugte
>
> https://pvs-studio.com/en/blog/posts/cpp/0917/

Beitrag "Re: c volatile -> wann bracht mans wirklich?"

von db8fs (Gast)


Lesenswert?

Wilhelm M. schrieb:
> db8fs schrieb:
>> Ich
>> glaub es gab mal nen Alexandrescu-Artikel drüber, wo der das -
>> allerdings für C++ - quasi wie const verwendet hat, um damit
>> multithreaded Locks zu machen.
>
> Nein, s.u.

Doch, halt wie du selber erkannt hast, eben über den ScopedLock in 
Verbindung damit.

> Nein, hat damit nichts zu tun, RAII-style Locker konnte man sich schon
> immer schreiben.

Ich geb dir recht, dass volatile von der oben diskutierten Semantik dann 
eben weg ist - nämlich der, nur externe Sachen zu machen. Annahme war 
halt, dass volatile für class/struct relativ frei user-definierbar ist.

Heißt also, dass es da keine wirkliche Vorgabe seitens des Standards gab 
dafür - demzufolge stehts ja dem Implementierer frei, das zu machen.


> Artikel ist einzig der "Missbrauch" von `volatile` als Kennzeichnung
> für Datentypen, die geeignet synchronisiert sind und sich damit als
> gemeinsame Datenstrukturen für die Verwendung zwischen Threads eignen.

Jo, und warum auch nicht? Wie oft hat man's schon gesehen, dass Klassen 
so zugemistet sind und ohne Sinn und Verstand Multi-Threaded verwendet 
werden.

Tatsächlich fehlt mir in C++ manchmal sowas, um Klassen markieren zu 
können, die explizit Adapter zwischen mehreren Threadkontexten 
darstellen.

> Die Idee ist nach langer Diskussion ad-acta gelegt worden, weil sie
> einfach
> Mist ist. Wie man sieht, wurde sie auch nicht in die stdlib aufgenommen.

Volatile ist in C++ bloß für POD relevant, für structs/classes halt 
nicht. Wenn's was besseres gäbe für aktive Klassen, könnte man das auch 
nehmen.

> Die
> UDT e.g. std::vector<> der stdlib können nicht `volatile` deklariert
> werden,
> weil sie keine solchen `volatile` Elementfunktionen haben.

Richtig, wobei man ab und zu schon mal mit Lock 'Threadsafe'-gemachte 
Container in der Praxis schon gesehen hatte. Solange es auch Lock-free 
geht, vermisse ich das aber auch nicht.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Es geht um C, nicht um C++:

Das aus deinem Munde ;)

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

db8fs schrieb:
> Tatsächlich fehlt mir in C++ manchmal sowas, um Klassen markieren zu
> können, die explizit Adapter zwischen mehreren Threadkontexten
> darstellen.

Geht nicht, weil Threads Laufzeitkonstrukte sind. Was Du aber willst ist 
eine statische Prüfung.

Was aber geht und gemacht, ist zuzusichern, dass unsynchronisierte 
Elementfunktionen bspw. nur vom besitzenden Thread aufgerufen werden.

von avr (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Es geht um C, nicht um C++:

Mein Beispiel war C++.

Peter D. schrieb:
> Ein Compiler warnt nie grundlos.

Grundlos nie, aber oft doch ohne, dass es gleich UB wird. Ihr müsst mir 
aber nicht erzählen, dass man Werror nutzen soll. Ich plädiere in der 
Firma schon länger dafür, aber das durchzusetzen ist nicht unbedingt 
ganz einfach.

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Wilhelm M. schrieb:
>> Es geht um C, nicht um C++:
>
> Mein Beispiel war C++.

Das war nicht zu erkennen.
Jedoch ist es dann eben immer UB!

von avr (Gast)


Lesenswert?

Wilhelm M. schrieb:
> avr schrieb:
>> Wilhelm M. schrieb:
>>> Es geht um C, nicht um C++:
>>
>> Mein Beispiel war C++.
>
> Das war nicht zu erkennen.
> Jedoch ist es dann eben immer UB!

Naja, offensichtlich schon wenn man etwas Ahnung hat. Das mit UB eh 
klar.

von db8fs (Gast)


Lesenswert?

Ein Compiler warnt nie grundlos. Poste mal einen compilierbaren Code mit
dieser Funktion und die vollständige Warnung.

von avr (Gast)


Lesenswert?

avr schrieb:
> Naja, offensichtlich schon wenn man etwas Ahnung hat. Das mit UB eh
> klar.

Das hier ist ein anderer aber

db8fs schrieb:
> Ein Compiler warnt nie grundlos. Poste mal einen compilierbaren Code mit
> dieser Funktion und die vollständige Warnung.

Zu meinem Beispiel ist alles gesagt, inklusive, dass es UB ist. Es war 
auch nur, um zu zeigen, dass UB zu Amoklaufenden Code führen kann. Für 
weiteres hat Oliver auch einen Link gepostet.

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Grundlos nie, aber oft doch ohne, dass es gleich UB wird. Ihr müsst mir
> aber nicht erzählen, dass man Werror nutzen soll.

Habe ich trotzdem gemacht ;-)

Was kommen denn da für schwachsinnige Gegenargumente?

: Bearbeitet durch User
von avr (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Was kommen denn da für schwachsinnige Gegenargumente?

Keine Lust Warnungen zu entfernen. Da geht es nicht um Sinn und 
Verstand.

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Keine Lust Warnungen zu entfernen.

Na, das ist schwachsinnig.

von avr (Gast)


Lesenswert?

Wilhelm M. schrieb:
> avr schrieb:
>> Keine Lust Warnungen zu entfernen.
>
> Na, das ist schwachsinnig.

Dein Leben ist schwachsinnig

von Wilhelm M. (wimalopaan)


Lesenswert?

avr schrieb:
> Wilhelm M. schrieb:
>> avr schrieb:
>>> Keine Lust Warnungen zu entfernen.
>>
>> Na, das ist schwachsinnig.
>
> Dein Leben ist schwachsinnig

Na endlich kommen die Ausfälligkeiten ... ich dachte schon, es ist das 
falsche Forum ;-)

von avr (Gast)


Lesenswert?

avr schrieb:
> Dein Leben ist schwachsinnig

Kann man Mal die IP Adresse von dem Troll sperren?

Wilhelm M. schrieb:
> avr schrieb:
>> Keine Lust Warnungen zu entfernen.
>
> Na, das ist schwachsinnig.

Und ja, da sind wir uns einig.

von W.S. (Gast)


Lesenswert?

Ach, ihr kreischt euch ja noch immer gegenseitig an.

Habt ihr zwischendurch auch mal daran gedacht, wozu 'volatile' 
eigentlich gut sein soll?

Nun, das Optimieren des zu erzeugenden Maschinencodes wird gerade (aber 
nicht nur) beim GCC gern so betrieben, daß er gelesene Daten gern in 
einem der CPU-Register zwischenspeichert, so daß er bei einer erneuten 
Verwendung die eigentliche Quelle nicht nochmal lesen muß. Wenn nun nach 
dem ersten Lesen sich etwas an den Daten der Quelle ändert, kriegt das 
der erzeugte Maschinencode nicht mit. Um sowas zu verhindern, teilt man 
ihm mittels 'volatile' mit, daß dem so sein kann und daß er folglich 
Maschinencode erzeugt, der die Daten von besagter Quelle jedesmal erneut 
liest.

So.

Das alles hat aber überhaupt nichts zu tun mit dem Regeln des Zugriffs 
mehrerer Instanzen auf die gleichen Variablen. Sowas ist Sache des 
Programmierers.

Und nun kommt mal wieder von euren diversen Palmen herunter.
Und lernt zu verstehen, wie euer GCC funktioniert ohne ihm geradezu 
magische Fähigkeiten zuzuschreiben.

W.S.

von A. S. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Deswegen: -Werror

Off topic hier: schaltest Du dann nur die schlimmsten Warnungen ein, 
z.b. mit -Wall? Oder hast Du verschiedene Läufe (Analyse und Build) mit 
verschiedenen extra-Warnungen. Oder ein paar ignores?

von Wilhelm M. (wimalopaan)


Lesenswert?

W.S. schrieb:
> Habt ihr zwischendurch auch mal daran gedacht, wozu 'volatile'
> eigentlich gut sein soll?

Ach komm, Du hast meine Beiträge doch auch gelesen, oder?

von Wilhelm M. (wimalopaan)


Lesenswert?

A. S. schrieb:
> Wilhelm M. schrieb:
>> Deswegen: -Werror
>
> Off topic hier: schaltest Du dann nur die schlimmsten Warnungen ein,
> z.b. mit -Wall? Oder hast Du verschiedene Läufe (Analyse und Build) mit
> verschiedenen extra-Warnungen. Oder ein paar ignores?

Die policy hier ist recht stringent: ggf. viele weitere Warnungen
neben -Werror -Wall -Wextra -Wstrict-aliasing=1 -Wconversion -Wswitch 
-Wswitch-enum -Wshift-count-overflow (-Waddr-space-convert) und dann 
gezielt Plattform/Compiler-spezifische ignores, die mit einem 
verifying-example abgesichert sein müssen.

von Purzel H. (hacky)


Lesenswert?

Interessant, da werden wegen maximal schwachen Konstruktionen, zB 
volatilen Pointergeschichten, C/Pascal Kriege ausgegraben...

Bei Volatile geht's um Interrupts. Interrupts sollten maximal kurz sein. 
Also eher nicht Poiner auf Struct. Dort kommt man eigentlich nur hin, 
wenn man ein RTOS selbst schreibt, und den Stack und Heap wechseln muss.

Mein Pascal Compiler kennt zB kein volatile, er ist zu faul, in einem 
Interrupt verwendete Variablen zu markieren, er optimiert solche 
Konstrukte auch nicht. Etwas mitdenken sollte man auch noch.

von Wilhelm M. (wimalopaan)


Lesenswert?

Purzel H. schrieb:
> Bei Volatile geht's um Interrupts.

Immer noch nein: s.a. 
Beitrag "Re: c volatile -> wann bracht mans wirklich?"

> Interrupts sollten maximal kurz sein.

Hat mit volatile nichts zu tun.

> Also eher nicht Poiner auf Struct.

Bezugslos.

> Dort kommt man eigentlich nur hin,
> wenn man ein RTOS selbst schreibt, und den Stack und Heap wechseln muss.

Nö.

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.