avr-gcc-10 - "volatile deprecated [-Wvolatile]" Warnungen

Moderator Persönliche Seite #6273399
Lesenswert?

Jörg W. schrieb:
>> Sind sie nicht, vor allem in C++ nicht (getrennte Operatorüberladung).
>
> Da ist allerdings was dran (hatte ich ja auch viel weiter oben schon
> erwähnt).

Wenn ich drüber nachdenke: für die Basistypen (für die volatile im 
Zusammenhang mit Hardware wichtig ist) kann man die Operatoren sowieso 
nicht überladen. Man könnte wahrscheinlich gut damit leben, volatile nur 
für solche Datentypen überhaupt noch zuzulassen (also Ganz- und 
Gleitkommazahlen, boolean, sowie Zeiger) und für alle anderen Typen das 
Verhalten als undefined zu erklären. Damit hätte sich das Problem 
überladener Operatoren in dieser Hinsicht erledigt.

Grenzfall wären enums: sie sind natürlich nützlich für 
Hardware-Abbildungen, aber für sie ist operator overloading zugelassen.
OP #6273439
Lesenswert?

Jörg W. schrieb:
> Veit D. schrieb:
>
>> Wie jetzt? Hat Johann hingeschmissen?
>
> Kennst du diese Diskussion denn nicht?
> Beitrag "gcc11 könnte das avr Backend verlieren"

Ja das kenne ich. Das klang jetzt leider für mich so das Johann gar 
nichts mehr für gcc macht, stimmt ja so auch nicht. 64bit Double hat er 
vor kurzem erst eingebaut bzw. gcc zur Verfügung gestellt, wie auch 
immer, ist von ihm. Das er dagegen keine Energie ins Backend steckt kann 
ich nachvollziehen.
OP #6276688
Lesenswert?

Hallo,

nö, es ging mir darum die Aussage von S.R.
Beitrag "Re: avr-gcc-10 - "volatile deprecated [-Wvolatile]" Warnungen"
und weit vorher von paar anderen Usern nachzuvollziehen bzw. zu 
widerlegen, die Compilierungen widerlegen es jedoch. Das heißt meine 
Gedanken das eh alles das Gleiche ist stimmt. Hat A.B. nochmal 
bestätigt.
Deswegen kann ich den gesamten Aufriss von gcc und der Warnung 
nachwievor nicht nachvollziehen. Wir drehen uns im Kreis.
Gast #6277194
Lesenswert?

Veit D. schrieb:
> und weit vorher von paar anderen Usern nachzuvollziehen bzw. zu
> widerlegen, die Compilierungen widerlegen es jedoch. Das heißt meine
> Gedanken das eh alles das Gleiche ist stimmt.

Damit hast du nur gezeigt, dass ganz bestimmte Compilerimplementierungen 
dies tun.
Das sagt über den Standard und wie dieser sein sollte überhaupt nichts 
aus.
Es sagt auch nichts über die Standardkonformität der Compiler aus.
Moderator #6277365
Lesenswert?

S. R. schrieb:
> Veit D. schrieb:
>> Also wo ist das Problem das standardmäßig vor der eigentlichen
>> Übersetzung mit der Langschreibweise zu ersetzen?
>
> In deiner Annahme, dass sie das gleiche wären.
> Sind sie nicht, vor allem in C++ nicht (getrennte Operatorüberladung).

Die Volatile-Deprecation erstreckt sich nicht auf überladene Operatoren.
Das wäre auch unsinnig, da sich die Semantik von überladenen Operatoren
beliebig von derjenigen der Basisoperatoren unterscheiden kann.

Dies kann man sich zunutze machen, um für memory-mapped I/O-Register
auch zukünftig Compound-Assignments verwenden zu können: Man verpackt
das zugrunde liegende Speicherobjekt einfach in eine Klasse, für die die
fraglichen Operatoren überladen werden.

Ich habe das einmal exemplarisch für die AVR-Familie versucht:

Im angehängten Header io-c++20.h wird eine Template-Klasse deklariert,
in der I/O-Register unterschiedlicher Bitbreite verpackt werden können.
Des Weiteren werden drei Makros aus der AVR-Libc so umdefiniert, dass
sie diese Klasse nutzen.

Wird nun in einem Anwendungsprogramm io-c++20.h anstelle von avr/io.h
inkludiert, werden die Beschränkungen durch die neuen Volatile-Regeln
umgangen, ohne dass dabei (außer der Include-Zeile) etwas am Quellcode
geändert werden muss. Auch die AVR-Libc muss dazu nicht angefasst
werden.

Falls das Standardisierungsgremium die neuen Regeln irgendwann bindend
macht (was ich nicht hoffe), könnte das ein Ansatz sein, die AVR-Libc
(und auch andere I/O-Bibliotheken) "C++2x-aware" zu machen, so dass
deren Nutzer ihren Quellcode ohne Änderungen weiterverwenden können.

Aber Vorsicht: Der angehängte Header ist ganz sicher weder vollständig
noch fehlerfrei, sondern dient lediglich als Proof-of-Concept. Also
bitte nicht hauen, wenn ihr den Header testet und dabei euer gesamter
AVR-Bestand in Flammen aufgeht ;-)

Das beiliegende Beispiel funktioniert mit GCC 10.1.0 und Clang 10.0.0.
Anders als Clang hat der GCC allerdings Probleme, die Operatporen <<=
und >>= in einfache 8-Bit-Shifts umzusetzen. Die anderen Operatoren,
insbesondere die häufig verwendeten |= und &= werden aber wie erwartet
übersetzt.

Anwendungsbeispiel:

1
#include <stdint.h>
2
#ifdef CLASSIC
3
#  include <avr/io.h>
4
#else
5
#  include "io-c++20.h"
6
#endif
7

8
uint8_t a;
9

10
int main() {
11

12
  PORTB |= 1<<PB3;      // 8 Bit
13
  PORTB &= ~(1<<PB5);
14
  PORTB ^= 1;
15
  ++PORTB;
16
  a = PORTB++;
17
  PORTB >>= 2;
18
  OCR1A += 4;           // 16 Bit
19

20
  return 0;
21
}

Generierter Assemblercode für den ATmega8:

1
; GCC 10.1.0           Clang 10.0.0
2

