Forum: Mikrocontroller und Digitale Elektronik Frage zu C++, warum ist nach 2 Mal rein in die/raus aus den Kartoffeln 104 nicht mehr konstant?


von Flunder (flunder)


Lesenswert?

Ich schreibe in der Arduino IDE ein Programm für den Arduino Nano.

Eventuell soll später nochmal der Pin, an dem Peripherie hängt geändert 
werden können, indem der Sketch geändert wird. Aber : der neue Pin muss 
wie der jetzige Interrupt on Change unterstützen. Das wollte ich 
absichern.

Hier mein zusammengekürzter Sketch :
1
#if 0 // folgende Zeilen sind in Dateien, die automatisch eingebunden werden, ich habe sie hier nur mal zusammen gesucht
2
3
// stdint.h
4
typedef unsigned int uint8_t __attribute__((__mode__(__QI__)));
5
6
// sfr_defs.h
7
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
8
9
#define _SFR_MEM8(mem_addr) _MMIO_BYTE(mem_addr)
10
11
// iom328p.h
12
#define PCICR _SFR_MEM8(0x68)
13
14
// pins_arduino.h
15
#define digitalPinToPCICR(p)    (((p) >= 0 && (p) <= 21) ? (&PCICR) : ((uint8_t *)0))
16
17
#endif
18
19
// Mein Sketch
20
/* Datentyp Pinnummer */
21
typedef uint8_t pinnr_t;
22
23
constexpr pinnr_t HALL_SPEED_PIN = 8U;
24
25
static_assert((uint8_t*)0 != digitalPinToPCICR(HALL_SPEED_PIN), "HALL_SPEED_PIN auf Pin legen, der Interrupt on Change unterstützt");
26
27
#if 0 // und das macht der Precompiler nach eigenen Angaben (-E) draus
28
static_assert((uint8_t*)0 != (((HALL_SPEED_PIN) >= 0 && (HALL_SPEED_PIN) <= 21) ? (&
29
                            (*(volatile uint8_t *)(0x68))
30
                            ) : ((uint8_t *)0)), "HALL_SPEED_PIN auf Pin legen, der Interrupt on Change unterstützt");
31
#endif

Der Compiler meint dazu nur
1
FQBN: arduino:avr:nano
2
Verwende das Board 'nano' von der Plattform im Ordner: *:\Users\*\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6
3
Verwendung des Kerns 'arduino' von Platform im Ordner: *:\Users\*\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6
4
5
Verwendete Bibliotheken erkennen ...
6
*:\Users\*\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7/bin/avr-g++ -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR -I*:\Users\*\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino -I*:\Users\*\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\variants\eightanaloginputs *:\Users\*\AppData\Local\arduino\sketches\*\sketch\problem.ino.cpp -o nul
7
Funktionsprototypen werden generiert ...
8
*:\Users\*\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7/bin/avr-g++ -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR -I*:\Users\*\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino -I*:\Users\*\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\variants\eightanaloginputs *:\Users\*\AppData\Local\arduino\sketches\*\sketch\problem.ino.cpp -o *:\Users\*\AppData\Local\Temp\531067917\sketch_merged.cpp
9
*:\Users\*\AppData\Local\Arduino15\packages\builtin\tools\ctags\5.8-arduino11/ctags -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives *:\Users\*\AppData\Local\Temp\531067917\sketch_merged.cpp
10
Sketch wird kompiliert ...
11
"*:\\Users\\*\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino7/bin/avr-g++" -c -g -Os -Wall -Wextra -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I*:\\Users\\*\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\avr\\1.8.6\\cores\\arduino" "-I*:\\Users\\*\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\avr\\1.8.6\\variants\\eightanaloginputs" "*:\\Users\\*\\AppData\\Local\\arduino\\sketches\\*\\sketch\\problem.ino.cpp" -o "*:\\Users\\*\\AppData\\Local\\arduino\\sketches\\*\\sketch\\problem.ino.cpp.o"
12
*:\*\problem\problem.ino:25:1: error: non-constant condition for static assertion
13
 static_assert((uint8_t*)0 != digitalPinToPCICR(HALL_SPEED_PIN), "HALL_SPEED_PIN auf Pin legen, der Interrupt on Change unterstützt");
14
 ^~~~~~~~~~~~~
15
In file included from *:\Users\*\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\variants\eightanaloginputs/pins_arduino.h:23:0,
16
                 from *:\Users\*\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino/Arduino.h:258,
17
                 from *:\Users\*\AppData\Local\arduino\sketches\*\sketch\problem.ino.cpp:1:
18
*:\Users\*\appdata\local\arduino15\packages\arduino\hardware\avr\1.8.6\variants\standard\pins_arduino.h:74:61: error: reinterpret_cast from integer to pointer
19
 #define digitalPinToPCICR(p)    (((p) >= 0 && (p) <= 21) ? (&PCICR) : ((uint8_t *)0))
20
                                                            ~^~~~~~~
