Frage zu C++, warum ist nach 2 Mal rein in die/raus aus den Kartoffeln 104 nicht mehr konstant?

OP #7810406
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 ?

#7810419
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

OP #7810487
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 ?

#7810500
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.

OP #7810517
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")
#7810529
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.

OP #7810542
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.

OP #7810552
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.

#7810577
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....

OP #7811013
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.

#7811516
Lesenswert?

Flunder schrieb:

Im Fall von constexpr soll er mir ja nur einen anderen Namen für "Zeiger auf Adresse 42" anlegen,

Du verlangst Unmögliches. Nimm stattdessen volatile uint8_t const* PcIcr = digitalPinToPCICR(HALL_SPEED_PIN); und lass den static_assert weg.

Dann kannst Du PcIcr wie ein Register einsetzen: *PcIcr = 2; // schreiben auto x = *PcIcr; // lesen

und bist dennoch vor unbeabsichtigtem Ändern des Zeigers sicher: // PcIcr = 0; //kompiliert nicht, da der Zeiger const ist

Irgendwo im code brauchst du noch einen sanity check als Ersatz für den static_assert:

main() { if (PcIcr == nullptr) blink_forever(); . . . }

Bedingte Compilierung oder if constexpr für einen "Testmode" empfielt sich dann. Und natürlich muss der PcIcr zugewiesene Wert auch zum Pin passen.

Ich würde dieses Makro daher erst gar nicht verwenden, sondern Pin und PCICR in der config.h aufführen.

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