3
  sbi 0x18,3           sbi    24, 3
4

5
  cbi 0x18,5           cbi    24, 5
6

7
  in r24,0x18          ldi    r24, 1
8
  ldi r25,lo8(1)       in    r25, 24
9
  eor r24,r25          eor    r25, r24
10
  out 0x18,r24         out    24, r25
11

12
  in r24,0x18          in    r24, 24
13
  subi r24,lo8(-(1)    inc    r24
14
  out 0x18,r24         out    24, r24
15

16
  in r24,0x18          in    r24, 24
17
  add r25,r24          mov    r25, r24
18
  out 0x18,r25         inc    r25
19
  sts a,r24            out    24, r25
20
                       sts    a, r24
21

22
  in r24,0x18          in    r24, 24
23
  ldi r25,0            lsr    r24
24
  asr r25              lsr    r24
25
  ror r24              out    24, r24
26
  asr r25
27
  ror r24
28
  out 0x18,r24
29

30
  in r24,0x2a          in    r24, 42
31
  in r25,0x2a+1        in    r25, 43
32
  adiw r24,4           adiw    r24, 4
33
  out 0x2a+1,r25       out    43, r25
34
  out 0x2a,r24         out    42, r24
Angehängte Dateien:
Gast #6283719
Lesenswert?

Und was tut man gegen die fiesen strict-aliasing Regeln?
1
(*(_IO<uint8_t > *)(mem_addr))

müsste ja gegen strict aliasing verstoßen.. eigentlich müsste da noch 
ein bit_cast drüber gewrapped werden
Persönliche Seite #6286240
Lesenswert?

Yalu X. schrieb:
> Anders als Clang hat der GCC allerdings Probleme, die Operatporen <<=
> und >>= in einfache 8-Bit-Shifts umzusetzen.
>
> [...] Generierter Assemblercode für den ATmega8: [...]
>
>   in r24,0x18          in    r24, 24
>   ldi r25,0            lsr    r24
>   asr r25              lsr    r24
>   ror r24              out    24, r24
>   asr r25
>   ror r24
>   out 0x18,r24

Altbekanntes Problem, das nicht in avr-gcc auftritt sondern nur mit 
avr-g++:

http://gcc.gnu.org/PR82658
Persönliche Seite #6305520
Lesenswert?

Jörg W. schrieb:
> Wenn wir das Problem nicht lösen können, definieren wir es weg?
>
> Klingt für mich eher nach Vogel Strauß als nach einer sinnvollen
> Alternative.
>
> Ich schrieb doch schon, von mir aus soll man sowas als
> implementation-defined festlegen, oder implementation-dependant oder was
> auch immer, aber nicht einfach nur den Deckel aufmachen, und das Problem
> in der Tonne versenken wollen.

100% ACK

SFRs unterschiedlicher Controller(-Familien) sind einfach zu 
unterschiedlich, als dass sich das sinnvoll über den Sprachstandard 
regeln ließe.

Ergo: volatile sollte Implementation Defined sind, was effektiv bedeutet 
dass Compiler der (E)ABI des Hardware-Herstellers folgen.  Für AVR gibt 
es aber m.W. kein solches EABI — weder von Atmel noch von µChip.

Und zur Synchronisation von Prozesse über Core-Grenzen hinweg taugt 
volatile eh nicht, da zu "schwach".

Wie sieht's eigentlich mit den 3 avr-libc Innovationen aus: math.h 
(64-Bit double), Pimp für Multilib und mehr Support für Devices aus 
avrxmega3?
#6332997
Lesenswert?

Yalu X. schrieb:
> cbi 0x18,5           cbi    24, 5
> in r24,0x18          ldi    r24, 1

Genau, so macht man das (nicht). Nicht den Inhalt von r24 testen, 
einfach rein damit. Und dann sich beschweren und kreischen, wieso die 
eigene Anwendung zickt... Die hardwaremäßige Eingangsseite hast du 
bestimmt einfach auf den Pin gelötet und betest dann, dass alles schon 
funktioniert, bis das Geld auf dem Konto ist? So macht man den Ruf von 
Selbständigen kaputt, hauptsache schnell und billig.
OP #6645721
Lesenswert?

Hallo,

gibt es bezüglich volatile deprecated Neuigkeiten? Hat sich am Standard 
nochmal etwas geändert? Ich finde es nämlich mit der Zeit ziemlich 
lästig jede volatile Variable links und rechts vom Operator doppelt zu 
schreiben. Vorallendingen bei struct Konstruktionen die schon mal länger 
werden können. Bei Änderungen vergisst man oder übersieht etwas. Damit 
sind Ausführungsfehler vorprogrammiert was lange Zeit zu unbemerkten 
Problemen führen kann und wird.

Nun wollte ich volatile_load und volatile_store ausprobieren aber das 
kennt mein avr-gcc 10.2 inkl. C++20 Option nicht.
1
// statt
2
buffer[USARTn].lastRxError = buffer[USARTn].lastRxError | lastRxError;
3

4
// dann
5
auto temp = volatile_load(buffer[USARTn].lastRxError);
6
temp |= lastRxError;
7
volatile_store(buffer[USARTn].lastRxError, temp);

Wie kann man load/store verwenden?

Laut Beschreibung soll load/store im Allgemeinen performanter sein, was 
immer das bedeuten mag. Ich stelle mir darunter 'schneller' vor, 
ansonsten macht das ja keinen Sinn. Das wollte ich untersuchen.
#6645769
Lesenswert?

Sorry, wenn ich erst jetzt reingrätsche, aber ich hatte diese Diskussion 
letztes Jahr nicht mitbekommen. Allerdings adressiert diese Änderung ein 
Problem, das sich deutlicher mit 8051 als mit AVRs cbi/sbi erklären 
lässt.

Mir fiel schon vor langer Zeit auf, dass bei 8051 die übliche Äquivalenz 
von a &= b mit a = a & b nicht gewährleistet ist, wenn man sie als 
Hochsprachen-Schreibweise der 8051 Befehle versteht. Und das betrifft 
direkt die übliche Umsetzung von C.

Wenn man bei 8051 einen Port liest, wird der Zustand der Pins gelesen. 
Wenn man ihn schreibt, wird der Zustand des Output-Registers 
geschrieben. Modifikationsbefehle wie AND besitzen aber eine 
Besonderheit: Es wird nicht etwa der Zustand der Pins gelesen, der Wert 
verändert und ins Ausgangsregister geschrieben. Sondern es werden die 
Bits im Ausgangsregister modifiziert.

Wenn bei 8051 ein Port-Pin eine Open-Collector-Leitung treibt, ist der 
Zustand des Ausgangsregisters nicht zwingend mit dem Zustand der Leitung 
identisch. Das ist bei anderen Prozessoren ein meist bewusst verwendeter 
Fall, bei 8051 aber im Prinzip immer so, weil alle Portpins so definiert 
sind.

Versteht man also
   port &= value  (1)
als Äquivalent zu
   AND port, value
dann ist das nicht identisch mit
   port = port & value  (2)
weil in (1) das Ausgangsregister gelesen wird, in (2) aber der 
Pinzustand gelesen werden muss.

Betrachtet man andererseits (1) als abgekürztes Äquivalent von (2), ist 
es eigentlich verboten, für (1) den AND Befehl des 8051 zu nutzen, denn 
der hat eine völlig andere Bedeutung. Es gibt somit keinen Weg, die 
Arbeitsweise dieser 8051 Befehle direkt in C auszudrücken.

Nur hält sich natürlich kein realer Compiler für 8051 an diese 
Besonderheit und verwendet bei (1) sehr wohl den AND Befehl. Wenn er 
dann aber auch für (2) den AND Befehl verwendet, dann misachtet ganz 
offen er den notierten Willen des Programmierers.

Und damit sind wir bei AVRs CBI/SBI. Wenn man (1) als Notation für diese 
Befehle versteht, dann darf bei (2) der Compiler eigentlich nicht in 
CBI optimieren, weil das eine andere Bedeutung hat. Und der assign-op in 
(1) wäre überhaupt nur zulässig, wenn der Ports diese Befehle 
unterstützt - das sind aber nur die ersten 32 Adressen mit einer 
Einzelbitkonstante.

Damit landet man also in der Falle, sowohl bei 8051 als auch bei AVR. 
Die Äquivalenz von (1) und (2) verbietet es somit eigentlich, bei 8051 
in die AND/OR Befehle zum Port hin zu übersetzen, und verbietet es 
ebenso, bei AVR in die CBI/SBI Befehle zu übersetzen. Macht freilich 
jeder Compiler trotzdem.

Korrekt lösbar ist das nur, wenn bei 8051 und AVR diese Befehle nicht 
über das übliche Sprachkonstrukt nutzbar sind, sondern über spezielle 
Pseudofunktionen, wie eben das früher genutzte Makros CBI, hinter dem 
dann aber nicht der Assign-Op stehen darf, sondern ein 
Compiler-Intrinsic, oder ASM. Wer also die Bedeutung von CBI haben will, 
muss das dann auch als CBI(reg,bit) hinschreiben, nicht als reg &= mask, 
und kriegt es um die Ohren gehauen, wenn reg nicht mit CBI genutzt 
werden kann.

Die Definition von C++20 kann man somit auch als Versuch werten, sich 
von der Spottbezeichnung von C als besserem Makroassembler zu lösen, und 
eine formal korrekte Umsetzung zu erzwingen.
#6645833
Lesenswert?

Veit D. schrieb:
> gibt es bezüglich volatile deprecated Neuigkeiten? Hat sich am Standard
> nochmal etwas geändert?

Lies halt nach. Ich sag mal, ohne nachzulesen, nein, hat es nicht.

Veit D. schrieb:
> Laut Beschreibung soll load/store im Allgemeinen performanter sein, was
> immer das bedeuten mag. Ich stelle mir darunter 'schneller' vor,
> ansonsten macht das ja keinen Sinn. Das wollte ich untersuchen.

Auf einem AVR? Ohne Betriebssystem, Multitasking bzw. -threading, 
mehrstufigen Caches, out-of order-execution, pre-fetch, und was richtige 
Prozessoren und Betriebssysteme heutzutage alles so treiben ?

Auf dem AVR wird nichts performanter.

Oliver
#6645858
Lesenswert?

Ach so, Nachtrag:

wenn google weder std::volatile_load<T> noch 
std::experimental::volatile_load<T> kennt, ist das ein ziemlich 
deutlicher Hinweis darauf, daß das zwar irgendwo diskutiert wird, aber 
halt (noch?) ungelegte Eier sind. Das wirst du dir wohl selber schreiben 
müssen.

Oliver
OP #6645941
Lesenswert?

Hallo,

@ Oliver
Danke für die Antwort. Dann hat sich das schon erledigt.
Schade das etwas "verboten" wird ohne zeitgleich die angestrebte Lösung 
zur Verfügung zu stellen.  ;-)  Es ist damit ein sehr unschöner Zustand 
im Moment.