21
*:\*\problem\problem.ino:25:30: note: in expansion of macro 'digitalPinToPCICR'
22
 static_assert((uint8_t*)0 != digitalPinToPCICR(HALL_SPEED_PIN), "HALL_SPEED_PIN auf Pin legen, der Interrupt on Change unterstützt");
23
                              ^~~~~~~~~~~~~~~~~
24
exit status 1
25
Compilation error: non-constant condition for static assertion

Und jetzt meine Fragen :
Was ist reinterpret_cast ? Wieso ist danach eine Konstante nicht mehr 
konstant ? Wie kann ich das umgehen ?

von Jens M. (schuchkleisser)


Lesenswert?

Da bin ich ja mal gespannt.

Normale Leute hätten einfach ein define für die Pinnummer genommen und 
einen Kommentar drangeschrieben wg. des PCI.
Ist ja schließlich Arduino, nicht Automotive...

von Flunder (flunder)


Lesenswert?

Jens M. schrieb:
> Da bin ich ja mal gespannt.
>
> Normale Leute hätten einfach ein define für die Pinnummer genommen und
> einen Kommentar drangeschrieben wg. des PCI.
> Ist ja schließlich Arduino, nicht Automotive...

Mist, erwischt ! Sonst muss ich mich beim Programmieren an MISRA halten. 
Der Mensch gewöhnt sich doch irgendwie an alles.

von Oliver S. (oliverso)


Lesenswert?

Flunder schrieb:
> Was ist reinterpret_cast ?
Ein cast zwischen nicht kompatiblen Typen.

> Wieso ist danach eine Konstante nicht mehr konstant ?
Weil sie es vorher auch nicht war.

> Wie kann ich das umgehen ?

Jens M. schrieb:
> Normale Leute hätten einfach ein define für die Pinnummer genommen und
> einen Kommentar drangeschrieben wg. des PCI.
> Ist ja schließlich Arduino, nicht Automotive...

Oder einfach: Versuch erst gar nicht, dich absichtlich von hinten durch 
die Brust ins Auge zu schiessen. Keep it simple and stupid...

Oliver

: Bearbeitet durch User
von Jens M. (schuchkleisser)


Lesenswert?

Oliver S. schrieb:
> Keep it simple and stupid

Ich kenn's eigentlich als "Keep it simple, stupid", wobei hier "stupid" 
als Anrede gemeint ist, nach dem Motto "Mach dir das Leben nicht so 
schwer, du Depp".

von Flunder (flunder)


Lesenswert?

Oliver S. schrieb:

>> Wieso ist danach eine Konstante nicht mehr konstant ?
> Weil sie es vorher auch nicht war.

Äh, wenn die Zahl 104, die da irgendwo als Literal steht nicht konstant 
ist, dann habe ich jetzt echt ein Verständnisproblem.

Oder ist das ein Witz wie 
https://www.gutefrage.net/frage/225-fuer-extrem-grosse-werte-von-2

Ein Zeiger, der immer auf Adresse 104 zeigt, sollte doch auch konstant 
sein. Ob das, wohin er zeigt konstant ist, ist ja schließlich eine 
andere Frage.

Es sieht doch so aus, als ob durch den reinterpret_cast die Eigenschaft 
konstant verloren geht. Wie kann man die erhalten ?

Ok, in der Tat schwierig, da ich mittlerweile im Web was gefunden habe, 
dass der gcc das lange entgegen dem Standard umgesetzt hat, dass aber 
dieses Verhalten, dass der jetzt von Arduino genutzte Compiler an den 
Tag legt, das richtige ist - wenn auch nicht das von mir gewünschte.

Warum fordert der C++ Standard also dieses Verhalten und wie teile ich 
einem C++ Compiler mit, dass ich es anders brauche ?

von Klaus H. (klummel69)


Lesenswert?

Glaub mir, es liegt nicht am Compiler oder am Standard. Schau Dein Makro 
an.
Du lieferst in digitalPinToPCICR den Wert (&PCICR) zurück.
Das Makro PCICR nutzt intern  (*(volatile uint8_t *)(mem_addr)).
Damit ist die Rückgabe natürlich nicht mehr konstant.

Ein cast hilft da nicht, dann wird der Compiler trotzdem ein 
„non-constant condition for static assertion“ melden.

: Bearbeitet durch User
von Flunder (flunder)


Lesenswert?

Dummerweise ist das nicht mein Makro, sondern eins, dass von Arduino 
mitgeliefert wurde. Also eventuell von jemandem stammt, der mittlerweile 
Microchip auf der Visitenkarte stehen hat.

Bei Stack Overflow habe ich mittlerweile die noch verwirrendere Lösung 
für das verwirrende Problem gefunden.
1
uint8_t eda;
2
#pragma push_macro("PCICR")
3
#undef PCICR
4
#define PCICR eda
5
static_assert((uint8_t*)0 != digitalPinToPCICR(HALL_SPEED_PIN), "HALL_SPEED_PIN auf Pin legen, der Interrupt on Change unterstützt");
6
#pragma pop_macro("PCICR")

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Flunder schrieb:
> Es sieht doch so aus, als ob durch den reinterpret_cast die Eigenschaft
> konstant verloren geht.

