ah verdammt, welcher Pin wird mit 8 gesetzt? ist das jetzt die überarbeitete Generation oder noch die alte?
außerdem kümmert sich der Präprozessor darum, dass (1 << PINB3) zu 8 wird (oder auch zu 1 oder 2 oder 4 oder was auch immer da mit einer neuen µC Familie daher kommen könnte)
In C hat sich die Shift-Schreibweise durchgesetzt.
Andere Dialekte als AVR-C verwenden die Schreibweise ja auch.
Aber: Mach wie du willst. Das stößt höchtens jemandem sauer auf, der an die Shift-Schreibweise gewöhnt ist (als ca. 90% aller C-Programmierer).
Auf jeden Fall besser als magic numbers.
ah verdammt, welcher Pin wird mit 8 gesetzt? ist das jetzt die
überarbeitete Generation oder noch die alte?
Meinst du mit "Generation" die Menschen ?
Haa ! Da kommen wir zusammen. (Wobei ich alt bin aber auch ständig überarbeitet war ...)
Das wäre dann also ein Ratespiel, 2 hoch was gibt denn 8 ??
Erzähl mit doch nicht, daß es irgendjemand gibt, der C programmiert, aber nicht die 2-er Potenzen wenigstens bis 256 auswendig kennt ....
Daß der Compiler 8 draus macht ist ja klar, sonst könnte er auf Maschine ja nicht maskieren :-)
Einser dreimal links weil PINB3 mit 3 vorbelegt,
ist exakt dasselbe wie 2^3=8 ... was soll's also ???
Weil es direkt lesbar ist, welches Bit (NAME!) gemeint ist, vor allem bei den Spezialregistern! Da muss man icht wissen, welcher Bitnummer das ist, das weil der Compiler durch die Headerfiles.
An sich ist die Version ala MSP430 besser, man spart sich Tiparbeit. Ist aber historisch bedingt beim avr gcc nicht so, es sei denn, einer konvertiert ALLE Headerfiles!
In C hat sich die Shift-Schreibweise durchgesetzt.
Nein. Auf dem avr gcc vielleicht, bei anderen Compilern bzw. CPUs nicht unbedingt.
Auf jeden Fall besser als magic numbers.
Stimmt. Das ist der Hauptgrund. Der Compiler soll schließlich was für sein geld machen. Quelltext soll so einfach, eindeutig und selbsterklärend wie möglich sein, fast so, wie ein normaler Text geschrieben ist.
Das wäre dann also ein Ratespiel, 2 hoch was gibt denn 8 ??
Erzähl mit doch nicht, daß es irgendjemand gibt, der C programmiert,
aber nicht die 2-er Potenzen wenigstens bis 256 auswendig kennt ....
Ja ganz toll, vor allem für andere Register zB. Timer, hilft sicher extrem beim Debuggen irgendeines komplexen Problems...
testbyte = PINB & 8
ist ja maximal bescheuert, es geht um Pin B3 aber man hat 8 als magic number im Code, wirklich super.
testbyte = PINB & (1 << 3)
ist ok
testbyte = PINB & (1 << PINB3)
ist besser
außerdem macht es alleine von der "Nachdenklogik" absolut Sinn, man nimmt ein Bit, und schiebt es an den richtigen Platz, versteht jeder.
Besser ist es noch bei den neuen AVRs zB. Atmega 0 series:
PORTB.OUTSET = PIN3_bm;
Jetzt ist es absolut verständlich:
Aha PORTB
aha OUTSET (also Ausgang setzen statt früher DDRB)
aha PIN Nr. 3
Ist das nicht ein wenig Kindergarten (oder Arduino ;-) ?
Motiv meiner Frage war : ich werde selbst manchmal gefragt, warum dieser komische Shift benutzt wird, und weil mir keine sinnvolle Antwort einfällt, hab ich jetzt mal hier gefragt.
Also Antwort : damit auch absolute Anfänger den Code besser lesen können.
Ist halt heutzutage so. Früher haben die Leute erst Digitaltechnik gelernt, und dann mit dem Programmieren angefangen.
(Wichtig : wie gesagt bitte nicht falsch verstehen. Ich bezeichne damit ausdrücklich keinen der hier Antwortenden als Anfänger, Kindergarten oder ähnlich !!!)
Nein. Auf dem avr gcc vielleicht, bei anderen Compilern bzw. CPUs nicht
unbedingt.
Dann schränke ich meine Aussage ein: Die C-Libraries, mit denen ich bis jetzt gearbeitet habe, verwenden die Shift-Schreibweise.
(Und damit gilt natürlich auch meine Verallgemeinerung bzgl 90% nicht mehr).
Also Antwort : damit auch absolute Anfänger den Code besser lesen
können.
Nein. Damit jeder, vom Anfänger bis zum Profi, den Code schneller und besser lesen kann.
Im Falle der Port-Register mag das auch noch ohne gehen, aber bei allen anderen Registern kann niemand mit Magic Numbers etwas anfangen, dagegen mit den Bitnamen sehr wohl.
Ist das nicht ein wenig Kindergarten (oder Arduino ;-) ?
Nö, das funktioniert ohne Shift nur deshalb so gut weil die meisten AVR Registerbits die Bitnummer im Namen haben.
Jetzt willst Du aber mal z.B. ein Bit maskieren, das nicht die Bitnummer im Namen hat. Dann fängst Du erstmal mit dem Datenblatt an....
Also machen wir es doch lieber einheitlich.
nicht auf den ersten Blick lesen könnt. Ihr seid doch Profis hier.
Und wenn ich das Datenblatt z.B. von einem AVR lese, stehen die Bitpositionen aller Register ja sogar grafisch dargestellt drin, also "so what" ?
btw : ich habs mal so oder ähnlich gelernt (lesbarkeit..) :
test = test | 40 ; // test = test ODER 2^5 ODER 2^3
Ok, ich danke allen für die Beiträge, mitgenommen habe ich, daß die Shiftschreibweise eine Konvention ist, die vermutlich eine bessere Lesbarkeit erzeugen soll. Meinem "Publikum" gebe ich ggf. dann noch mit, daß 2-er Potenzen und Bitmaskierung unverzichtbare Grundlagen darstellen, wenn ich merke daß irgend jemand test = 40 nicht lesen kann ;-)
ah verdammt, welcher Pin wird mit 8 gesetzt? ist das jetzt die
überarbeitete Generation oder noch die alte?
Meinst du mit "Generation" die Menschen ?
na, eine hypothetische überarbeitete Generation des Prozessors, bei dem halt aus irgend einem Grund die Register anders belegt sind als bei der alten. Gleiche Eigenschaften, aber halt an einem anderen Bit (z.B. 4) wegen irgendeinem Padding oder so.
Ist ja wurscht, es geht ums Prinzip - PINB3 ist ein definierte Offset, den man ggf. an einer Stelle ändert und alle Vorkommen im Code passen sich automatisch an. Ohne dass man nach Vorkommen von "8" suchen muss und diese dann einzeln prüfen ob da jetzt was anderes hin kommt oder nicht.
Aber keine Binärfreaks oder Hex-Freaks wie der gute Josef G.
Und wenn ich das Datenblatt z.B. von einem AVR lese, stehen die
Bitpositionen aller Register ja sogar grafisch dargestellt drin, also
"so what" ?
Und die willst du ALLE im Kopf behalten? Viel Spaß!
Diese Information ist nur INDIREKT wichtig, so wie ein Telefonbuch! DIREKT interessiert der BitNAME und dessen Funktion!
Du kannst auch gern die Namen der Register weglassen und direkt die Adressen ansprechen. Viel Spaß!
Ok, ich danke allen für die Beiträge, mitgenommen habe ich, daß die
Shiftschreibweise eine Konvention ist, die vermutlich eine bessere
Lesbarkeit erzeugen soll.
Das tut sie auch!
Meinem "Publikum" gebe ich ggf. dann noch mit,
daß 2-er Potenzen und Bitmaskierung unverzichtbare Grundlagen
darstellen, wenn ich merke daß irgend jemand test = 40 nicht lesen kann
;-)
Programierer wollen sich, nicht erst heute, nicht mit so einem Krümelkram befassen, der DEUTLICH besser vom Compiler verwaltet wird!
Das, was du da veranstaltest, erinnert mich schon ein wenig daran. Wenn andere das anders schreiben wollen als du, was ist denn daran grundsätzlich schlecht?
Meinem "Publikum" gebe ich ggf. dann noch mit, daß 2-er Potenzen und
Bitmaskierung unverzichtbare Grundlagen darstellen, wenn ich merke daß
irgend jemand test = 40 nicht lesen kann ;-)
Wenn schon einzelne Bits gemeint sind, dann gibt man den Wert nicht dezimal mit 40 an, sondern hexadezumal mit 0x28. Da sieht man das Bitmuster sofort. Man muss aber trotzdem im Datenblatt nachschaeun, welche Funktionen mit den Bits denn nun tatsächlich gesetzt werden. Und mit
test = (1<<ABC) | (1<<XYZ); ist softort erkennbar, dass die Funktionen ABC und XYZ gemeint sind.
ich habs mal so oder ähnlich gelernt (lesbarkeit..) :
test = test | 40 ; // test = test ODER 2^5 ODER 2^3
Wofür soll das gut sein? Da steht einfach nur die Codezeile im Kommentar in anderen Worten nochmal da. Und sobald etwas geändert wird, ist der Kommentar automatisch falsch oder er muss überarbeitet werden. Wenn ich aber
test = (1<<ABC) | (1<<XYZ);
abändere in
test = (1<<ABC) | (1<<DEF);
dann muss ich keinen Kommentar ändern und der Code ist immer noch leserlich und quasi selbstkommentierend.
Meinem "Publikum" gebe ich ggf. dann noch mit, daß 2-er Potenzen und
Bitmaskierung unverzichtbare Grundlagen darstellen, wenn ich merke daß
irgend jemand test = 40 nicht lesen kann ;-)
Wenn schon, dann nicht dezimal, sondern hex 0x28. Da sieht man das
Bitmuster sofort.
Aber auch nur, wenn es ein Bit pro Nibble ist. Was ist 0xCA? Jaja, man kann auch die 16 Nibble auswendig lernen und lesen, ist aber nerviger Unsinn.
dann muss ich keinen Kommentar ändern und der Code ist immer noch
leserlich und quasi selbstkommentierend.
Ok, ich danke allen für die Beiträge, mitgenommen habe ich, daß die
Shiftschreibweise eine Konvention ist, die vermutlich eine bessere
Lesbarkeit erzeugen soll. Meinem "Publikum" gebe ich ggf. dann noch mit,
daß 2-er Potenzen und Bitmaskierung unverzichtbare Grundlagen
darstellen, wenn ich merke daß irgend jemand test = 40 nicht lesen kann
;-)
Ich hoffe mal stark, das dein Publikum keine Schüler sind.
Um es mal für dich etwas deutlicher darzustellen:
UCSR0B |= (1<<TXEN0);
UCSR0B |= 8;
Macht beides das gleiche. Der absolute Anfänger kann mit beiden Zeilen nichts anfangen. Jemand mit etwas Erfahrung sieht in der ersten Zeile sofort, was passiert, in der zweiten aber nicht.
Das hat überhaupt nichts damit zu tun, ob man Zweierpotenzen im Kopf ausrechnen kann oder nicht.
Das hat überhaupt nichts damit zu tun, ob man Zweierpotenzen im Kopf
ausrechnen kann oder nicht.
Eben. Man will die Codierung der Bits dort lassen wo sie hingehört. Im Compiler. Menschen können mit Namen deutlich mehr anfangen als Nummern. TXEN sagt mehr als 8, erst recht wenn es von diesen Codes tausende gibt!
Also Antwort : damit auch absolute Anfänger den Code besser lesen
können.
Nöö.
Weil es jeder besser lesen kann.
Weil sofort klar ist, was man beabsichtigt.
Weil man kein Masochist ist.
Weil es völlig egal ist, auf welchem Bit der Pin oder z.B. beim TIMSK-Register die Bits TOIE0 und TOIE1 gemappt sind. Die beiden letzteren liegen z.B. beim Tinyx5 auf Bit 1 und Bit 2. Das interessiert einfach nicht und kann beim nächsten Prozessortyp anders festgelegt sein. Und schon muss dein Code intensiv durchforstet und mit dem Datenblatt abgeglichen werden, und meiner nicht.
Selbst bei den Ports könnte das sein. Mich interessiert doch, was TOIE0 und TOIE1 bewirken und nicht auf welchem Bit sie festgelegt sind. Dafür hat jemand einmal die Headerfiles erstellt ...
Und wenn man schon direkt Zahlen angeben will, dann wenigstens im binären oder gerade noch im hexadezimalen Format, aber ganz bestimmt nicht in DEZ!
Klar, man kann schon mal schreiben
1
DDRB=0;
2
PORTB=0xFF;
wenn man alles auf Eingang schalten und die Pullups am Port aktivieren will. Aber sonst?
Meinem "Publikum" gebe ich ggf. dann noch mit,
daß 2-er Potenzen und Bitmaskierung unverzichtbare Grundlagen
darstellen, wenn ich merke daß irgend jemand test = 40 nicht lesen kann
;-)
Programierer wollen sich, nicht erst heute, nicht mit so einem
Krümelkram befassen, der DEUTLICH besser vom Compiler verwaltet wird!
Nicht nur das, es ist vor allem eine Fehlerquelle weniger!
Wenn ich magic numbers verwende, und etwas ändere, z.B. die LED in einer neuen Platinenversion an einem anderen Portbit anschließe, dann muß ich mühsam alles durchsuchen und anpassen. Mit der ständigen Gefahr eine Stelle zu übersehen oder eine falsche mitzuändern
Mit Portnamen und Pinbezeichnern muß man bei so einer Änderung nur eine Stelle in einem Headerfile anpassen.
Außerdem vermeidet man auch gleich im Ansatz die Fehlerquelle, daß Kommentar und magic number auseinanderlaufen könnten, was dann umso verwirrender wäre.
aber immer noch deutlich schlechter als die direkten Bitnamen.
Ja nun, es gibt darüber hinaus auch noch Situationen, wo mehrere Bits im Verband angesprochen werden und Bitmuster wieder sinnvoll sind (z.B. wenn am Port ein Lauflicht dargestellt werden soll).
Ganz ungünstig ist dann, wenn der Programmierer nur diese "Bitschieberei" kennt und die Bitreihenfolge z.B. bei einem Multiplexer nur partiell oder sogar "verkehrt herum" auscodiert:
Gibt ja so viele Möglichkeiten. Ich finde es immer ganz gut wenn jeder Pin eindeutig zu finden ist, und zu jeden Pin gehören die entsprechenden Register...
Die 4-Bit Binär vs. Hex-Darstellung eines Nibbles dürften die meisten Entwickler genauso einfach im Kopf parat haben, wie sie z.B. Widerstands-Beschriftungen, seien es Farbringe oder Ziffern auf SMDs, nicht mehr "buchstabieren", sondern auf einen Blick als Wort lesen.
Solche Fähigkeit hilft ungemein beim Debuggen, wenn man auf die Schnelle Registerinhalte aufschlüsseln möchte.
Dennoch käme ich nie auf die Idee magic numbers in meinen Programmen zu verwenden, ganz gleich in welcher Programmiersprache auch immer.
Das ist zwar semantisch Blödsinn, funktioniert aber trotzdem.
Oder
1
testbyte=PINB*3;
Auch das ist Blödsinn, der Compiler hindert Dich aber nicht daran.
Das dieser Mist möglich ist (sprich: nicht zur Compilezeit aufgedeckt
werden kann), liegt daran, dass hier Typsicherheit fehlt.
PINB ist leider als volatile uint8_t definiert. Und (1<<PINB3)
hat den Typ int. Und ist damit implizit konvertierbar. Weiterhin sind
sowohl uint8_t als auch int arithmetische Typen. Damit ist also
etwa auch * oder / dafür definiert, was bei der hier angesprochenen
Verwendung natürlich keinen Sinn macht.
PINB oder DDRB oder PORTB.OUTSET ist erstmal nur eine Ansammlung
von Bits, sonst nichts. Solche Bitmuster möchte man gerne mal schieben,
aber eher selten als Ganzzahl interpretieren. Und wenn doch, dann sollte
der Code das klar aussagen.
AUßerdem ist natürlich auch
1
TCCR0A=(1<<CS00)
falsch und sollte deswegen gar nicht erst compilieren.
Leider kannst Du das mit C nicht erreichen.
Wobei natürlich
das von Dir oben angesprochene "Arduino"-Kindergartenzeug C++ ist. Und
in C++ ist das recht elegant möglich.
Abgesehen davon, dass bitfields stark implementation-defined sind,
Sind sie, kann man aber trotzdem machen. Wir auch so gemacht.
mach mal bitte ein Beispiel mit folgendem fiktivem Register:
Hab ich dir schon gezeigt. Folge meinem Link, dort ist ein Dokument von TI, wie sie es im Code Composer Studio machen, zumindest für die C2000er Serie.
Das ist wieder mal ein sehr schönes triviales Thema, zu dem jeder seine eigene ganz feste und ganz richtige Meinung haben kann.
Wenn du lieber 8 statt 1<<3 schreibst und das für genau so lesbar hältst, dann mache es halt so.
Ich mache es nicht so, denn ich halte es für schwer verständlich das dritte Bit mit 8 zu beschreiben, obwohl es natürlich vollkommen richtig ist.
Vieeeeel viel wichtiger als Neulingen einen ganz bestimmten "richtigen" Stil beizubringen, ist zu vermitteln, dass ein konsistenter Stil viel wichtiger ist!
Wenn ich eine Änderung in einem Stück Code mache, wo Masken alle als Zahl ohne Shift dargestellt sind, dann halte ich mich daran und füge neue Masken auch als Zahl ohne Shift ein. Konsistenz ist viel wichtiger als irgendeine Stil-Religion, weil Konsistenz die mentale Last absenkt. Darunter fallen dann auch so Dinge wie Formatierungsstil.
Abgesehen davon, dass bitfields stark implementation-defined sind,
Sind sie, kann man aber trotzdem machen. Wir auch so gemacht.
mach mal bitte ein Beispiel mit folgendem fiktivem Register:
Hab ich dir schon gezeigt. Folge meinem Link, dort ist ein Dokument von
TI, wie sie es im Code Composer Studio machen, zumindest für die C2000er
Serie.
Enums werden dabei nicht verwendet. Die Bits bzw. Bitgruppen werden per Name angesprochen. Damit kann man nicht die falschen Bits im falschen Register ansprechen.
Enums werden dabei nicht verwendet. Die Bits bzw. Bitgruppen werden per
Name angesprochen. Damit kann man nicht die falschen Bits im falschen
Register ansprechen.
Enums werden dabei nicht verwendet. Die Bits bzw. Bitgruppen werden per
Name angesprochen. Damit kann man nicht die falschen Bits im falschen
Register ansprechen.
Ok,geht also nicht in C
Das war gar nicht dein Argument! Sondern daß man falsche Bits in falschen Registern ansprechen kann!
"AUßerdem ist natürlich auch
TCCR0A = (1 << CS00)
falsch und sollte deswegen gar nicht erst compilieren.
Also Antwort : damit auch absolute Anfänger den Code besser lesen
können.
Ist halt heutzutage so. Früher haben die Leute erst Digitaltechnik
gelernt, und dann mit dem Programmieren angefangen.
Eigentlich ist es genau andersrum. Mit einem
PORTB = PORTB & (1 << PORTB3);
kann ein absoluter Anfänger nichts anfangen. er versteht das Shiften nicht und weiß auch nicht, was PORTB3 ist. Er weiß nichtmal ob es eine Variable, ein Define oder sonst etwas ist. Da ist die Version mit der 8 einfacher für das Grundverständnis, da weiß man sofort, dass es um eine Zahl geht, in dem Beispiel dem Port also ein Wert zugewiesen wird. Das Rückrechnen auf den Pin ist relativ intuitiv. Für den Profi ist aber das schnelle Lesen und Vermeiden von unnötigem Nachdenken und Verringerung der Gefahr etwas falsch zu verstehen so wertvoll, dass es sich für ihn lohnt, das 1er-Shiften als Konvention zu lernen und er weiß auch sofort, was das PORTB3 wohl beinhaltet.
nur sind typsichere C++ Registerdefinitionen nicht Standard bei den Hersteller SDK, für einen STM32H7 möchte ich das nicht selber schreiben.
STM hat ja schon ewig gebraucht den 'register' storage specifier zu entfernen.
Ich weiß nicht wo du das her hast. Solche Zeilen würdest du bei mir nur in temporärem Code für Experimentierbretter finden, wo z.B. ein Taster mit B3 beschriftet ist.
In sinnvollen Programmen würde eher so etwas stehen:
if PINB & (1<<ENDSCHALTER) {...}
Oder
UCSR3B = (1<<RXEN3) | (1<<TXEN3) | (1 << RXCIE3);
Da sieht man an den Namen der Bits ungefähr, worum es in der Zeile geht.
Wenn ich schon weiß, daß das ein shift-Befehl ist, dann werd ich wohl
auch wissen, was 2^3 ist, oder ?
Wetten, dass du das mit 32 Bit Registern nicht so einfach im Kopf hinbekommst? Ich vermute stark, dass du dir die Kopf-Rechnerei bei der Programmierung von ARM Controllern ganz schnell abgewöhnst.
Warum schreiben die Meisten in AVR-C :
testbyte = PINB & (1 << PINB3)
Bisher hatte ich mir immer eingebildet, das käme der Portabilität zugute. Bei Pin3 von PortB ists wohl nicht so notwendig, aber bei
UART0CONTROL = UART0CONTROL | (1<<TXEnable)
erlaubt es dem Hersteller, das Bit mal an einer anderen Stelle zu plazieren und in einem geänderten Headerfile anders anzugeben. Der Programmierer muß dann nichts ändern, das Headerfile richtets.
Aber nachdem hier über 50 Posts nur über Schönheit diskutieren, ist meine Sicht der Dinge wohl ziemlich abartig. Beauty rules!
Ich weiß nicht wo du das her hast. Solche Zeilen würdest du bei mir nur
in temporärem Code für Experimentierbretter finden, wo z.B. ein Taster
mit B3 beschriftet ist.
In sinnvollen Programmen würde eher so etwas stehen:
Wenn ich schon weiß, daß das ein shift-Befehl ist, dann werd ich wohl
auch wissen, was 2^3 ist, oder ?
Ja, aber woher weißt du was die 8 bedeutet? Mit (1 << PB3) weißt du, dass das 3. Bit hier gesetzt werden soll. Und das ganze ganz ohne in das Datenblatt zu schaun. OK, bei simplen Port-Routinen gehts vielleicht auch noch mit den Magic Numbers aber was ist z.B. wenn du denn ADC eines Atmegas starten willst? Meinst du nicht, dass ein "ADCSRA |= (1 << ADSC);" einfacher zu verstehen ist als ein "ADCSRA |= 64;"? (und ja, die 64 musste ich jetzt echt im Datenblatt nachlesen)
Keine Ahnung, wie du darauf kommst, dass mehr Kapselung grundsätzlich zu mehr Übersicht führt. Aber das ist doch offensichtlicher Unsinn. Schaue dir den Grenzfall an: Wenn ich alles immer weiter rekursiv kapsele, dann soll das der Übersicht helfen? Das Gegenteil ist der Fall.
Bei
if (ENDSCHALTER) foo(); else LED_WARNING_ON;
sehe ich überhaupt nicht mehr, was passiert. Das kann natürlich in einigen Fällen hilfreich sein. In anderen aber auch wieder nicht.
Und warum nicht so, wenn Kapselung doch die Übersicht verbessert?
#define REAKTION_AUF_ENDSCHALTER if (ENDSCHALTER) foo(); else LED_WARNING_ON
nur sind typsichere C++ Registerdefinitionen nicht Standard bei den
Hersteller SDK, für einen STM32H7 möchte ich das nicht selber schreiben.
STM hat ja schon ewig gebraucht den 'register' storage specifier zu
entfernen.
Tja, das ist zwar traurig, aber nicht mein Problem.
Sowas macht man mit einem Skript, was die ganzen templates aus den XML-Beschreibungen der µC automatisch erzeugt.
Wetten, dass du das mit 32 Bit Registern nicht so einfach im Kopf
hinbekommst? Ich vermute stark, dass du dir die Kopf-Rechnerei bei der
Programmierung von ARM Controllern ganz schnell abgewöhnst.
Keine Ahnung, wie du darauf kommst, dass mehr Kapselung grundsätzlich zu
mehr Übersicht führt. Aber das ist doch offensichtlicher Unsinn. Schaue
dir den Grenzfall an: Wenn ich alles immer weiter rekursiv kapsele,
Wo soll das Rekursion sein? Das sind einfahe Abstraktionsebenen, auch HAL genannt (Hardware Abstraction Layer).
dann
soll das der Übersicht helfen? Das Gegenteil ist der Fall.
Bei
if (ENDSCHALTER) foo(); else LED_WARNING_ON;
sehe ich überhaupt nicht mehr, was passiert.
Das WILL ich in den meisten Fällen auch gar nicht! Bei der Anwendung von Sensoren und Aktoren will ich mich um die Hardwaredetails NICHT mehr kümmern. Das mache ich einmalig, wenn ich die HAL aufschreibe. Ebenso in Funktionen. Wie die INTERN arbeiten, intertessiert beim AUFRUF/Anwendung NICHT! Im Gegenteil.
Das kann natürlich in
einigen Fällen hilfreich sein. In anderen aber auch wieder nicht.
Und warum nicht so, wenn Kapselung doch die Übersicht verbessert?
#define REAKTION_AUF_ENDSCHALTER if (ENDSCHALTER) foo(); else
LED_WARNING_ON
REAKTION_AUF_ENDSCHALTER;
Man kann alles übertreiben. Du bist ein Meister darin.
Wasser ist lebensnotwendig. 10l auf einmal "getrunken" tödlich, aka Ertrinken (oder Wasservergiftung).
Ich bin ja kein Softwerker, aber soviel hab selbst ich Hardwaredepp mal vor vielen Jahren im Studium gelernt.
Und 0xCA ist garantiert leichter in einzelne Bits umzurechnen als 204.
204 ist nämlich nicht 0xCA, sondern 0xCC. Das sieht man aber eben ohne viel Umrechnerei auf keinen Fall. Deshalb nimmt man für Bitmuster (wenn sie schon als "Magic Number" hingeschrieben werden sollen) immer Hex-Zahlen.
Ich möchte einfach, dass ein Unsinn wie
... nicht kompiliert.
Geht leider nicht. In den Headern von STM32 werden Fehler allerdings recht wirksam vermieden, indem die Namen der Bit-Konstanten mit dem Namen des Register beginnen.
204 ist nämlich nicht 0xCA, sondern 0xCC. Das sieht man aber eben ohne
viel Umrechnerei auf keinen Fall. Deshalb nimmt man für Bitmuster (wenn
sie schon als "Magic Number" hingeschrieben werden sollen) immer
Hex-Zahlen.
Wenn du Binärzahlen gefordert hättest, ergäbe der Satz sehr viel mehr Sinn.
Muss er ja auch gar nicht sofort verstehen. Oder verstehst du immer
sofort alles?
Als Anfänger sieht man den Wald vor lauter Bäumen nicht, da ist soetwas nicht hilfreich. Mir scheint die Meisten hier haben überhaupt keine Ahnung mehr, wieviel "Beiwissen" für das Verständnis von Programmcode erforderlich ist.
Siehe:
und weiß auch nicht, was PORTB3 ist.
Was soll das denn anderes sein als Pin 3 auf PORTB?
Ein Anfänger versteht vielleicht, dass mit Pin 3 irgendwas gemacht werden soll, aber nicht, dass der Mechanismus dahinter einfach ein zugewiesener Zahlenwert ist.
Ist eigentlich ganz einfach. Schreibe die Zahl in einem gut lesbaren Format hin, wenn es um die Zahl geht.
Aber wenn eine bestimmte Funktion gemeint ist, dann nenne sie beim Namen.
3 ist keine gute Bezeichnung für den Endschalter. Und 4 ist keine gute Bezeichnung für den Status des Empfangspuffers. Ich will nicht wissen was mit der 4 los ist, sondern was mit dem Empfangspuffer ist. Also schreibe ich das auch so im menschen-lesbarer Form hin. Das ist schließlich der Sinn jeder Programmiersprache.
Mir scheint die Meisten hier haben überhaupt keine
Ahnung mehr, wieviel "Beiwissen" für das Verständnis von Programmcode
erforderlich ist.
Das ist mir sehr wohl bewusst.
Genau so könntest du aber argumentieren, dass das Konzept von Zahlen ansich viel zu kompliziert ist. Woher soll der Anfänger das denn bitte alles wissen? Es könnte sein, dass er noch in den Kindergarten geht.
Irgendwo muss man halt die Messlatte anlegen.
Und irgendwelche Dinge werden immer gelernt werden müssen.
Ich sehe da auch überhaupt kein Problem, wenn ein Anfänger erst einmal eine Woche zu 5 Programmzeilen recherchiert, bis er sie wirklich verstanden hat.
Ein Anfänger versteht vielleicht, dass mit Pin 3 irgendwas gemacht
werden soll, aber nicht, dass der Mechanismus dahinter einfach ein
zugewiesener Zahlenwert ist.
Was soll es denn sonst sein? Ein Mettbrötchen?
Ein Rechner kennt nur Zahlen. Deshalb heißt er so. Das sollte doch Allgemeinwissen sein.
Auf dem avr gcc vielleicht, bei anderen Compilern bzw. CPUs nicht
unbedingt.
Meine Vermutung ist, dass das damit zusammenhängt, wie die SBI- und CBI-Befehle beim AVR gestrickt worden sind: sie benutzen eine Bitnummer. Wenn man nun daraus eine Bitmaske machen muss, braucht man den Schiebebefehl, auch im Assemblercode.
AVR-C hat das von da geerbt, zumal SBI und CBI anfangs noch nicht vom Compiler selbst generiert werden konnten, sondern über inline-Makros produziert worden sind.
Der Rest der Welt benutzt schon immer vorzugsweise gleich Bitmasken für das Benennen von Bits (kann man prima sehen, wenn man sich irgendwelche UNIX-Quellen ansieht).
Hätte man beim AVR natürlich auch haben können: es gibt keinen Grund, warum der Assembler nicht hätte in der Lage sein sollen, ein
1
SBI PORTC, 8
selbst so umzurechnen, dass er im Opcode die Bitnummer 3 generiert (und sich über einen Semantikfehler erbricht, wenn jemand versucht, mehr als ein Bit im Operanden zu setzen).
Hat man aber nicht. Also schieben wir beim AVR fröhlich die Bitnummern herum. :-)
außerdem kümmert sich der Präprozessor darum, dass (1 << PINB3) zu 8
wird
Eine nicht ausrottbare Fehlinformation. Der Präprozessor rechnet nur in #if Statements und macht hier aus (1 << PINB3) lediglich (1 << 3). Die Rechnung erfolgt im Compiler selbst.
Über Geschmacksfragen kann man trefflich streiten. Ich auch. ;-)
Ein Charme von sowas wie
(1 << REGx_CB1)
und auch dem von manchen als absurd angesehenen
(0 << REGx_CB1)
ist, dass man im Quelltext danach suchen kann.
Ersetzt man die Bitangabe durch eine benannte Maske, spart man zwar Schreibarbeit, aber die zweite Variante fällt durch ersatzloses Fehlen der Bitbezeichnung auf. Und etwas, was fehlt, widersetzt sich jeder Suche im Quelltext. Man findet also jene Stellen, in denen es auf 1 gesetzt wird, nicht aber jene, in denen es auf 0 gesetzt wird.
Für die Maske empfiehlt sich deshalb also sowas wie
REGx_CB1_ENABLE
REGx_CB1_DISABLE
und hat dann halt jedes Bit zweimal im Header.
Bei der Variante, alle Bits eines Registers im Kopf zusammenzurechnen und als Ergebnis in Hex, Oktal oder Dezimal in den Quelltext zu werfen, empfehle ich ersatzweise Programmierung in INTERCAL. Das ist konsequenter. Ist dir das zu doof, nimm APL. Damit habe ich angefangen, weiss also, was Write-Only-Code ist.
Bei C++14 hatte man den Geistesblitz, das so schreiben zu dürfen:
REG |= 0b0000'0000'0000'1000'0000'0000'0000'0000;
Bei C23 wurde das übernommen.
Der Unterstrich wurde nicht benutzt, weil er ein gültiges Präprozessorsymbol ist und ja auch tatsächlich in der Form benutzt wird (beispielsweise in Systemen zur Internationalisierung von Texten).
Das waren bestimmt Deutsche, die den Geistesblitz hatten ...
Mit der von mir früher verwendeten US ASCII Tastatur wäre mir das nicht passiert. ;-)
Um das andere Ende ebenso zu würdigen: Vor Jahrzehnten verwendete ich eine Tastatur, die auf eigene (private!) Anfrage in kundenspezifischem Layout produziert wurde. Gibts sowas heute noch, und bezahlbar? Ich würde liebend gerne einen halben Meter Abstand zwischen Shift|CAPS Lock und A einbauen. Besser noch, Shift Lock ganz weglassen. Die Kappe rauszureissen ist leider nur eine Teillösung, weil bei Notebooks nicht anwendbar.
nur sind typsichere C++ Registerdefinitionen nicht Standard bei den
Hersteller SDK, für einen STM32H7 möchte ich das nicht selber schreiben.
Das sehe ich auch so.
Wenn der Toolchain/SDK-Hersteller eine C++-Template-Bibliothek mit allen
Schikanen anbietet, nehme ich die natürlich gerne. Gibt es für jedes
Register ein C-Struct mit Bitfeldern, ist das ebenso in Ordnung, auch
wenn es vielleicht nicht ganz so idiotensicher ist. Beschränken sich die
Header-Files – wie in der AVR-Libc – auf Makros für die Registeradressen
und Bitnummern, kann ich auch damit sehr gut leben.
Wichtig ist lediglich, dass diese Header-Files von anderen bereits
ausgiebig getestet und gedebugt sind, alles andere ist nice-to-have.
Wenn mein Programm nicht läuft, möchte ich die Fehler schließlich nur in
meinem eigenen Code und nicht in den kilometerlangen, unausgereiften
Header-Files suchen müssen.
Es mag zwar nett sein, wenn der Compiler für das Setzen des CS00-Bits in
TCCR0A eine Fehlermeldung ausgibt, es ist aber auch kein nennenswerter
Nachteil, wenn er es nicht tut. Mir ist ein solcher Fehler noch nie
passiert, was aber überhaupt nicht daran liegt, dass ich ein perfekter
Programmierer bin. Der Grund dafür liegt vielmehr darin, dass ich bei
der Initialisierung bspw. eines Timers das Datenblatt aufschlage und
darin schrittweise alle betroffenen Register mit den jeweiligen Bits
durchgehe. Da jedem Register ein eigener Abschnitt gewidmet ist, kommt
da überhaupt nichts durcheinander.
In dem sehr unwahrscheinlichen Fall, dass mir dieser Fehler dennoch
unterläuft, merke ich das recht schnell, da der Timer nicht so läuft,
wie ich es gerne hätte. Dann muss ich eben die paar Code-Zeilen, in
denen er initialisiert wird, noch einmal genau unter die Lupe nehmen. Da
ist eine Sache von maximal 10 Minuten, dann ist der Fehler behoben.
Würde ich stattdessen versuchen, das genannte Problem (das eigentlich
gar keines ist) durch das Schreiben einer C++-Template-Bibliothek zu
lösen, würde ich erst einmal viele Tage an Aufwand darin versenken, und
die Wahrscheinlichkeit, dabei einen Fehler zu machen, wäre wegen der
vielen Codezeilen um mehrere Größenordnungen höher. Wenn mein Programm
dann nicht läuft, muss ich den Fehler nicht nur in meinem Anwendungscode
suchen, sondern auch in den selbstgeschriebenen Header-Files, was um ein
Vielfaches aufwendiger ist.
Dieser ganze Zusatzaufwand würde sich für mich nie auch nur ansatzweise
amortisieren. Er lohnt sich – wenn überhaupt – nur dann, wenn (bspw. in
einer größeren Entwicklergruppe) einer die Arbeit übernimmt und viele
andere davon profitieren.
Wenn ich dann für das nächste Projekt keinen AVR, sondern einen ARM,
einen ESP32 oder irgendeinen anderen Mikrocontroller einsetze möchte,
geht das ganze Gefrickel wieder von vorne los.
Die in der Praxis tatsächlich auftretenden Fehler und Probleme wie bspw.
Missverständnisse beim Lesen des Datenblatts oder Vergessen, ein
bestimmtes Bit in einem Register zu setzen, werden auch mit noch so
vielen C++-Templates nicht vermieden.
Deswegen lasse ich mir das Verfahren, mit dem Register mit Werten belegt
werden, einfach vom Hersteller der Toolchain vorgeben. Damit bin ich
noch nie schlecht gefahren.
Zum eigentlichen Thema:
Statt
1
testbyte=PINB&8;
würde ich (wie schon einige andere in diesem Thread)
1
testbyte=PINB&1<<FUNKTION_DES_PINS;
scheiben (FUNKTION_DES_PINS durch einen zur Anwendung passenden Namen
ersetzen) .
Magic Numbers sind fast immer schlecht. Eine Magic Number entsteht meist
durch eine Berechnung oder durch die Nummerierung einer bestimmten, mit
der Nummer verbundenen Funktion oder Eigenschaft (bspw. bei Bitnummern
in I/O-Registern, Kommando-IDs oder Errorcodes).
Die Berechnung einer Konstanten sollte man, wenn möglich, im Quellcode
zu Dokumentationszwecken stehen lassen. Der Compiler übernimmt die
Auswertung gerne. Deswegen ist in obigem Fall 1<<3 schon einmal besser
als einfach nur 8. Erst recht ist 1<<31 besser als 2147483648 (in diesem
Fall sogar weniger Tipparbeit).
Nummern, mit denen eine bestimmte Funktion oder Eigenschaft verbunden
ist, sollten einen entsprechenden Namen bekommen, üblicherweise mittels
#define, enum oder der Initialisierung einer const-Variable.
Ist es nicht eher so, dass du rein aus Interesse, die diesbezüglichen
Möglichkeiten von C++ zu erkunden, viel Zeit für eine Lösung investiert
hast, die du jetzt durch die übertriebenen Darstellung der Schwere des
Problems aufzuwerten versuchst?
Nicht, dass du mich falsch verstehst:
Ich finde deine Lösung gut¹ und bin prinzipiell für alles dankbar, was
Fehler (auch wenn es nur seltene sind) bereits zur Compilezeit
aufdecken. Nur scheint bei diesem konkreten Problem der Aufwand für
dessen Lösung den Nutzen bei Weitem zu übersteigen, sonst würde sie von
den kommerziellen Toolchain-Entwicklern standardmäßig angeboten. Auch
bei den Open-Source-Entwicklern scheint so etwas keine Priorität zu
genießen, sonst hätte sich sicher längst eine Gruppe zusammengefunden,
die das in einer AVR-Libc++ umsetzt.
──────────────
¹) auch wenn ich sie nicht im Detail kenne, weil du ja nicht viel
darüber veröffentlichst
Naja, Bitfield-structs für IO-Register gibt es ja durchaus (und die lösen das potenzielle Setzen von Bits in falschen Registern genauso), beispielsweise bei vielen Cortex-M. Beim AVR hatten wir das mal beim ATmega128RFA1 (und Nachfolgern) gemacht, aber dort hat das seitens Atmel und dann Microchip keiner aufgegriffen.
Bei Verwendung des Shiftoperators hätte man vermutlich die Möglichkeit, dem Compiler eine Fehlererkennung beizubringen. Auch bei aufmerksamen Kontrollieren seines Codes hat man die Chance, zu erkenne, dass CS00 eben nicht im TCCR0A-Register vorhanden ist. Mit
1
TCCR0A=1;
wird dasselbe bewirkt und da wäre die Chance nicht vorhanden, weder für den Programmierer (oder steht im Kommentar, dass man CS00 meinte?) noch für den Compiler.
Und es ging ja ursprünglich um das Thema Shiftoperator oder Magic Number.
Ein Anfänger versteht vielleicht, dass mit Pin 3 irgendwas gemacht
werden soll, aber nicht, dass der Mechanismus dahinter einfach ein
zugewiesener Zahlenwert ist.
Wie an vielen Stellen: das muss man eben lernen! Wer ein richtiger Anfänger ist, hat noch viele andere Baustellen. Und wer nur Anfänger im Programmieren von µCs ist, der hat in Minuten verstanden, warum man das hier übersichtlicher so schreibt. Es sind doch prinzipiell nur die beiden Schreibweisen zu verstehen:
1
PORTB|=(1<<PB0);// B0 setzen
2
// und
3
PORTB&=~(1<<PB0);// B0 löschen
Etwas gefährlicher ist dies (bin auch schon selber auf mich reingefallen):
1
WDTCR|=(1<<WDP2)|(0<<WDP1)|(1<<WDP0);
Man führt alle Bits auf um bei Überarbeitung schnell mal auf eine andere Kombination ändern zu können, sieht aber die '0' bei WDP1 nicht auf Anhieb ...
Es wäre auch vom Compiler erkennbar, dass in der Zeile (0<<WDP1) nichts bewirkt.
Namen statt magic numbers sollte man auch nicht übertreiben. Gerade will ich eine RTC von Hand stellen und die Eingabe irgendwo begrenzen; RTCs kennen ja nur Jahre von 0 bis 99.
1
utc=mktime(&temp);
2
if(utc>4070908799){// 2098-12-31 23:59:59
3
utc=4070908799;
4
}else{
5
if(utc<source_date_epoch()){
6
utc=source_date_epoch();
7
}
8
}
Würde irgendwer diese magic number per Präprozessor ausrechnen lassen? Nachdem dies die einzige Verwendung im ganzen Programm ist, lohnt sich doch nicht einmal ein einziges #define? Eher würde ich sie zur Laufzeit ausrechnen...
Wie an vielen Stellen: das muss man eben lernen! Wer ein richtiger
Anfänger ist, hat noch viele andere Baustellen.
Das sag ich ja grad. Es ist ja wirklich nicht schwierig das zu lernen. Aber es ist eben nicht wie der TE meinte eine Hilfe für Anfänger, sondern eine Vereinfachung für Fortgeschrittene. Und die Frage kam ja scheinbar aus dem Kontext der Ausbildung.
Genau so könntest du aber argumentieren, dass das Konzept von Zahlen
ansich viel zu kompliziert ist. Woher soll der Anfänger das denn bitte
alles wissen?
Deswegen fängt man in der Mathematik nicht mit Differentialgleichungen an, sondern mit dem Zählen. Bei 0 oder 1 gehts los, weiß ich nicht mehr.
Aber du hast mich wohl sowieso falsch verstanden. Ich argumentiere ja gar nicht gegen das Konstrukt, siehe oben, sondern möchte hier nur verdeutlichen, dass das auch etwas ist, das man erst lernen muss und im Sinne der Lehre an geeigneter Stelle einbauen sollte und nicht einfach sagt, das versteht sich ja von selbst.
Ein Anfänger versteht vielleicht, dass mit Pin 3 irgendwas gemacht
werden soll, aber nicht, dass der Mechanismus dahinter einfach ein
zugewiesener Zahlenwert ist.
Was soll es denn sonst sein? Ein Mettbrötchen?
Ein Rechner kennt nur Zahlen. Deshalb heißt er so. Das sollte doch
Allgemeinwissen sein.
Holla. PortA ist schonmal nicht einfach eine Zahl im Compilersinn. Sondern eine Adresse, die dann dank gemapptem Speicher gleich noch ein Hardwareregister ist. Der Compiler dereferenziert automatisch, das Beschreiben hat dann auch noch eine Auswirkung auf die Umwelt. Je nach Anwendung sogar auf ein Mettbrötchen. Für die Profis sind die Implikationen alle klar. Um sofort zu sehen, was ein Hardwareregister, was eine Variable, was eine Funktion, was eine Konstante ist, muss das Hirn erst auf Programmiersyntax eingestellt werden. Das geht nicht in Minuten und nicht in Tagen.
Holla. PortA ist schonmal nicht einfach eine Zahl im Compilersinn.
Sondern eine Adresse, die dann dank gemapptem Speicher gleich noch ein
Hardwareregister ist.
1<<PA3 sollte man also deiner Meinung nach 8 schreiben, weil das weniger abstrakt ist.
Wie müsste man dann PORTA genau schreiben?
Adresse hartcodieren?
So?
*((volatile uint8_t *)0x1234)
Schließlich muss das ja verständlicher sein, deiner Argumentation nach.
Würde dafür eine constexpr-Funktion nehmen: das kann der Compiler ganz
gut zur Compilezeit selbst ausrechnen.
Das geht auch mit jeder beliebigen "static" Funktion. Der Compiler sieht, dass sie nur einmal benutzt wird und inlinet sie, auch ganz ohne "constexpr". Aber es spricht absolut nichts dagegen, dass per Präprozessor-Makro zu machen (außer das C++ den Präprozessor nicht mag ;-).
Würde dafür eine constexpr-Funktion nehmen: das kann der Compiler ganz
gut zur Compilezeit selbst ausrechnen.
Diese Funktion wird man aber wohl selber schreiben müssen, oder geht das
auch irgendwie mit Funktionen aus der Standardbibliothek?
Für den AVR, um den es hier wohl primär geht und für den es keine
C++-Standardbibliothek gibt, müsste die Funktion auf jeden Fall selber
geschrieben und natürlich wie jede anderen Funktion erst einmal
ausgiebig getestet werden.
Ich glaube, in diesem konkreten Fall würde ich die Regel, Berechnungen
möglichst durch den Compiler erledigen zu lassen, ausnahmsweise brechen.
Ich denke, die jeweiligen programmiersprachlichen Ausdrücke werden je nach persönlicher Historie so oder so benutzt und bewertet.
Ihr hier seid mehrheitlich junge Leute denke ich, die in moderner Zeit mit ausgereiften Tools, IDEs und komplexen Compilern digital sozialisiert wurden.
Ich hab das Controller-Programmieren auf dem 8048 gelernt, in Maschine, das Entwicklungssystem und der Emulator so groß wie ein Küchenschrank. Damals gabs nichts anderes ;-)
Daraus resultieren verschiedene Vorgehensweisen. Es würde mir nicht einfallen, irgend etwas mit einem Controller zu tun, ohne vorher den Absatz im Datenblatt gelesen zu haben.
(Da bin ich sehr bei yalu : "dass ich bei der Initialisierung bspw. eines Timers das Datenblatt aufschlage und darin schrittweise alle betroffenen Register mit den jeweiligen Bits durchgehe. ").
Libs sind mir ein Gruselding, ich will verstehen was da läuft.
btw. Was mir auffällt, sind einige Beiträge mit sehr unsachlichen Inhalten.
Was soll das in einem technischen Forum ? Vorschlag : Beiträge mit persönlichen Schmähungen werden nur akzeptiert, wenn sie in korrekter Syntax geschrieben sind ;-)
Ihr hier seid mehrheitlich junge Leute denke ich, die in moderner Zeit
mit ausgereiften Tools, IDEs und komplexen Compilern digital
sozialisiert wurden.
Manchmal umschleicht mich das Gefühl, dass die Tools vor 4 Jahrzehnten ausgereifter waren. Allerdings waren die Compiler nicht so komplex.
Für den AVR, um den es hier wohl primär geht und für den es keine
C++-Standardbibliothek gibt, müsste die Funktion auf jeden Fall selber
geschrieben und natürlich wie jede anderen Funktion erst einmal
ausgiebig getestet werden.
Aus praktischen Gründen macht man Datumsarithmetik auf Basis des julianischen Datums. Dann ist es auch für den AVR schnell geschrieben:
Dies ist natürlich nur so dahingerotzt. Im echten Leben gehen natürlich die 3-stelligen Parameterliste mit identischen Datentypen gar nicht. Sondern man muss hier sinnvollerweise eine Klasse Date einführen so ähnlich wie in der stdlib für std::year_month_date. So kann man nicht lassen.
Hier sei es gestattet um das Prinzip zu zeigen.
Wenn ich jetzt gehässig wäre, würde ich sagen: Du hast zwar die magische
Konstante 4070908799 erfolgreich eliminiert, dafür aber drei neue (4800,
153 und 32045) eingeführt ;-)
constexpr auto v1 = secondsSinceEpoch(2098y/12/31) - 1 ;
Du hast (warum auch immer) die Uhrzeit (23:59:59) weggelassen und
wolltest das mit dem -1 am Ende kompensieren. Dann hättest du das Datum
aber auf den 1.1. des Folgejahres setzen müssen:
aus der Eingabe von Datum und Uhrzeit berechnen lassen und mit dem
Ergebnis (4070908799) im Programm eine Konstante mit einem
aussagekräftigen Namen initialisiert, wäre das nicht passiert.
constexpr auto v1 = secondsSinceEpoch(2098y/12/31) - 1 ;
Du hast (warum auch immer) die Uhrzeit (23:59:59) weggelassen und
wolltest das mit dem -1 am Ende kompensieren. Dann hättest du das Datum
aber auf den 1.1. des Folgejahres setzen müssen:
1
>constexprautov1=secondsSinceEpoch(2099y/1/1)-1;
2
>
Wunderbar!
Du hast also sofort erfasst, was diese Zeile sollte. Und den offensichtlichen Fehler direkt erkannt. Es ist ja kein Fehler der Funktion, sondern seiner Anwendung.
Die RTC kann auch sicher bis Jahr 99, Monat 12, Tag 31 zählen, oder?
Dann wäre ja sogar Deine Korrektur falsch gewesen.
Ein Fehler in dem Literal 4070908799 wäre wohl länger verborgen geblieben.