@ A.K.
Naja, Kurzschreibweise sagt eigentlich im Namen schon aus das es 
Äquivalent zu seiner Langschreibweise ist. Sprich es muss das gleiche 
Ergebnis bringen. Zumindestens sagt mir das meine Logik. Aber dafür bin 
ich ein viel zu kleines Licht. Was ich aber weiß ist, das sbi und cbi 
genau die Kurzschreibweisen verwenden. Beim AVR. Was anderes kenne ich 
nicht.  ;-)
1
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))  
2
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))

Die haben jedoch darauf schon reagiert und neue Makros erstellt.
https://www.nongnu.org/avr-libc/user-manual/group__avr__sfr.html
1
#define bit_is_set (sfr, bit) (_SFR_BYTE(sfr) & _BV(bit))
2
#define bit_is_clear (sfr, bit) (!(_SFR_BYTE(sfr) & _BV(bit)))
#6646263
Lesenswert?

Oliver S. schrieb:
> Ach so, Nachtrag:
>
> wenn google weder std::volatile_load<T> noch
> std::experimental::volatile_load<T> kennt, ist das ein ziemlich
> deutlicher Hinweis darauf, daß das zwar irgendwo diskutiert wird, aber
> halt (noch?) ungelegte Eier sind. Das wirst du dir wohl selber schreiben
> müssen.

Beispiel:

In Header-Datei vol_access:
1
#pragma once
2
#include <cassert>
3
#include "type_traits"
4

5
namespace std {
6
    namespace experimental {
7
        template<typename T>
8
        T volatile_load(const volatile T* const p) {
9
            assert(p);
10
//            static_assert(std::is_trivially_copyable_v<T>);
11
            return *p;
12
        }
13
        
14
        template<typename T>
15
        void volatile_store(volatile T* const p, const T v) {
16
            assert(p);
17
//            static_assert(std::is_trivially_copyable_v<T>);
18
            *p = v;
19
        }
20
        
21
    } // namespace experimental
22
} // namespace std
#6647351
Lesenswert?

Veit D. schrieb:
> mh schrieb:
>> Veit D. schrieb:
>>> Eine Frage dazu. Was genau macht die assert Prüfung an der
>>> Stelle? Das kann ich nicht erkennen. Irgendeine Gültigkeit ja, aber
>>> welche genau?
>>
>> Schlaf mal ne Nacht drüber und wenn du es morgen nach dem zweiten Kaffee
>> noch nicht siehst, meld dich nochmal.
>
> okay   :-)
1
const int* const p = new int(42);
2

3
// ...
4

5
if (p) { // checked-pointer-idiom
6
    foo(*p);
7
}

Vielleicht erkennst Du es so?

Was ist die Vorbedingung, damit die Funktionen volatile_load() oder 
volatile_store() arbeiten können? Oder anders ausgedrückt: welcher Wert 
eines Zeigertyps gehört hier nicht zum zugelassenen Wertebereich des 
Parameters p?
OP #6647446
Lesenswert?

Hallo,

ich komme noch nicht ganz drauf was assert mit dem Zeiger macht. Mir 
fehlt dabei entweder ein Adressoperator, ein Dereferenzierer oder ein 
Datentyp cast. Obwohl mir der Datentyp cast fehlt kommt mir die Sache am 
nächsten, dass assert die Adresse vom Zeiger prüft auf die er zeigt, 
dass diese nicht 'nullptr' ist. Macht aber keinen richtigen Sinn. assert 
wertet ein Bedingung aus die wahr sein muss. Unwahr wirft einen Fehler. 
Hier wird jedoch kein Vergleich o.ä. gemacht. Nur warum sollte auf 
ungleich 0 geprüft werden?
Warum ist es nicht ausreichend zu dereferenzieren und den Wert (auf den 
der Zeiger zeigt) zurückzugeben? Einfach nur return *p;
#6647460
Lesenswert?

Veit D. schrieb:
> ich komme noch nicht ganz drauf was assert mit dem Zeiger macht

assert(x) darf man sich ungefähr so vorstellen:
   if (x == 0) abort(...)

"The assert macro puts diagnostic tests into programs; it expands to a 
void expression. When it is executed, if expression (which shall have a 
scalar type) is false (that is, compares equal to 0), the assert macro 
writes information about the particular call that failed (...) It then 
calls the abort function."
OP #6647469
Lesenswert?

Hallo,

was assert selbst macht ist mir schon klar. Eine Wahrheitsprüfung.
Nur was ist der Sinn mit dem Zeiger? Wird damit geprüft ob der Zeiger 
auf eine Addresse ungleich nullptr zeigt? Wenn ja, warum? Ein Variable 
als Parameter hat immer eine andere Adresse != nullptr. Wäre damit immer 
wahr.
???
#6647478
Lesenswert?

Veit D. schrieb:
> Nur was ist der Sinn mit dem Zeiger? Wird damit geprüft ob der Zeiger
> auf eine Addresse ungleich nullptr zeigt? Wenn ja, warum? Ein Variable
> als Parameter hat immer eine andere Adresse != nullptr. Wäre damit immer
> wahr.

In der Definition von volatile_load ist nicht der Parameter selbst 
volatile, sondern das, worauf er zeigt. In
   volatile int *p
ist nicht p volatile, sondern *p.
#6647494
Lesenswert?

Veit D. schrieb:
> Hallo,
>
> ich komme noch nicht ganz drauf was assert mit dem Zeiger macht. Mir
> fehlt dabei entweder ein Adressoperator, ein Dereferenzierer oder ein
> Datentyp cast.

Der abgeleitete Typ pointer-to (Zeiger) kann implizit nach bool 
konvertiert werden:

nullptr -> false
andere Werte -> true

Das findet in meinem Beispiel mit dem if() und natürlich auch im assert 
statt (wenn es enabled ist).

> Obwohl mir der Datentyp cast fehlt kommt mir die Sache am
> nächsten, dass assert die Adresse vom Zeiger prüft auf die er zeigt,

Nein, es wird der Zeigerwert geprüft.
Und zwar genau aus dem Grund, den ich oben schon geschrieben habe (und 
Du wohl nicht gelsen hast).

> dass diese nicht 'nullptr' ist. Macht aber keinen richtigen Sinn. assert
> wertet ein Bedingung aus die wahr sein muss.

Die kann auch falsch sein, eben wenn nullptr übergeben wird.

> Unwahr wirft einen Fehler.

Ja, wenn enabled.

> Hier wird jedoch kein Vergleich o.ä. gemacht. Nur warum sollte auf
> ungleich 0 geprüft werden?

s.o.

> Warum ist es nicht ausreichend zu dereferenzieren und den Wert (auf den
> der Zeiger zeigt) zurückzugeben? Einfach nur return *p;

Beschäftige Dich mal mit dem Zweck von Zusicherungen (assertionen).

Hinweis: Herb Sutter hat gerade in seinem Blog (GotW) gute Beiträge 
dazu.
#6647498
Lesenswert?

Veit D. schrieb:
> Hallo,
>
> was assert selbst macht ist mir schon klar. Eine Wahrheitsprüfung.
> Nur was ist der Sinn mit dem Zeiger? Wird damit geprüft ob der Zeiger
> auf eine Addresse ungleich nullptr zeigt? Wenn ja, warum? Ein Variable
> als Parameter hat immer eine andere Adresse != nullptr.

Es geht um den Inhalt:
1
volatile_load(nullptr)

ist nicht zugelassen (weil nullptr nicht zum zugelassenen Wertebereich 
von volatile_load() gehört).
OP #6647679
Lesenswert?