So ist es:

> § 5.19/2 Constant Expressions [expr.const] the result of a
> reinterpret_cast can't be a constant expression

von Klaus H. (klummel69)


Lesenswert?

Flunder schrieb:
> Bei Stack Overflow habe ich mittlerweile die noch verwirrendere Lösung
> für das verwirrende Problem gefunden.

Ach du Scheiße, das verwirrt den Verwirrten noch mehr….

Dann würde ich eher auf static_assert verzichten und es zur Laufzeit 
beim Start-Up überprüfen, z.B. per assert().

von Klaus H. (klummel69)


Lesenswert?

Johann L. schrieb:
> So ist es:
> § 5.19/2 Constant Expressions [expr.const] the result of a
> reinterpret_cast can't be a constant expression

Das ist aber nicht das eigentliche Problem.
Das eigentliche Problem ist, dass die Makros den Inhaltsoperator * 
nutzen und der Compiler natürlich nicht den Inhalt des Registers kennt. 
Daher funktioniert static_assert nicht. Da hilft kein cast.


Nachtrag:
>Dann würde ich eher auf static_assert verzichten und es zur Laufzeit
>beim Start-Up überprüfen, z.B. per assert().

Falsch gedacht, das funktioniert IMHO nicht, da der Inhaltsoperator 
natürlich auch auf der Adresse 0 zurück liefern kann.

: Bearbeitet durch User
von Flunder (flunder)


Lesenswert?

Klaus H. schrieb:
> Falsch gedacht, das funktioniert IMHO nicht, da der Inhaltsoperator
> natürlich auch auf der Adresse 0 zurück liefern kann.

Ne, nach dem * kommt doch auch wieder ein &. Das kommt vermutlich daher, 
weil hier mindestens 3 Programmierer am Werk waren und einer die Makros 
des anderen nutzt. Und das frei nach dem Motto : was nicht passt, wird 
passend gemacht.

von Flunder (flunder)


Lesenswert?

Schon ein
1
constexpr uint8_t* hurz = (uint8_t*)42;

gibt Mecker, weil eine Zahl nicht so einfach in einen Zeiger gewandelt 
werden kann.

Das sieht der Compiler als "Ein cast zwischen nicht kompatiblen Typen" 
an, und streicht dem Pointer auf die feste Adresse 42 gemäß "§ 5.19/2" 
die Eigenschaft konstant.

Gut, im Buch kommt ja auch irgendwann raus, dass es 56 statt 42 hätte 
heißen müssen. So etwas kann aber doch nicht der Grund für die 
Einschränkung beim Typecast sein.

von Klaus H. (klummel69)


Lesenswert?

Flunder schrieb:
> Ne, nach dem * kommt doch auch wieder ein &.

Aber das ist das Problem.

In deinem Beispiel würde folgendes ausgeführt:
1
&(*(volatile uint8_t *)(hurz))

Er holt den Inhalt und macht aus dem Inhalt einen Zeiger.
Damit ist das Ganze abhängig vom Inhalt des Registers PCICR:

 (&(*(volatile uint8_t *)(0x68))

&(*(&(*(&x))) ist nicht die Adresse von x....

: Bearbeitet durch User
von Flunder (flunder)


Lesenswert?

Äh ne :
1
&hurz
gibt einen Zeiger auf hurz.
1
(uint8_t*)hurz
bastelt einen Zeiger aus dem Inhalt von hurz.

von Harald (hasanus)


Lesenswert?

Flunder schrieb:
> So etwas kann aber doch nicht der Grund für die
> Einschränkung beim Typecast sein.

Doch. Constexpr verlangt dass der L - Wert auch zur Kompilierzeit 
auswertbar ist. Der Compiler weiß aber nicht was an Addresse 42 ist.

Ungekehrt ist
    const uint8_t* hurz = (uint8_t*)42;
erlaubt, denn hurz wird hier zur Laufzeit initialisiert.

von Flunder (flunder)


Lesenswert?

Harald schrieb:
> Der Compiler weiß aber nicht was an Addresse 42 ist.

Muss er doch auch nicht. Was er mit der Adresse anfangen soll, sagt ihm 
ja das Programm. Im Fall von constexpr soll er mir ja nur einen anderen 
Namen für "Zeiger auf Adresse 42" anlegen, damit ich nicht bei jedem 
Zugriff auf Adresse 42 einen Kommentar dazu schreiben muss, worauf ich 
da eigentlich zugreife.

Die Frage wieso müsste ich vermutlich Bjarne Stroustrup stellen. Er wird 
sich bestimmt etwas dabei gedacht haben. Vielleicht hat er es sogar 
schon irgendwo aufgeschrieben.

von Cyblord -. (cyblord)


Lesenswert?

Ich möchte den Titel dieses Threads schon mal zum "Bescheuertsten Titel 
des Jahres 2025" nominieren.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.