Forum: Compiler & IDEs volatile qualifier und interrupts in C/C++


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von volatily (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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
volatile uint32_t head, tail;
2
char data[1];     // not volatile
3
4
main()
5
{
6
    while (head - tail != 0) {
7
        char rx = data[tail++];        // correct access to data guaranteed?
8
        ...
9
    }
10
}
11
12
void ISR()
13
{
14
    data[head++] = ...;
15
    ...
16
}

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
von A. S. (achs)


Bewertung
1 lesenswert
nicht lesenswert
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
char rx = data[tail]; 
2
tail++;
3
/* unbeachtet der Fehler/Überläufe beim TO-Code */

von Jemand (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Ich empfehle die Zugriffe ausschließlich mit den standardisierten 
Funktionen fur explizites Lesen und Schreiben durchzuführen, das ist 
allgemein verständlicher.

von Rolf M. (rmagnus)


Bewertung
1 lesenswert
nicht lesenswert
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.

von Heiko L. (zer0)


Bewertung
1 lesenswert
nicht lesenswert
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. :)

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Bewertung
1 lesenswert
nicht lesenswert
volatile ist deprecated in C++20, was ist da der Ersatz?

von Jemand (Gast)


Bewertung
-3 lesenswert
nicht lesenswert
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)

von Spongebob (Gast)


Bewertung
0 lesenswert
nicht lesenswert

von Rolf M. (rmagnus)


Bewertung
1 lesenswert
nicht lesenswert
Das heißt, in Zukunft müssen sämtliche I/O-Register eines µCs als 
std::atomic-Spezialisierungen definiert sein statt volatile?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Bewertung
3 lesenswert
nicht lesenswert
Johann L. schrieb:
> volatile ist deprecated in C++20, was ist da der Ersatz?

Nein, nur spezielle Operationen, deren Semantik nicht klar ist.

von Bernd K. (prof7bit)


Bewertung
0 lesenswert
nicht lesenswert
Johann L. schrieb:
> volatile ist deprecated in C++20, was ist da der Ersatz?

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1152r0.html#prop
1
3.2. Proposed changes
2
3
This proposal has the following goals:
4
5
  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.

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
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.

von Heiko L. (zer0)


Bewertung
1 lesenswert
nicht lesenswert
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.

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
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!

von Jemand (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Bernd K. (prof7bit)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Rolf M. (rmagnus)


Bewertung
1 lesenswert
nicht lesenswert
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.

von Jemand (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Heiko L. (zer0)


Bewertung
1 lesenswert
nicht lesenswert
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.

von leo (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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

von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht lesenswert
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.

von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Rolf M. (rmagnus)


Bewertung
2 lesenswert
nicht lesenswert
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.

von Bernd K. (prof7bit)


Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
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

von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht lesenswert
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.

von volatily (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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
int b = 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
int b = 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.

von Oliver S. (oliverso)


Bewertung
2 lesenswert
nicht lesenswert
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

: Bearbeitet durch User
von volatily (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Oliver S. (oliverso)


Bewertung
1 lesenswert
nicht lesenswert
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

: Bearbeitet durch User
von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
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

von volatily (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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?

von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
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

von Veit D. (devil-elec)


Bewertung
1 lesenswert
nicht lesenswert
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.

von Rolf M. (rmagnus)


Bewertung
1 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
von mh (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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?

von Veit D. (devil-elec)


Bewertung
0 lesenswert
nicht lesenswert
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 ...

von c-hater (Gast)


Bewertung
-2 lesenswert
nicht lesenswert
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++...

von Veit D. (devil-elec)


Bewertung
1 lesenswert
nicht lesenswert
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 ...  :-) 
:-)  :-)

von A. S. (achs)


Bewertung
1 lesenswert
nicht lesenswert
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.

von (prx) A. K. (prx)


Bewertung
1 lesenswert
nicht lesenswert
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.

von (prx) A. K. (prx)


Bewertung
1 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Und außerdem sind in der Mathematik Variablen streng genommen eigentlich
> konstant.

Eigentlich nicht. Variablen sind Platzhalter und damit eben nicht 
konstant. So spinnert sind die Mathematiker nun auch wieder nicht, 
Variablen ihren Variabilität abzusprechen.
https://de.wikipedia.org/wiki/Variable_(Mathematik)

Die Verwendung des Summenzeichens ist mit Formulierungen in 
Programmiersprachen vergleichbar: 
https://de.wikipedia.org/wiki/Summe#Notation_mit_dem_Summenzeichen

: Bearbeitet durch User
von mh (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
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?

von mh (Gast)


Bewertung
0 lesenswert
nicht lesenswert
A. S. schrieb:
> 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?

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

von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
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.

von mh (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
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?

Beitrag #6493887 wurde von einem Moderator gelöscht.
von volatily (Gast)


Bewertung
0 lesenswert
nicht lesenswert
mh schrieb:
> A. S. schrieb:
>> 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?
>
> 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

Interessant
1
(x * 10) / 5

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

von mh (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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?

von volatily (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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 ARM single 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.

von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
von mh (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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").

von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
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

von mh (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

Beitrag #6496993 wurde von einem Moderator gelöscht.
Beitrag #6496998 wurde von einem Moderator gelöscht.
Beitrag #6502566 wurde von einem Moderator gelöscht.
Beitrag #6507352 wurde von einem Moderator gelöscht.
Beitrag #6508623 wurde von einem Moderator gelöscht.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.