@ Wilhelm
was assert macht weiß ich doch. Verwendet habe ich bis jetzt immer 
static_assert, weil es Meldungen ausgeben kann. Übrigens ist der 
Zeigerwert die Adresse auf die er zeigt. Ein Zeiger speichert ja nur 
Adressen. Hatte ich schon richtig geschrieben. Ich fasse zusammen, es 
wird überprüft ob der Zeiger auf nullptr zeigt oder nicht.

@ prx
dich habe ich so verstanden dass geprüft wird ob der Zeiger auf eine 
volatile deklarierte Variable zeigt. Wenn nicht -> wirf einen Fehler. 
Funktioniert aber nicht. Wirft bei mir ohne volatile keinen Fehler. 
Vermutlich habe ich dich falsch verstanden.
1
uint16_t store;
2
uint16_t load {111};
3

4
template<typename T>
5
T volatile_load(const volatile T* const p) {
6
    assert(p);
7
    return *p;
8
}
9

10
store = volatile_load(&load);

Ganz schön schwierig drei Zeilen Code zu erklären.  :-)

Jetzt müßte man das nur noch ohne Verrenkungen avr-gcc kompatibel 
machen. Weil die benötigte std Lib gibts auf dem AVR nicht. assert(p) 
wirft keine Fehlermeldung, nützt mir also nichts. Programm schmiert ab 
bzw. bleibt stehen ohne das man den Grund sieht.

Und damit bin ich fast schon wieder beim Ausgangsproblem. Wenn das so 
einen Rattenschwanz nach sich zieht, dann kann das leider keine 
praktikable Lösung sein. Es verkompliziert alles und damit sinkt die 
Bereitschaft das zu nutzen. Das ist überhaupt mein Problem was ich an 
C++ in der Zukunft sehe. Anstatt Dinge zu Vereinfachen wird es außen 
verkompliziert. Damit wird auch die Sprache immer komplizierter statt 
einfacher. Das kann niemals der richtige Weg sein. Man müßte auf die 
Rückwärtskompatibilität verzichten und einen harten Schnitt machen. Ab 
Version C++ xx verhalten sich alle Funktionen/Methoden korrekt, nach 
aktuellen besten Wissen und Gewissen, ohne äußerliche Syntaxänderungen. 
Zum Bsp.

Denn bleiben wir einmal bei den Funktionen volatile_load und 
volatile_store. Man muss trotzdem seine zu ändernde Variable 2x angeben. 
Man hat nichts gewohnen. Also kann man das im Grunde auch sein lassen 
und man schreibt sie als Lvalue und Rvalue vom Operator.
#6647707
Lesenswert?

Veit D. schrieb:
> @ Wilhelm
> was assert macht weiß ich doch.

Da bin ich mir nicht ganz sicher ...

> Verwendet habe ich bis jetzt immer
> static_assert, weil es Meldungen ausgeben kann.

 ... ob Du den Unterschied zu static_assert weißt

> @ prx
> dich habe ich so verstanden dass geprüft wird ob der Zeiger auf eine
> volatile deklarierte Variable zeigt. Wenn nicht -> wirf einen Fehler.

Nein, die Dereferenzierung eine nullpr ist UB.

Und genau das soll durch die Zusicherung verhindert werden. Hat man ein 
Betriebssystem, so wird der Prozess mit einer Fehlermeldung abgebrochen. 
hat man kein Betriebssystem ... es bleibt Dir überlassen, z.B. einen 
bestimmten Pin wackeln zu lassen.

> Funktioniert aber nicht. Wirft bei mir ohne volatile keinen Fehler.

Mit volatile hat das erstmal gar nichts zu zu.

Wenn Du zur Compilezeit überprüfen / verhindern willst, ob der Zeiger 
wirklich auf ein volatile zeigt, musst Du das mit einem type_trait 
machen (Meta-Funktion) oder mir concepts/requirements komplett 
unterbinden, dass das template instanziiert wird.

> Ganz schön schwierig drei Zeilen Code zu erklären.  :-)

Es hängt immer vom Vorwissen des Zuhörers ab.

>
> Jetzt müßte man das nur noch ohne Verrenkungen avr-gcc kompatibel
> machen.

Für mein obiges Beispiel geht es auch ohne die stdlibc++, wenn Du
die auskommentierten static_assert auch weglässt.

>Weil die benötigte std Lib gibts auf dem AVR nicht. assert(p)
> wirft keine Fehlermeldung, nützt mir also nichts.

Ja, wohin auch. Denk doch mal nach!

> Programm schmiert ab
> bzw. bleibt stehen ohne das man den Grund sieht.

Genau. Geht nämlich in eine kleine Endlosschleife ;-)

> Bereitschaft das zu nutzen. Das ist überhaupt mein Problem was ich an
> C++ in der Zukunft sehe.

Dein Problem ist im Moment, dass Dir Grundlagen fehlen.

> Denn bleiben wir einmal bei den Funktionen volatile_load und
> volatile_store. Man muss trotzdem seine zu ändernde Variable 2x angeben.
> Man hat nichts gewohnen. Also kann man das im Grunde auch sein lassen
> und man schreibt sie als Lvalue und Rvalue vom Operator.

Du hast den Grund der deprecation nicht vertanden.
#6647866
Lesenswert?

Jörg W. schrieb:
> Wilhelm M. schrieb:
>> Nein, die Dereferenzierung eine nullpr ist UB.
>
> Ist nicht letztlich jede Dereferenzierung eines ungültigen Zeigers eine
> UB?

Natürlich.

>
> Dann greift das assert auf einen Nullzeiger einfach mal zu kurz. Kann
> man dann wohl genauso gut weglassen.

Nein. Ich habe nicht behauptet, dass damit jedweder Fehler verhindert 
wird. Aber eine sehr häufige Fehlerquelle wird aufgedeckt. Eine 
Verletzung einer Vorbedingung ist immer ein Fehler des Aufrufers.

