Hi,
ausgehend von single core Mikrocontrollern:
Muss man strikt alle Variablen die zwischen Kontexten geteilt werden mit
volatile qualifizieren?
Z.B. Interrupt erzeugt Daten und legt sie in einen Ringpuffer. Hier habe
ich schon oft gesehen, dass head und tail volatile sind aber das
Datenarray nicht. Das wundert mich, scheint aber zu funktionieren.
Gibt es hier Ausnahmen, dass man das Array im Beispiel nicht volatil
markieren muss?
Beispielcode:
1
volatileuint32_thead,tail;
2
chardata[1];// not volatile
3
4
main()
5
{
6
while(head-tail!=0){
7
charrx=data[tail++];// correct access to data guaranteed?
volatily schrieb:> Muss man strikt alle Variablen die zwischen Kontexten geteilt werden mit> volatile qualifizieren?
Ja, und auch die Atomizität der Zugriffe sicherstellen.
volatily schrieb:> Gibt es hier Ausnahmen, dass man das Array im Beispiel nicht volatil> markieren muss?
Nein. Keine, die nicht unter bestimmten Bedingungen den Erwartungen
eines allwissenden Beobachters (dem, der es debuggt), widersprechen
würden.
volatily schrieb:> Hier habe> ich schon oft gesehen, dass head und tail volatile sind aber das> Datenarray nicht. Das wundert mich, scheint aber zu funktionieren.
Das tut es in vielen Situationen auch. Aber nicht immer.
Im Speziellen ist es so z.B. undefiniert, was zwei Lesezugriffe auf den
selben Wert, zwischen denen ein Interrupt stattfindet, als Ergebnis
liefern.
Heiko L. schrieb:> Das tut es in vielen Situationen auch. Aber nicht immer.> Im Speziellen ist es so z.B. undefiniert, was zwei Lesezugriffe auf den> selben Wert, zwischen denen ein Interrupt stattfindet, als Ergebnis> liefern.
Beim vom TO genannten Ringpuffer stellt der Code normalerweise sicher,
dass der Main-Teil nur Daten liest, die vom Interrupt nicht verändert
werden. Darum reicht es, nur Schreib- und Lesezeiger zu sichern
(atomarer Zugriff, volatile).
Beim Beispiel des TO (char rx = data[tail++];) wär ich mir aber nicht
sicher, ob das OK ist, da zwischen Lesen und Incrementieren kein
Sequence-Point liegt. Nacheinander schon:
1
charrx=data[tail];
2
tail++;
3
/* unbeachtet der Fehler/Überläufe beim TO-Code */
Ich empfehle die Zugriffe ausschließlich mit den standardisierten
Funktionen fur explizites Lesen und Schreiben durchzuführen, das ist
allgemein verständlicher.
A. S. schrieb:> Beim vom TO genannten Ringpuffer stellt der Code normalerweise sicher,> dass der Main-Teil nur Daten liest, die vom Interrupt nicht verändert> werden. Darum reicht es, nur Schreib- und Lesezeiger zu sichern> (atomarer Zugriff, volatile).>> Beim Beispiel des TO (char rx = data[tail++];) wär ich mir aber nicht> sicher, ob das OK ist, da zwischen Lesen und Incrementieren kein> Sequence-Point liegt. Nacheinander schon:
Sequence-Points haben damit gar nichts zu tun. Man bräuchte eher eine
Memory Barrier, um das sicherzustellen.
A. S. schrieb:> Beim vom TO genannten Ringpuffer stellt der Code normalerweise sicher,> dass der Main-Teil nur Daten liest, die vom Interrupt nicht verändert> werden. Darum reicht es, nur Schreib- und Lesezeiger zu sichern> (atomarer Zugriff, volatile).
Im ersten Moment dachte ich "richtig", aber:
Woher weißt du, dass der Compiler nicht den Wert der letzten Schleife
noch in einem Register hat? So, wie es da steht, ist data[1] nur ein
char. Und auch, wenn es mehr wären - woher wüsste man, dass er nicht die
letzten X Werte irgendwo herumschleppt?
Edit: Es ist erschreckend, wie weit man kommt, ohne individuelle
Besonderheiten auch nur eines Blickes zu würdigen. :)
1. Continue supporting the time-honored usage of volatile to load and store variables that are used for shared memory, signal handling, setjmp / longjmp, or other external modifications such as special hardware support.
6
7
2. Deprecate [other useless stuff ...]
Also alles weiterhin problemlos. Volatile in der einzig nützlichen Form
soll erhalten beliben für genau den Zweck für den es schon seit Anbeginn
der Zeit gedacht war und für den wir es auch verwenden.
Rolf M. schrieb:> Das heißt, in Zukunft müssen sämtliche I/O-Register eines µCs als> std::atomic-Spezialisierungen definiert sein statt volatile?
Nein, atomic und volatile sind unterschiedliche Dinge.
Johann L. schrieb:> volatile ist deprecated in C++20, was ist da der Ersatz?
Das ist doch eine Ente.
Ich glaube, man sollte nur für HW-Register noch volatile benutzen.
Ansonsten std::atomic usw. - ein volatile-Konstrukt, das unbemerkt die
Plattform wechselt und dann auf einmal nicht mehr atomar ist, dürfte ein
ärgerlicher Fehler sein.
Heiko L. schrieb:> Johann L. schrieb:>> volatile ist deprecated in C++20, was ist da der Ersatz?>> Das ist doch eine Ente.>
Leute, lest doch mal genau!
Heiko L. schrieb:> ein volatile-Konstrukt, das unbemerkt die> Plattform wechselt und dann auf einmal nicht mehr atomar ist, dürfte ein> ärgerlicher Fehler sein.
Wer sich auf undefiniertes Verhalten verlässt, hat es nicht besser
verdient.
Jemand schrieb:> std::atomic
atomic und volatile sind zwei komplett andere Schuhe. Volatile hat
nichts mit atomic zu tun.
* Volatile bedeutet: "laden/speichern dieser Variable gilt als IO, muss
also genau in der Reihenfolge und genauso oft stattfinden wie im
Quelltext angegeben."
* Atomic bedeutet: "Wenn das gemacht wird dann wird es in einem einzigen
Rutsch geschehen und nichts kann mittendrin dazwischen kommen."
Zwei grundverschiedene Dinge also, es ist mir schleierhaft warum das
immer wieder verwechselt wird.
Wilhelm M. schrieb:> Nein, atomic und volatile sind unterschiedliche Dinge.
Das ist schon klar. Ich hatte auf die diese Aussage geantwortet:
Jemand schrieb:> Johann L. schrieb:>> volatile ist deprecated in C++20, was ist da der Ersatz?>> std::atomic und stdatomic.h in C (feiert bald zehnjähriges Jubiläum)
Die klang für mich, als sei es ganz selbstverständlich, dass man
pauschal volatile nicht mehr nutzt und durch std::atomic ersetzt.
Bernd K. schrieb:> atomic und volatile sind zwei komplett andere Schuhe. Volatile hat> nichts mit atomic zu tun.>> * Volatile bedeutet: "laden/speichern dieser Variable gilt als IO, muss> also genau in der Reihenfolge und genauso oft stattfinden wie im> Quelltext angegeben.">> * Atomic bedeutet: "Wenn das gemacht wird dann wird es in einem einzigen> Rutsch geschehen und nichts kann mittendrin dazwischen kommen.">> Zwei grundverschiedene Dinge also, es ist mir schleierhaft warum das> immer wieder verwechselt wird.
Ja, offensichtlich hast du noch erhebliche Verständnisprobleme mit
std::atomic.
Problematisch dürfte sein, dass std::atomic z.T. mit locks arbeitet, die
natürlich für interrupts nicht taugen. Andererseits soll eine
atomic_signal_fence unter Windows z.B. einfach überhaupt gar nichts tun.
Ein Dilemma.
Bernd K. schrieb:> * Volatile bedeutet: "laden/speichern dieser Variable gilt als IO, muss> also genau in der Reihenfolge und genauso oft stattfinden wie im> Quelltext angegeben."
Nope. Du bestaetigt hier nur, warum viele Anwendungen von volatile
deprected sein sollen.
"Continue supporting the time-honored usage of volatile to load and
store variables that are used for shared memory, signal handling, setjmp
/ longjmp, or other external modifications such as special hardware
support."
volatile Variable koennen sich abseits des Programmflusses aendern
(wegen IO oder ISR usw.) und duerfen daher nicht in Registern gehalten
werden. Das hat nichts mit einer Reihenfolge zu tun.
Was du hier meinst sind "memory barriers".
leo
Heiko L. schrieb:> Im ersten Moment dachte ich "richtig", aber:
Uuups, ja ich fürchte, Ihr habt recht und ich schrieb absoluten
Blödsinn.
Vermutlich wird es kaum zu Problemen führen, wäre aber eine gefährliche
Zeitbombe.
Ich behaupte darum nun das Gegenteil: Die Daten im Ringpuffer müssten
ebenso volatile sein.
Vermutlich schadet das auch kaum (wird der Code kaum langsamer oder
größer): Gelesen werden muss sowieso und wird meist eh nur einmal.
leo schrieb:> volatile Variable koennen sich abseits des Programmflusses aendern> (wegen IO oder ISR usw.) und duerfen daher nicht in Registern gehalten> werden. Das hat nichts mit einer Reihenfolge zu tun.
Alle Volatile-Zugriffe müssen so erfolgen wie sie im Code stehen - auch
in der Reihenfolge. Und neben File-I/O ist das das einzige überhaupt,
was das tatsächlich für das Programm vorgeschriebene Verhalten
definiert.
leo schrieb:> Bernd K. schrieb:>> * Volatile bedeutet: "laden/speichern dieser Variable gilt als IO, muss>> also genau in der Reihenfolge und genauso oft stattfinden wie im>> Quelltext angegeben.">> Nope. Du bestaetigt hier nur, [...]
Doch, doch, genau so wie ich schreibe ist es definiert. Mit volatile
werden erwünschte Seiteneffekte markiert, diese Seiteneffekte sind die
einzigen Punkte an denen eine reale Maschine an die abstrakte Maschine
gebunden ist, ansonsten darf die reale Maschine Kapriolen schlagen oder
ganze Programmteile weglassen und stattdessen Däumchen drehen,
Hauptsache alle definierten Seiteneffekte finden immer noch genau so (in
der exakten Anzahl und Reihenfolge) statt wie sie in der abstrakten
Maschine stattfinden. Normalerweise ist das nur Datei-I/O und der
Rückgabewert von main(), mit volatile kann man zusätzliche solche
Schnittstellen definieren.
Wilhelm M. schrieb:> Ich habe mittlerweile alles schon umgestellt, da ich C++20 (c++2a) schon> verwende. Insgesamt finde ich es sogar gut, weil die Semantik nun klarer> ist
Auf was hast du den jetzt das, was berechtigterweise „formerly known as
volatile“ war, umgestellt?
Oliver
Oliver S. schrieb:> Auf was hast du den jetzt das, was berechtigterweise „formerly known as> volatile“ war, umgestellt?
Einfach auf die zusammengesetzten op= (+=, etc.) und
pre/post-increment/decrement für volatiles verzichtet, und die volatile
gekennzeichneten Elementfunktionen heraus genommen. Alles ersetzt durch
die gleichwertigen Anweisungen.
Nicht ganz konsequent habe ich nur teilweise volatile_load<> und
volatile_store<> als vocabulary-functions eingebaut. Ist zwar ganz gut,
um die Semantik klar zu machen. Andererseits ist ja noch nicht klar, was
in C++23 kommen wird.
Ich hole das Thema nochmal raus:
Soll man jetzt (mit C++20) std::atomic benutzen statt volatile für
Variablen, die zwischen normalen und Interrupts geteilt werden? Für
signal handler ist volatile ja noch "erlaubt", obwohl man technisch auch
dort std::atomic benutzen kann, wenn is_always_lock_free == true.
Ich habe das mal ausprobiert:
Dies erzeugt den gleichen Code als wenn a volatile statt std::atomic
wäre:
1
std::atomic<int>a;
2
...
3
intb=a.load(std::memory_order_relaxed);
4
a.store(std::memory_order_relaxed);
Dies erzeugt dagegen "dmb ish" Instruktionen um das laden/schreiben
herum, also eine memory barrier.
1
std::atomic<int>a;
2
...
3
intb=a;
4
a=b;
Wilder direkter Zugriff auf volatile Variablen will ich nicht mehr,
daher gibt es wohl 2 Möglichkeiten:
1
std::atomic<int>::load(std::memory_order_relaxed)
2
volatile_load()
Die atomic Variante gefällt mir besser, da sie Teil vom C++ Standard
ist.
volatily schrieb:> Soll man jetzt (mit C++20) std::atomic benutzen statt volatile für> Variablen, die zwischen normalen und Interrupts geteilt werden?
C++20 ändert nichts an der Bedeutung von volatile und Atomic. Es wird
nur drüber nachgedacht, die Verwendung von volatile einzuschränken.
Atomic und volatile sind zwei unabhängige Baustellen, die je nach
Anwendungsfall einzeln oder auch kombiniert werden können bzw. müssen.
Oliver
Oliver S. schrieb:> C++20 ändert nichts an der Bedeutung von volatile und Atomic. Es wird> nur drüber nachgedacht, die Verwendung von volatile einzuschränken.
Stimmt, hilft mir aber nicht weiter.
Standardfall auf embedded: Im Interrupt wird eine volatile Variable
beschrieben und im nicht Interrupt Context wird diese gelesen.
-> Sollte man hier stattdessen std::atomic<T> benutzen (für T <= int)?
-> Mit dem "volatile" will man einen Seiteneffekt ausdrücken (und wenn
man nicht doof ist, nimmt man auch einen atomar schreib-/lesbaren Typ).
Beides wird letztendlich ebenso durch
std::atomic<T>::load(std::memory_order_relaxed) ausgedrückt.
-> Geschmackssache?
-> Wenn die Variable größer als int ist, geht std::atomic<T> nicht.
Du hast den Thread gelesen? Wenn nicht, machen.
Was genau hast du an den folgenden Aussagen, die völlig unabhängig von
C++20 gelten, nicht verstanden?
Wilhelm M. schrieb:> Nein, atomic und volatile sind unterschiedliche Dinge.Bernd K. schrieb:> atomic und volatile sind zwei komplett andere Schuhe. Volatile hat> nichts mit atomic zu tun.Oliver S. schrieb:> Atomic und volatile sind zwei unabhängige Baustellen, die je nach> Anwendungsfall einzeln oder auch kombiniert werden können bzw. müssen.
Nicht umsonst gibt es std::atomic<T>::load auch "in volatile".
https://en.cppreference.com/w/cpp/atomic/atomic/load
Oliver
volatily schrieb:> -> Wenn die Variable größer als int ist, geht std::atomic<T> nicht.
Ach so, Nachtrag: Die Anforderung ist "is_trivially_copyable". Das hat
mit int oder nicht nichts zu tun.
Oliver
Oliver S. schrieb:> Du hast den Thread gelesen? Wenn nicht, machen.>> Was genau hast du an den folgenden Aussagen, die völlig unabhängig von> C++20 gelten, nicht verstanden?
Die Frage war aber:
>Standardfall auf embedded: Im Interrupt wird eine volatile Variable beschrieben
und im nicht Interrupt Context wird diese gelesen.
>-> Sollte man hier stattdessen std::atomic<T> benutzen?
volatily schrieb:> -> Mit dem "volatile" will man einen Seiteneffekt ausdrücken (und wenn> man nicht doof ist, nimmt man auch einen atomar schreib-/lesbaren Typ).> Beides wird letztendlich ebenso durch> std::atomic<T>::load(std::memory_order_relaxed) ausgedrückt.
std::memory_order_relaxed ist die einzige memory-order, die volatile
nicht ersetzen kann. Mit den anderen Varianten geht das, und die bieten
dabei schon eine genauere Memory-Order-Kontrolle als volatile.
Ob es das in deinem "Standardfall"
> Im Interrupt wird eine volatile Variable> beschrieben und im nicht Interrupt Context wird diese gelesen.
braucht, kommt auf deine Anwendung drauf an.
Oliver
Hallo,
ich hatte das schon einmal hier angesprochen.
Beitrag "avr-gcc-10 - "volatile deprecated [-Wvolatile]" Warnungen"
Wenn ich die C++ 20 Neuerungen so verfolge, dann kann ich eigentlich nur
mit dem Kopf schütteln. Ich habe nichts gegen die Neuerungen selbst.
Aber für das Wie fehlt mir doch das Verständnis. Ich habe immer öfters
das Gefühl das die Macher zu tief im Detail stecken und sich verzetteln
und gar nicht mehr das große Ganze sehen. Sprich die blenden die oberste
Anwenderschicht komplett aus. Eine Hochsprache sollte das Ziel haben
einfacher zu programmieren. Sein Problem zu beschreiben.
Das nahm mit "volatile deprecated" bei mir den Anfang wo mir das so
richtig auffiel. Ich kann in obigen Thread alle vernünftigen Antworten
lesen so oft ich möchte. Ist mir alles klar. Sofort danach kommt dennoch
wieder völliges Unverständnis warum das Problem dennoch so komisch
gelöst wurde. Sprich, Kurzschreibweise für volatile Variablen verboten.
Das macht kein Sinn. Der Compiler weiß trotz Kurzschreibweise wie er mit
den Variablen umzugehen hat. Deswegen ist es ja nur eine
Kurzschreibweise für eine klare Operation. Die Operation an sich ändert
sich dadurch nicht. Dennoch wird das mit volatile Variablen verboten.
Ich kann da heute noch nur den Kopfschütteln. Ich weiß auch warum. Weil
sich der Syntax dadurch verkompliziert und sogar Fehleranfälliger wird
wenn man alle Variablen doppelt schreiben muss.
1
variable[2].max=variable[2].max+irgendwas;
2
statt
3
variable[2].max+=irgendwas;
Und stellt euch das länger vor, ihr müßt den Index ändern und vergesst
irgendwann irgendwo eine Änderung. Viel Spass beim suchen.
Heute las ich vom Feature sicherer Vergleich von Ganzzahlen.
https://www.heise.de/developer/artikel/Sicherer-Vergleich-von-Ganzzahlen-in-C-20-4967401.html
Finde ich gut. Aber die Umsetzung ist wieder völlig daneben. Da wurde
wieder ein wüstes Syntaxkonstrukt erfunden, kann man kaum noch schreiben
geschweige denn später jemals wieder vernünftig lesen. Auch hier fragte
ich mich warum das nicht unter Haube mit bestehenden Syntax korrigiert
wird. Dabei hätte man gleich das UB von signed Variablen beheben können.
Weil das ist aus Programmierersicht völlig unlogisch warum die nicht
korrekt überlaufen sollen dürfen. Die Begründung kenne ich. Eine
positive Zahl darf niemald negativ werden. Alles klar. Aber
Programmierung hat nicht immer was mit 100% Mathe zu tun. Wenn ein
Wertebereich von negativ bis positiv reicht, dann kennt der
Programmierer den Bereich. Der ist nun einmal klar definiert. Und da
geht man eigentlich davon aus, dass der Wert am Rand auf den anderen
Rand überspringt. Ist aus Programmierersicht völlig logisch. Genauso
logisch wie unsigned von max auf 0 springt beim Überlauf. Was aus
Mathesichtweise wiederum unlogisch ist. Leider wird einmal mit Mathe
begründet und dann wieder nicht. Das ist unlogisch!
Aus Mathesichtweise ist unser gewöhnlicher Syntax
1
a=a+b;
auch unlogisch. Da sagt auch niemand etwas.
Im Moment kann ich die immer komplizierter werdende Syntaxentwicklung
nicht gut heißen. Ne, wirklich nicht. Wenn das so weiter geht besteht
jede Zeile nur noch aus Hunderten Klammern und mehreren Operatorennamen.
Wo früher einfach ein Operator Symbol gereicht hat. Damit wird
irgendwann vielleicht Assembler doch für jeden einfacher zu beherrschen
wie C++. ;-) Vom Gefühl her läuft das wie mit dem Partikelfilter beim
Auto ab. Es wird ein Problem um ein Problem herum gebaut. Aber intern
nicht gelöst. So sehr ich C++ gern habe, so sehr ärgert mich diese
Entwicklung.
Veit D. schrieb:> Heute las ich vom Feature sicherer Vergleich von Ganzzahlen.> https://www.heise.de/developer/artikel/Sicherer-Vergleich-von-Ganzzahlen-in-C-20-4967401.html> Finde ich gut. Aber die Umsetzung ist wieder völlig daneben. Da wurde> wieder ein wüstes Syntaxkonstrukt erfunden, kann man kaum noch schreiben> geschweige denn später jemals wieder vernünftig lesen. Auch hier fragte> ich mich warum das nicht unter Haube mit bestehenden Syntax korrigiert> wird.
Weil dadurch bestehender Code inkompatibel wird. Aber ja, das finde ich
auch ziemlich hässlich und macht den Code schlechter lesbar. Ich halte
mich da lieber an die einfache Regel, signed und unsigned nie zu
mischen. gcc warnt auch bei solchen Vergleichen.
> Dabei hätte man gleich das UB von signed Variablen beheben können.> Weil das ist aus Programmierersicht völlig unlogisch warum die nicht> korrekt überlaufen sollen dürfen.
Die Frage ist, was du unter "korrekt überlaufen" verstehst. Da gibt es
nämlich verschiedene Varianten. (neben wrap-around z.B. saturierend oder
als trap).
> Und da geht man eigentlich davon aus, dass der Wert am Rand auf den anderen> Rand überspringt. Ist aus Programmierersicht völlig logisch. Genauso logisch> wie unsigned von max auf 0 springt beim Überlauf.
In C++ gibt es eigentlich gar keine Überläufe. Es wird dort einfach
definiert, dass unsigned-Arithmetik immer modulo größter darstelbarer
Wert +1 gerechnet wird. Diesen Modulo muss man halt nicht hinschreiben,
sondern er ist immer implizit mit dabei. Dass dadurch der Wert "vom
einen Rand auf den anderen" springt, ist eigentlich nur ein Folgeeffekt
davon. Wenn man das für signed-Rechnung genauso definiert hätte, dann
käme dabei allerdings kein Sprung zum entgegengesetzten Ende des
Wertebereichs raus, sondern wie bei unsigned nach 0.
> Was aus Mathesichtweise wiederum unlogisch ist. Leider wird einmal mit Mathe> begründet und dann wieder nicht. Das ist unlogisch!
Nö. Man muss die Mathe, die dahinter steckt, nur kennen 😀.
> Aus Mathesichtweise ist unser gewöhnlicher Syntax a = a + b ;> auch unlogisch.
Wenn b = 0 ist, dann ist das auch in der Mathematik logisch :)
Aber generell ist halt das = nicht das selbe, wie das in der Mathematik.
Und außerdem sind in der Mathematik Variablen streng genommen eigentlich
konstant.
Veit D. schrieb:> Aber die Umsetzung ist wieder völlig daneben.
Was wäre denn deine Lösung?
Veit D. schrieb:> Dabei hätte man gleich das UB von signed Variablen beheben können.
Echt? Wie denn?
Veit D. schrieb:> Weil das ist aus Programmierersicht völlig unlogisch warum die nicht> korrekt überlaufen sollen dürfen.
Für mich ist das aktuelle Verhalten logisch.
Veit D. schrieb:> Die Begründung kenne ich. Eine positive Zahl darf niemald negativ werden.
Was? Lies dir den Satz nochmal durch und überprüfe deine "Logik".
Veit D. schrieb:> Alles klar. Aber Programmierung hat nicht immer was mit 100% Mathe zu> tun. Wenn ein Wertebereich von negativ bis positiv reicht, dann kennt> der Programmierer den Bereich. Der ist nun einmal klar definiert. Und> da geht man eigentlich davon aus, dass der Wert am Rand auf den anderen> Rand überspringt. Ist aus Programmierersicht völlig logisch.
Das magst du vielleicht logisch finden, ich nicht! Warum sollte 127 + 1
== -128 sein?
Veit D. schrieb:> Genauso logisch wie unsigned von max auf 0 springt beim Überlauf. Was> aus Mathesichtweise wiederum unlogisch ist. Leider wird einmal mit Mathe> begründet und dann wieder nicht. Das ist unlogisch!
Warum ist das aus "Mathesicht" unlogisch? Und wo genau steht diese
Begründung?
Hallo,
Danke Rolf für die versöhnlichen und verständlichen Worte. Ich hatte das
angestaute Bedürfnis aus Anwendersicht auf heranrollende Probleme
hinzuweisen. An die Inkompatibilitätsprobleme hatte ich so tief nicht
gedacht. Hatte so als Idee das man die Angabe std=c++17 bzw. std=c++20
dafür als "Schalter" nutzen hätte können. Vielleicht denke ich manchmal
zu einfach. Am Verständis von diesem "einfach" trennt sich die Spreu vom
Weizen. ;-)
Damit verabschiede ich mich mit dem Ausspruch von Joey Kelly. "Wenn es
einfach nicht geht, geht es einfach nicht".
In diesem Sinn. Fröhliches Programmieren ...
Veit D. schrieb:> Damit wird> irgendwann vielleicht Assembler doch für jeden einfacher zu beherrschen> wie C++. ;-)
Das war eigentlich schon immer so, seitdem es C++ überhaupt gibt...
OK, die Maschinen sind auch wesentlich komplexer geworden und
dementsprechend die Assembler. Man muss auch hier sehr viel
Hintergrundwissen haben, um den Assembler kompetent nutzen und
effiziente Programme damit schreiben zu können.
Aber der ganze Kram ist immer noch deutlich einfacher zu überschauen als
C++...
Hallo,
die Vorlieben hängen sicherlich in erster Linie damit zusammen mit
welcher Sprache man angefangen hat und ob einem diese liegt. Wenn alles
zusammenpasst ist Gut. Egal welche Sprache man programmiert. Was einfach
und was kompliziert ist liegt immer im Auge des Betrachters.
Du bist Assembler Verfechter. Okay. Wissen alle im Forum.
Dennoch musst du anderen zugestehen wenn sie andere Sprachen pflegen.
Das muss möglich sein. Ich bitte darum.
Auch wenn ich mich hier zu C++ kritisch geäußerst habe, bin ich noch
weit davon entfernt auf Assembler umzuschwenken. Das mache ich schon
deshalb nicht, damit du nicht in meinem Schatten stehen musst ... :-)
:-) :-)
mh schrieb:> Veit D. schrieb:>> Dabei hätte man gleich das UB von signed Variablen beheben können.>> Echt? Wie denn?
Indem man aus UB Implementation defined macht und dabei die Methoden
anbietet, die 99.99% der Compiler der letzten 20 Jahre sowieso
implementiert haben. Z.B. der Überlauf im zweierkomplement.
Veit D. schrieb:> Da sagt auch niemand etwas.
Sprachen der Algol-Tradition, damit auch Pascal und andere Nachfahren,
verwenden eben deshalb a := b + c.
C ist weniger an Theorie angeleht als an Pragmatik, und als Folge davon
auch C++. Zuweisung ist häufiger als Vergleich und := ist auf einer
US-Tastatur ziemlich beknackt zu tippen.
A. S. schrieb:> mh schrieb:>> Veit D. schrieb:>>> Dabei hätte man gleich das UB von signed Variablen beheben können.>>>> Echt? Wie denn?>> Indem man aus UB Implementation defined macht und dabei die Methoden> anbietet, die 99.99% der Compiler der letzten 20 Jahre sowieso> implementiert haben. Z.B. der Überlauf im zweierkomplement.
Damit machst du meine Programme aber langsamer, da der Compiler weniger
Möglichkeiten hat zu optimieren. Und was soll der Vorteil davon genau
sein? In geschätzt™ 99.999% der Fälle ist der signed integer overflow eh
ein Fehler, bei dem der Compiler dann nicht mehr warnen kann, weil es ja
gewollt sein könnte.
mh schrieb:> Damit machst du meine Programme aber langsamer, da der Compiler weniger> Möglichkeiten hat zu optimieren.
Kannst Du da ein (echtes) Beispiel nennen?
mh schrieb:> Das ist nix was ich neu aufschreiben müsste, da gibts genug im Internet,> z.B.> https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-enables.html
Danke für den Link.
Ich hab vielleicht was übersehen, aber gälte nicht jedes einzelne
Beispiel genauso für unsigned?
Warum ist das bei signed evil, bei unsigned gut?
Das sind m.E. die zweierlei Maß, die Inkonsistenzen, von denen Veit D
spricht.
A. S. schrieb:> Ich hab vielleicht was übersehen, aber gälte nicht jedes einzelne> Beispiel genauso für unsigned?
Nein, weil für unsigned nicht immer gilt
1
x<x+1
Wir können natürlich unsigned integer overflow zum UB machen, dann
machen wir allerdings alle unglücklich, die den Overflow ausnutzen.
Ich bin übrigens noch immer an Argumenten interessiert, die für ein
definiertes Verhalten beim signed integer overflow sprechen.
mh schrieb:> A. S. schrieb:>> Ich hab vielleicht was übersehen, aber gälte nicht jedes einzelne>> Beispiel genauso für unsigned?>> Nein, weil für unsigned nicht immer gilt: x < x + 1
Dann habe ich mich falsch ausgedrückt, sorry:
Verlinkt waren Beispiele, warum UB bei signed von Vorteil ist.
Hätte UB bei unsigned nicht die gleichen Vorteile?
Warum ist das eine dann UB, das andere OK?
Oder habe ich einen Vor-/Nachteil übersehen?
Oder ist es bewusst unterschiedlich, aus performance-Gründen? Das wäre
idiotisch, weil unsigned sehr häufig Vorteile hat, z.B. bei % oder /2^n.
Oder bräuchte man einen dritten typen, unsigned ohne überlauf? Damit man
all die Optmimierungs-Vorteile (die ja wirklich da sind) auch für einen
dann nochmal performanteren unsigned hat?
wird für signed optimiert, aber nicht für unsigned.
C++ failed hier den Programmierer.
Das hätte man besser über verschiedene Datentypen lösen können (aber
nicht zwischen signed und unsigned). Unsigned wird schließlich oft
benutzt wenn ein Abstand aka positive Länge ausgedrückt wird (z.B.
size_t überall in std:: libs).
volatily schrieb:> Interessant> (x * 10) / 5>> wird für signed optimiert, aber nicht für unsigned.> C++ failed hier den Programmierer.
Dann nen mal mindestens eine Sprache, die das besser macht. Alternativ
kannst du erklären was und wie die Sprache C++ das hier besser machen
kann.
volatily schrieb:> Das hätte man besser über verschiedene Datentypen lösen können (aber> nicht zwischen signed und unsigned). Unsigned wird schließlich oft> benutzt wenn ein Abstand aka positive Länge ausgedrückt wird (z.B.> size_t überall in std:: libs).
signed und unsigned sind unterschiedliche Datentypen? Was hat die
Benutzung als Länge damit zu tun?
Kommen wir mal auf das eigentliche Thema zurück.
Oliver S. schrieb:> volatily schrieb:>> -> Mit dem "volatile" will man einen Seiteneffekt ausdrücken (und wenn>> man nicht doof ist, nimmt man auch einen atomar schreib-/lesbaren Typ).>> Beides wird letztendlich ebenso durch>> std::atomic<T>::load(std::memory_order_relaxed) ausgedrückt.>> std::memory_order_relaxed ist die einzige memory-order, die volatile> nicht ersetzen kann. Mit den anderen Varianten geht das, und die bieten> dabei schon eine genauere Memory-Order-Kontrolle als volatile.> Oliver
Ich glaube es mittlerweile verstanden zu haben :D
-volatile verhindert Compiler Reordering zwischen volatiles und jeder
load/store muss so auftauchen wie er im code steht (ist ja auch für
hardware register gedacht)
-std::atomic<T>::load()/store() mit mo_relaxed eignet sich um 1
Variable zwischen ISR und nicht ISR zu teilen
-werden mehrere std::atomic<T> mit mo_relaxed zwischen ISR und nicht
ISR geteilt, darf der compiler die load/stores umsortieren, daher wäre
das UB
-korrekt kann man mehrere Variablen teilen indem man mo_release und
mo_acquire benutzt, das erzeugt eine Compiler Barrier und eine Memory
Barrier ("dmb ish" Befehl). Hier müssen dann auch gar nicht mehr alle
Variablen std::atomic<T> sein, sondern nur die zum synchronisieren.
Zusätzlich:
-Memory barrier wird für ARMsingle core eigentlich nicht benötigt
-Für den Fall dass man mehrere Variablen zwischen ISR und nicht ISR
teilen will (und es nur 1 Kern gibt), könnte man mehrere std::atomic<T>
mit mo_relaxed + Compiler barrier benutzen um das Reordering zu
verbieten. Dadurch spart man sich die Memory Barrier Befehle.
mh schrieb:> Alternativ kannst du erklären was und wie die Sprache C++ das hier> besser machen kann.
Du hast gesagt, dass Du UB bei signed gut findest, weil der Compiler
dann optimieren kann
Daher ging die Frage an Dich, warum unsigned anders (nicht optimierbar)
sein soll.
Die Alternative (Ausgangslage) war ja, signed Implementation Defined
überlaufen zu lassen.
Wenn Du dagegen, dann kann man doch nach Deinen Gründen dafür fragen.
volatily schrieb:> Kommen wir mal auf das eigentliche Thema zurück.
War klar ...
volatily schrieb:> Ich glaube es mittlerweile verstanden zu haben :D
Nope ...
A. S. schrieb:> mh schrieb:>> Alternativ kannst du erklären was und wie die Sprache C++ das hier>> besser machen kann.>> Du hast gesagt, dass Du UB bei signed gut findest, weil der Compiler> dann optimieren kann>> Daher ging die Frage an Dich, warum unsigned anders (nicht optimierbar)> sein soll.>> Die Alternative (Ausgangslage) war ja, signed Implementation Defined> überlaufen zu lassen.>> Wenn Du dagegen, dann kann man doch nach Deinen Gründen dafür fragen.
Es soll nicht geändert werden, weil es nicht geändert werden kann!
Niemand, außer ihr, kommt auch nur auf die Idee. Habt ihr ne idee, was
das für ein Aufwand wäre, nur die Compiler zu ändern? Über die C++
Software, die in den letzten 35 Jahren entstanden ist und dieses
Verhalten bewusst oder unbewusst ausnutzt, reden wir gar nicht erst.
Dann sind da noch all die existierenden Bücher, Lehrmaterialien,
Standards, Dokumentationen, ...
Ihr habt bis jetzt auch noch keinen Grund genannt, warum etwas geändert
werden sollte (außer "mag ich nicht").
volatily schrieb:> -werden mehrere std::atomic<T> mit mo_relaxed zwischen ISR und nicht> ISR geteilt, darf der compiler die load/stores umsortieren, daher wäre> das UB
Das macht dann vielleicht nicht das, was du möchtest, aber UB wird das
dadurch nicht.
Oliver
Oliver S. schrieb:> Das macht dann vielleicht nicht das, was du möchtest, aber UB wird das> dadurch nicht.
Es sei denn, es verursacht ne race condition.