Eine Zusicherung als Vorbedingung dient auch dazu, den "zu großen" 
Wertebereich eines Datentyps - vor allem bei den primitiven DT - 
einzugrenzen, soweit eben möglich. Bei Zeigerwerten ist dies (leider) 
nicht vollständig möglich. Aber den nullptr kann man schon mal 
herausnehmen, falls die folgenden Anweisungen davon ausgehen, das der 
Wert kein nullptr ist. Damit hat man schon viel erreicht, wenngleich 
nicht alles.
Zudem erfüllt eine Zusicherung immer auch die Funktion eines 
"überprüften" Kommentars zu einem bestimmten Zustand des Objektes bzw. 
der Funktion (ihrer Variablen).
#6647869
Lesenswert?

Veit D. schrieb:
> Alles klar Wilhelm. Ich weiß schon, ich bin ringsherum bekloppt. Danke.

Komm schon, wir sind doch hier in diesem speziellen Forum ;-) Da muss 
man sowas aushalten!

> Meine Aussagen bügelst du komplett weg.

Naja, es stimmte eben nicht wirklich so ganz, war aber nur leicht 
falsch.

> Das der Syntax immer nur wächst
> und wächst und wächst ist dir demnach völlig egal.

Ja. Denn ich weiß um den Wert der Abwärtkompatibilität.
Gast #6647910
Lesenswert?

Veit D. schrieb:
> Alles klar Wilhelm. Ich weiß schon, ich bin ringsherum bekloppt. Danke.
Der einzige, der das behauptet, bist du.

> Das der Syntax immer nur wächst
> und wächst und wächst ist dir demnach völlig egal.
Hast du dich nicht vor kurzem beschwert, dass etwas deprecated wird, 
ohne für Ersatz zu sorgen?
Moderator Persönliche Seite #6647933
Lesenswert?

Wilhelm M. schrieb:
> Aber eine sehr häufige Fehlerquelle wird aufgedeckt.

Sehe ich nicht so. Die Dinge, um die es hier bei der 
"volatile"-Diskussion geht, sind doch nicht irgendwelche Zeiger, die 
jemand auch mal in einer Variablen hat und dann aus Versehen nicht 
passend zugewiesen hat, sondern das sind doch Hardware-Adressen. Da 
übergibt keiner „aus Versehen mal“ ein nullptr.
OP #6647938
Lesenswert?

Hallo,

irgendwie bin ich ja fast geneigt, um von der unpraktischen Syntax 
loszukommen, irgendwie sowas in der Art anzulegen. Grobes Bsp.
1
template<typename T>
2
void volatileAdd(T &d, T &mod)
3
{
4
   d = d + mod;
5
}

Damit muss man nicht irgendwelche Variablennamen doppelt tippen und hat 
nur eine Zeile.
1
volatileAdd(var, mod);

Im C++ Forum habe ich das hier entdeckt.
https://www.c-plusplus.net/forum/topic/263375/operator-per-parameter-%C3%BCbergeben
Leider fehlt die map Lib. Nur kann man da nicht etwas mit using zaubern?
Die Idee wäre eine Funktion mit Operator als 3. Parameter.
1
void volatileMod(a, b, '+') { ... }
Wobei immer der 2. Parameter mit dem Ersten modifiziert wird. Genau das 
was man bis jetzt getippt hat.
1
a += b;
2
bzw.
3
a = a + b;

Geht da was?
#6647947
Lesenswert?

Veit D. schrieb:
> Hallo,
>
> irgendwie bin ich ja fast geneigt, um von der unpraktischen Syntax
> loszukommen, irgendwie sowas in der Art anzulegen. Grobes Bsp.
>
1
> template<typename T>
2
> void volatileAdd(T &d, T &mod)
3
> {
4
>    d = d + mod;
5
> }
6
>

Kann man machen. Aber: mod als in/out-Parameter? Wohl kaum.

> Damit muss man nicht irgendwelche Variablennamen doppelt tippen und hat
> nur eine Zeile.
>
1
> volatileAdd(var, mod);
2
>
>
> Im C++ Forum habe ich das hier entdeckt.
> 
https://www.c-plusplus.net/forum/topic/263375/operator-per-parameter-%C3%BCbergeben
> Leider fehlt die map Lib. Nur kann man da nicht etwas mit using zaubern?

Was mit using? Hast Du was mit Medien studiert ;-)

> Die Idee wäre eine Funktion mit Operator als 3. Parameter.
>
1
> void volatileMod(a, b, '+') { ... }
2
>
> Wobei immer der 2. Parameter mit dem Ersten modifiziert wird. Genau das
> was man bis jetzt getippt hat.
>
1
> a += b;
2
> bzw.
3
> a = a + b;
4
>
>
> Geht da was?

Na klar, etwa mit tagging:
1
volatileOp(a, b, add{});

oder explizit:
1
volatileOp<add>(a,b);
#6647949
Lesenswert?

Jörg W. schrieb:
> Wilhelm M. schrieb:
>> Aber eine sehr häufige Fehlerquelle wird aufgedeckt.
>
> Sehe ich nicht so. Die Dinge, um die es hier bei der
> "volatile"-Diskussion geht, sind doch nicht irgendwelche Zeiger, die
> jemand auch mal in einer Variablen hat und dann aus Versehen nicht
> passend zugewiesen hat, sondern das sind doch Hardware-Adressen. Da
> übergibt keiner „aus Versehen mal“ ein nullptr.

Sehe ich anders: warum sollte der Einsatz auf HW-Adressen beschränkt 
sein?

Einen optimizer-killer volatile braucht es an vielen Stellen.
Gast #6647963
Lesenswert?

Jörg W. schrieb:
> Wilhelm M. schrieb:
>> Aber eine sehr häufige Fehlerquelle wird aufgedeckt.
>
> Sehe ich nicht so. Die Dinge, um die es hier bei der
> "volatile"-Diskussion geht, sind doch nicht irgendwelche Zeiger, die
> jemand auch mal in einer Variablen hat und dann aus Versehen nicht
> passend zugewiesen hat, sondern das sind doch Hardware-Adressen. Da
> übergibt keiner „aus Versehen mal“ ein nullptr.

Das hängt ganz stark davon ab, was diese Hardware-Adressen genau sind 
und wie sie benutzt werden.
OP #6648168
Lesenswert?

Hallo,

also auf Hardware Adressen ist die Geschichte mit volatile nun wirklich 
nicht beschränkt. Da es jede Variable betrifft die als volatile 
deklariert ist. Das kann wiederum unterschiedlichste Gründe haben.

"tagging". Danke. Mal sehen ob mir das Stichwort reicht.

Edit:
Du meinst damit aber nicht Operator Überladung? Oder doch?
#6648250
Lesenswert?

Veit D. schrieb:

> "tagging". Danke. Mal sehen ob mir das Stichwort reicht.
>
> Edit:
> Du meinst damit aber nicht Operator Überladung? Oder doch?

Nein!

Du hast doch oben eine Variante vorgegeben, und darauf habe ich 
geantwortet und Dir sogar gezeigt, wie die Aufrufseite für zwei 
Versionen davon aussehen könnte. Daraus solltest Du eigentlich eine 
einfache, vielleicht nicht allumfassende Realisierung ableiten können.

Die erste, oben gezeigte Möglichkeit ist vielleicht einfacher für Dich. 
Dort geht es um ein freies Funktionstemplate mit drei Parametern. Die 
ersten beiden sind wie von Dir vorgeschlagen, der dritte ist ein sog. 
tag-type. Dieser dient nur dazu, die verschiedenen Überladungen dieses 
Templates auszuwählen. 'add{}' erzeugt ein anonymes Objekt des Typs 
'add'. 'add' ist eine sonst leere Klasse, etwa 'struct add{};'. Der Rest 
dürfte jetzt klar sein.

Die andere Variante ist nicht so straight-forward, da man wie Du sicher 
weißt Funktionstemplates nicht partiell spezialisieren kann. Ist seit 
C++17 aber kein Problem, denn es gibt ja constexpr-if.

Wenn Du damit etwas hinbekommst, zeige ich Dir gerne meine Version von 
dem oben genannten ...
OP #6649094
Lesenswert?

Hallo,

die Implementierung bereitet mir noch Schmerzen. Funktionieren tuts 
erstmal, nur werden die "Tags" als unused angemeckert, was irgendwie 
logisch ist, nur ohne gehts nicht. Was müßte ich ändern?
1
volatile uint16_t var {2222};
2
uint16_t load  {1111};
3

4
struct add {};
5
struct sub {};
6

7
template <typename T>
8
void volatileOps(volatile T &Lvalue, const T &Rvalue, const add &tag) {
9
  Serial.print("ADD ");
10
  Lvalue = Lvalue + Rvalue;
11
  Serial.println(Lvalue);
12
}
13

14
template <typename T>
15
void volatileOps(volatile T &Lvalue, const T &Rvalue, const sub &tag) {
16
  Serial.print("SUB ");
17
  Lvalue = Lvalue - Rvalue;
18
  Serial.println(Lvalue);
19
}
20

21
void setup (void)
22
{
23
  Serial.begin(250000);
24
  Serial.println("\nStart #### ####");
25

26
  volatileOps(var, load, add{} );
27
  var = 2222;
28
  volatileOps(var, load, sub{} );
29
}
30

31
void loop (void)
32
{
33
}
Gast #6649144
Lesenswert?

Veit D. schrieb:
> die Implementierung bereitet mir noch Schmerzen.
Mal abgesehen von der Implementierung, warum möchtest du das mit den 
Tags machen? Ich sehe hier keinen Vorteil. Warum machst du nicht 
Funktionstemplates volatileAdd, volatileSub, usw.

Wilhelm M. schrieb:
> Deine Beschriftung LValue und RValue passt auch nicht ...
Er kann schlecht beide Lvalue nennen ... ;-)
#6649155
Lesenswert?

Veit D. schrieb:
> Danke, ohne jede Warnung. Wie würdest du die Paramternamen sinnvoll
> benennen?
> Mir fällt da aktuell nichts besseres ein.

Das bleibt Dir überlassen.
Aber: lvalue hat im Compilerbauer-Jargon eine bestimmt Bedeutung wie 
auch rvalue. Nur der Parameter, den Du RValue genannt hast ist eben kein 
rvalue. Ein rvalue ist ein anonymes, temporäres Objekt. Und da der 
Parameter einen Namen hat, kann er kein rvalue sein. In Deinem Fall ist 
es eben eine lvalue-reference.
OP #6649168
Lesenswert?

Hallo,

mh schrieb:
> Mal abgesehen von der Implementierung, warum möchtest du das mit den
> Tags machen? Ich sehe hier keinen Vorteil. Warum machst du nicht
> Funktionstemplates volatileAdd, volatileSub, usw.

Das wollte ich auch erst so machen.
Beitrag "Re: avr-gcc-10 - "volatile deprecated [-Wvolatile]" Warnungen"
Ich experimentiere derzeit rum und muss mich irgendwann entscheiden.

> Wilhelm M. schrieb:
>> Deine Beschriftung LValue und RValue passt auch nicht ...

gut, ich bin da ziemlich flexibel.  ;-)
Wie wäre es mit ... ?
1
template <typename T>
2
void volatileOps(volatile T &Lvalue, const T &modifier, const add) {
3
  Lvalue = Lvalue + modifier;
4
}
Gast #6649174
Lesenswert?

Veit D. schrieb:
> Ich experimentiere derzeit rum und muss mich irgendwann entscheiden.
Der verlinkte Beitrag hilft nicht wirklich. Mit dem verlinkten Beitrag 
auf c-plusplus.net hat dein Code relativ wenig zu tun. Das Beispiel dort 
hat nämlich Vorteile ;-)

>> Wilhelm M. schrieb:
>>> Deine Beschriftung LValue und RValue passt auch nicht ...
>
> gut, ich bin da ziemlich flexibel.  ;-)
> Wie wäre es mit ... ?template <typename T>
Du hättest wenigstens mal nachgucken können, was dieser lvalue ist, von 
dem Wilhelm schreibt ...
#6649257
Lesenswert?

Veit D. schrieb:
> @ Wilhelm:
> Dümmer gewurden bin ich dadurch nicht.  :-)   Kann man bestimmt einmal
> gebrauchen. Würdest du mir deine Variante zeigen wie du das machen
> würdest?

Ja, muss ich nur erstmal aufschreiben ...

In der Zwischenzeit (wer den cpp mag):
1
#define VOLOP(x) _Pragma("GCC diagnostic push") \
2
    _Pragma("GCC diagnostic ignored \"-Wvolatile\"") \
3
    x; \
4
    _Pragma("GCC diagnostic pop")
5

6
//...
7
VOLOP(a += 1);
#6649274
Lesenswert?

So, quick and dirty jetzt mal:
1
struct add{
2
    template<typename T>
3
    T operator()(volatile T& a, const T& b) const {
4
        return a + b;
5
    }
6
};
7
struct sub{
8
    template<typename T>
9
    T operator()(volatile T& a, const T& b) const {
10
        return a - b;
11
    }    
12
};
13

14
template<typename T, typename Op>
15
requires std::is_volatile_v<T>
16
void volOp(T& v, const std::remove_volatile_t<T>& m, const Op op) {
17
    v = op(v, m);
18
}

Dann kann man es so verwenden:
1
    volatile uint8_t a{}; 
2
    // ...
3
    volOp(a, uint8_t{1}, add{}); // should not work if a is not volatile     
4
    volOp(a, uint8_t{1}, sub{});
5
    volOp(a, uint8_t{1}, [](auto& a, const auto& b){return a * b;});

Es hat den Op als customization-point, so dass man auch ein closure aus 
einer lambda-expression übergeben kann.
Und man kann es dann nicht mehr (fälschlicherweise) auf non-volatiles 
anwenden.

Wobei ich jetzt anmerken muss, dass ich bei volatile_load / ..._store 
bleibe.
Moderator Persönliche Seite #6649282
Lesenswert?

Wilhelm M. schrieb:
>> Ich dachte, die kann man nur auf komplette Funktionen anwenden?
>
> Nein: ist ein Optionen-Stack, auf den Du die aktuelle Optionenwahl
> pushst, anschließend kannst Du manipulieren, und später holst Du es vom
> Stack wieder runter.

Das ist mir schon klar. Ich hatte es nur so in Erinnerung, dass man 
diese Optionen halt nur immer vor bzw. nach einer Funktion ändern 
kann. Das Einbetten in den Makro würde aber die Änderung innerhalb einer 
Funktion bewirken.

(Kann auch sein, dass ich das falsch in Erinnerung habe und dass dieses 
Verhalten nur für das Ändern von Optimierungs-Einstellungen zutrifft.)
Gast #6649291
Lesenswert?

Jörg W. schrieb:
> (Kann auch sein, dass ich das falsch in Erinnerung habe und dass dieses
> Verhalten nur für das Ändern von Optimierungs-Einstellungen zutrifft.)

Wenn ich das grad in der Doku richtig gesehen hab, müsste das Pragma von 
Wilhelm funktionieren. Das Pragma für Optimierungen steht allerdings 
unter
1
Function Specific Option Pragmas
#6649303
Lesenswert?

Jedenfalls setze ich das gerne ein für sehr spezielle. 
plattform-spezifische Warnungen, über die man sich Gedanken gemacht hat 
und zu dem Schluss gekommen ist, dass man diese Warnung an genau dieser 
einen Stelle akzeptieren kann. Das ist natürlich sehr selten. Ermöglicht 
aber warnungsfreie Compilation, damit man neue Warnungen eben sofort 
erkennt und beheben kann.
Persönliche Seite #7539271
Lesenswert?

2 1/2 Jahre und 3 C++ Versionen später:

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2327r1.pdf

1
| P2327R1
2
| Title: De-deprecating volatile compound operations
3
|
4
| [...]
5
| The C++ 20 standard deprecated many functionalities of the volatile
6
| keyword. [...] The deprecation was not received too well in the
7
| embedded community as volatile is commonly used for communicating with
8
| peripheral devices in microcontrollers.  The purpose of this paper is
9
| to give a solution that will not undo what was achieved with P1152,
10
| and still keep the parts that are critical to the embedded community.

WG21 hat also eine (teilweise) Rolle rückwärts gemacht?

Jedenfalls hat z.B. GCC obiges P2327R1 bereits implementiert (ab v13).

Moderator #7540138
Lesenswert?

Johann L. schrieb:

WG21 hat also eine (teilweise) Rolle rückwärts gemacht?

Eine nur teilweise Rolle rückwärts bewirkt, dass der Körper in eine instabile und auf die Dauer ziemlich schmerzhafte Stellung gerät :)

Genau das trifft auf den Lösungsvorschlag in

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2327r1.pdf

zu:

1
3 Solution
2

3
As the compound operations are mainly used for bitwise manipulation, the
4
proposed solution is to only de-deprecate the use of bitwise compound
5
operations ( |= &= ˆ= ) as these are the ones that are useful for this
6
purpose. In the examination of the libraries += and -= did not occur,
7
however they might in commercial / closed source libraries.

Der Autor wollte also die Ausnahme von der Ausnahme einführen, und seine Argumentation dafür stützt sich auf irgendwelche selbst erstellten und wenig repräsentativen Statistiken zur Häufigkeit der Verwendung von |=, &= und ^= auf der einen und +=, -=, *=, /=, %=, <<= und >>= auf der anderen Seite.

Das verlinkte Dokument ist aber schon über zwei Jahre alt, und zum Glück sind inzwischen sämtliche Compound-Assignments (also insbesondere auch +=) wieder vom Volatile-Verbot befreit.

Damit hat die WG21 schon fast eine Dreiviertelrolle rückwärts geschafft.

Leider dürfen die Operatoren ++ und -- (sowohl die Präfix- als auch die Postfix-Variante) auch weiterhin nicht auf Volatile-Operanden angewandt werden. Hätte man die nicht gleich zusammen mit den ganz ähnlichen Operatoren += und -= de-deprecaten können?

Als nur mittelmäßiger C++-Programmierer, der den mittlerweile auf stolze 2182 Seiten angewachsenen Standard-Draft nur partiell gelesen hat, kann und muss ich das wohl nicht verstehen ;-)

Was ich ebenfalls ein Bisschen komisch finde:

1
  int i;
2
  volatile int v;
3

4
  i = v += 1;      // Erlaubt
5
  i = v = v + 1;   // Fehler

Das kratzt mich allerdings wenig, da ich solche Konstrukte sowieso nicht verwende.

Beitrag #7540192 wurde von einem Moderator gelöscht.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren