Na wer hätte gewusst, dass die erste Variante nicht wie erwartet
funktioniert (auf alles wo int >= 32bit)?
Zum Glück hatte ich einen Unit Test für den Überlauf, in dem das
aufgefallen ist.
1
uint16_tmicros();
2
3
voiddelayMicrosWrong(uint16_tusecs)
4
{
5
uint16_tstart=micros();
6
while(micros()-start<usecs){}
7
}
8
9
voiddelayMicrosCorrect(uint16_tusecs)
10
{
11
uint16_tstart=micros();
12
while(uint16_t(micros()-start)<usecs){}
13
}
Der Grund ist, dass micros() - start auf int Basis gerechnet wird. In
dem Vergleich wird dann usecs auf int promoted. Somit findet kein
Überlauf nach 16-bit statt.
unerwartet schrieb:> Na wer hätte gewusst, dass die erste Variante nicht wie erwartet> funktioniert (auf alles wo int >= 32bit)?
Jeder, der die absoluten Grundlagen der Sprache gelernt hat?
unerwartet schrieb:> Der Grund ist, dass micros() - start auf int Basis gerechnet wird.
Das ist eine ziemlich unbewiesene Vermutung...
Welchen Compiler benutzt du denn?
Oliver
unerwartet schrieb:> Na wer hätte gewusst, dass die erste Variante nicht wie erwartet> funktioniert (auf alles wo int >= 32bit)?
Sie funktioniert doch wie erwartet, nur vielleicht nicht so, wie du
ursprünglich erwartet hast ;-)
Oliver S. schrieb:> Das ist eine ziemlich unbewiesene Vermutung...
Der TO hat's sogar im Betreff schon verraten, dass das alles andere als
eine unbewiesene Behauptung ist.
Stichwort: Integer Promotion
Damit wird
1
micros()-start
zu einem Integer, welcher unter Umständen von der Größe her von der
eines uint16_t verschieden ist.
> Welchen Compiler benutzt du denn?
Offenbar benutzt er einen Compiler, der Code für eine 32-Bit-Plattform
generiert. Auf einem AVR tritt das Problem natürlich nicht auf, denn da
ist ein Integer ein 16-Bit-Wert. Auf einem STM32 kommt es aber zu dem
Problem, wenn man den Ausdruck
1
micros()-start
nicht in einen uint16_t castet - und zwar bei einem Überlauf, welcher
zwischen dem ersten und dem zweiten Aufruf von micros() auftritt.
Oliver S. schrieb:> unerwartet schrieb:>> Der Grund ist, dass micros() - start auf int Basis gerechnet wird.>> Das ist eine ziemlich unbewiesene Vermutung...
Den Beweis kannst du im C-Standard nachlesen. Nennt sich "integer
promotion".
Dass alles, was kleiner als int ist, vor jeder arithmetisch/logischen
Operation erst mal mindestens nach int konvertiert wird, ist eine recht
grundlegende Eigenschaft der Sprache. Und ja, die kann tatsächlich zu
recht fiesen Fallstricken führen.
Wenn man sich auf ein ganz bestimmtes Überlaufverhalten verlässt, muss
man an der Stelle besonders genau hinschauen.
Rolf M. schrieb:> Dass alles, was kleiner als int ist, vor jeder arithmetisch/logischen> Operation erst mal mindestens nach int konvertiert wird, ist eine recht> grundlegende Eigenschaft der Sprache.
Theoretisch wird nach int oder unsigned int promoted, je nach
Wertebereich der beteiligten Typen.
Rolf M. schrieb:> Den Beweis kannst du im C-Standard nachlesen. Nennt sich "integer> promotion".
Es hätte schon gereicht, wenn ich den Ausgangsbeitrag richtig gelesen
hätte…
Oliver
unerwartet schrieb:> Na wer hätte gewusst, dass die erste Variante nicht wie erwartet> funktioniert (auf alles wo int >= 32bit)?
Da ist etwas, das mich bei allen diesen C-Klimmzügen verwundert: immer
wieder findet man Stellen, wo die Autoren mit Typecasts nur so um sich
werfen.
Warum dieses, wo doch angeblich alles heutzutage mit der stdint.h
erledigt sein sollte?
Nein, die Leute ergehen sich lieber in hochdenglischen Bezeichnern als
daß sie ihre Projekte gründlich durchdenken und dann die sinnvollsten
Integer-Variationen für ihre Berechnungen hernehmen anstatt anschließend
herumzucasten.
W.S.
W.S. schrieb:> Nein, die Leute ergehen sich lieber in hochdenglischen Bezeichnern als> daß sie ihre Projekte gründlich durchdenken und dann die sinnvollsten> Integer-Variationen für ihre Berechnungen hernehmen anstatt anschließend> herumzucasten.
Was genau möchtest du uns sagen?
a) Alle Typen ausser int sind sinnlos
b) Ich habe das Problem nicht verstanden
c) Ich 'abe gar kein Auto
Oliver
Oliver S. schrieb:> Was genau möchtest du uns sagen?
d) daß ihr viel zu viel herum-casted. Damit handelt man sich viel zu
leicht einen Batzen ein, den man ohne die Casterei nicht hätte. In
vielen Fällen sagt euch sogar der Compiler, was er an Datentyp erwarten
würde. Ihr unverständigen Geradeaus-Programmierer solltet daher öfter
auf euren Compiler hören.
Seid ihr so harthörig und von euch selbst eingenommen, daß man mit euch
nur mittels sprachlichem Knüppel reden kann?
W.S.
W.S. schrieb:> d) daß ihr viel zu viel herum-casted. Damit handelt man sich viel zu> leicht einen Batzen ein, den man ohne die Casterei nicht hätte.
Hm. Kannst du das mal am Code des TO etwas näher erläutern?
Oliver
W.S. schrieb:> d) daß ihr viel zu viel herum-casted.
Du zeigst nur, daß Du keine Ahnung von C hast und daher nicht weißt, was
"integer promotion" bedeutet. In einem Vorstellungsgespräch wärest Du
mal wieder durchgefallen. Wie eigentlich immer, wenn Du irgendwas zu C
sagst.
Ich verstehe W.S. Mir geht das viel zu häufige casten auch auf den Nerv.
Aber: Manchmal braucht man es und in diesem Fall wüsste ich auch nicht,
wie es anders geht. `micros() - start` ist nunmal int ( == int32_t auf
32 Bit compilern). Möchte man Überlaufchakteristiken von uint16_t
ausnutzen, muss das Ergebnis der Subtraktion dahin gecastet werden. Ist
sogar "doppelt" nötig, einmal wegen Vorzeichen (signed -> unsigned) und
einmal wegen der Bitbreite. Ein cast reicht dafür natürlich.
Imo relativ eindeutig dieser Fall.
Jan K. schrieb:> Möchte man Überlaufchakteristiken von uint16_t> ausnutzen, muss das Ergebnis der Subtraktion dahin gecastet werden.
Es zwingt dich niemand die Aufgabe so zu lösen. Du möchtest es so lösen,
obwohl du weißt, dass ein cast nötig ist.
Wenn man den cast für einen "Vergleich mit Überlauf" nutzen möchte, wäre
es sinnvoll, ihn in eine Funktion auszulagern, die genau das macht. Dann
ist der cast sicher verpackt und kann ordentlich getestet und
dokumentiert werden.
mh schrieb:> Es zwingt dich niemand die Aufgabe so zu lösen. Du möchtest es so lösen,> obwohl du weißt, dass ein cast nötig ist.
Das stimmt natürlich. Bietet sich aber in so Timergeschichten IMO an,
weil der unsigned Überlauf sicherstellt, dass die zu wartende Zeit immer
noch stimmt (auch wenn keine Delay Funktionen verwendet werden, auch in
simplen timer basierten schedulern ist das nicht unüblich).
mh schrieb:> Wenn man den cast für einen "Vergleich mit Überlauf" nutzen möchte, wäre> es sinnvoll, ihn in eine Funktion auszulagern, die genau das macht. Dann> ist der cast sicher verpackt und kann ordentlich getestet und> dokumentiert werden.
Full ack.
Hab den Code wie er oben ist (+die fehlenden Klammern um den unsigned
cast) mal durch Polyspace laufen lassen. Der erkennt leider bei beiden
Funktionen keine Probleme. Auch keine MISRA violation. Bisschen
ernüchternd...
mh schrieb:> Wenn man...
Du hast ja Recht.
Ich sehe hier aber oftmals, daß die grandiosen C Programmier zwar an
fast jeder Stelle irgendwelche Delays benötigen, es jedoch
offensichtlich nicht schaffen, so etwas einmal und richtig zu tun,
sondern jedesmal in jede Anwendung solche Funktionen einbauen, wie im
Eröffnungsthread gezeigt. Und jedesmal wird nach Kräften gecastet, weil
es entweder nicht paßt oder jemand etwas besonders Schlaues tun will.
Und dabei halten sich diese Programmierer für die großen Experten. Nein,
das sind diese Leute nicht und wenn sie nicht irgendwann einmal
beginnen, über den Rand ihrer Furche zu schauen, dann werden sie auch
nicht besser sondern nur eingebildeter.
W.S.
Jan K. schrieb:> Aber: Manchmal braucht man es und in diesem Fall wüsste ich auch nicht,> wie es anders geht.
Grundübel ist hier die unreflektierte Nutzung von uint16_t.
Warum ist millis() et al. nicht einfach als unsigned deklariert?
Dann wär' nix passiert (und schneller wär's wahrscheinlich auch).
Markus F. schrieb:> Jan K. schrieb:>> Aber: Manchmal braucht man es und in diesem Fall wüsste ich auch nicht,>> wie es anders geht.>> Grundübel ist hier die unreflektierte Nutzung von uint16_t.>> Warum ist millis() et al. nicht einfach als unsigned deklariert?> Dann wär' nix passiert (und schneller wär's wahrscheinlich auch).
Wie kommst du auf die Idee, dass die Nutzung unreflektiert ist? Warum
glaubst du, dass die Deklaration als unsigned das Problem löst? Zur
Erinnerung, du hast keine Ahnung was in millis passiert.
mh schrieb:> Wie kommst du auf die Idee, dass die Nutzung unreflektiert ist? Warum> glaubst du, dass die Deklaration als unsigned das Problem löst? Zur> Erinnerung, du hast keine Ahnung was in millis passiert.
Du wirst lachen, ich muss gar nicht wissen, was in millis passiert.
Markus F. schrieb:> mh schrieb:>> Wie kommst du auf die Idee, dass die Nutzung unreflektiert ist? Warum>> glaubst du, dass die Deklaration als unsigned das Problem löst? Zur>> Erinnerung, du hast keine Ahnung was in millis passiert.>> Du wirst lachen, ich muss gar nicht wissen, was in millis passiert.
Aber du glaubst die Deklaration ändern, löst alle Probleme. Was sagt
dein Glauben zu usec? Kann das uint16_t bleiben?
unerwartet schrieb:> void delayMicrosCorrect(uint16_t usecs)> {> uint16_t start = micros();> while (uint16_t(micros() - start) < usecs) {}> }
Eine weitere Fehlerquelle ergibt sich aber auch bei hohen Werten von
usecs, z.B. 65535. Die Funktion micros kann ja durch Interrupts
unterbrochen werden und dann zwischen 2 Aufrufen um mehr als einen
Schritt weiterzählen.
Man müßte also den Worst-Case aller Interupts berechnen und beim
maximalen Wert der Zuweisung berücksichtigen.
Peter D. schrieb:> unerwartet schrieb:>> void delayMicrosCorrect(uint16_t usecs)>> {>> uint16_t start = micros();>> while (uint16_t(micros() - start) < usecs) {}>> }>> Eine weitere Fehlerquelle ergibt sich aber auch bei hohen Werten von> usecs, z.B. 65535. Die Funktion micros kann ja durch Interrupts> unterbrochen werden und dann zwischen 2 Aufrufen um mehr als einen> Schritt weiterzählen.> Man müßte also den Worst-Case aller Interupts berechnen und beim> maximalen Wert der Zuweisung berücksichtigen.
Es ist vermutlich ziemlich einfach eine Kombination aus
Interruptfrequenz und Interruptlänge zu finden, die zu einer
Endlosschleife führt. Das lässt sich ohne absolute Zeit kaum verhindern.
Ich habe solche Probleme generell nicht, da ich mir keine Delayschleifen
selber code. Ich nehme entweder das Delay-Macro des AVR-GCC oder einen
Scheduler. Der Scheduler hat den Vorteil, daß man die Wartezeit
anderweitig nutzen kann.
Ich würde daher sagen, solche Delayloops sind schlechter
Programmierstil.
Peter D. schrieb:> Der Scheduler hat den Vorteil, daß man die Wartezeit> anderweitig nutzen kann.> Ich würde daher sagen, solche Delayloops sind schlechter> Programmierstil.
Auch für z.B. I2C oder 1-Wire in Software? Wenige µs wird der Scheduler
nicht liefern und ganze ms möchte/kann man ja doch nicht warten?
An einer Stelle, wo es um µs geht, finde ich micros() übertrieben
abstrakt. Für den STM32 würde man es vielleicht so schreiben:
1
voiddelayMicrosWrong(uint8_tus)
2
{
3
uint32_tstart=TIM6->CNT;
4
while(((TIM6->CNT-start)&TIM_CNT_CNT_Msk)<us){};
5
return;
6
}
start ist uint32_t, genau wie TIM->CNT in stm32l412xx.h definiert ist.
Da ist auch diese praktische Maske definiert. Das wäre ja genau, was wir
hier brauchen? Leider ist die auch 32 Bit breit. Die Hardware von Timer6
ist aber nur 16 Bit breit. Danke, STmicro.
Peter D. schrieb:> Eine weitere Fehlerquelle ergibt sich aber auch bei hohen Werten von> usecs, z.B. 65535.
Deswegen wird us als uint8_t definiert ;) Dann gibt es ab 0.256ms Mecker
vom Compiler, längere Warteschleifen sind doch unmoralisch, da muss ich
Peter Recht geben.
Peter D. schrieb:> Ich habe solche Probleme generell nicht, da ich mir keine Delayschleifen> selber code.
Es geht hier imo nicht um delay schleifen. Wie oben geschrieben tritt
sehr ähnlicher Code auch in schedulern auf, denn auch darin muss geprüft
werden, ob es Zeit ist, etwas zu tun...
Jojo schrieb:> Wie oben geschrieben tritt> sehr ähnlicher Code auch in schedulern auf, denn auch darin muss geprüft> werden, ob es Zeit ist, etwas zu tun...
Ich hab meinen Scheduler überprüft, der ist sicher.
Es wird die Zeit bis zur nächsten Aktion runter gezählt und mit 0
verglichen. Daher erfolgt keine Auswertung des Vorzeichens.
Und beim Einsortieren in die Liste werden der aktuelle Zähler und der
neue Eintrag verglichen. Das ist auch sicher bei Promotion der beiden
uint16 nach int32.
Jojo schrieb:> Es geht hier imo nicht um delay schleifen. Wie oben geschrieben tritt> sehr ähnlicher Code auch in schedulern auf, denn auch darin muss geprüft> werden, ob es Zeit ist, etwas zu tun...
Aber doch nicht im Mikrosekunden Bereich!
So wie ich das sehe, gibt es zum Warten um einige Mikrosekunden nur 2
praktisch gangbare Wege:
1. ne reine Software-Trampelschleife, wo man irgend eine Variable
herunterzählt bis sie 0 ist. Vorteil: sehr simpel, Nachteil: ungenau und
speziell beim GCC offenbar verhaßt und automatisch eliminiert, wenn man
nix dagegen tut - jedenfalls nach diversen Beiträgen von GCC-Benutzern.
2. ein dediziert dazu benutzter Timer. Vorteil: recht genau, Nachteil:
ein Timer im System ist dafür verwendet und somit für andere Zwecke
blockiert.
Wenn es noch genauer und zugleich zuverlässiger werden soll, dann sehe
ich für so etwas nur eine Hardwarelösung, denn die wird nicht durch
Zeitprobleme in der Firmware (wie z.B. dazwischenkommende Interrupts)
gestört.
Das Verwenden eines durchlaufenden Uhrzählers für sowas ist keine gute
Lösung, denn der Rechenaufwand für das richtige Setzen und Erkennen des
Endezeitpunktes ist vergleichsweise hoch und wird erst bei höheren
Systemtakten zeitlich nebensächlich.
Naja, und für das Herumwarten im Millisekunden-Bereich sieht das Ganze
wieder völlig anders aus. Da habe ich aus prinzipiellen Gründen etwas
dagegen. Wer will schon etwa 10 Sekunden oder gar ne Minute auf den
Mikrocontroller warten? Für welche Anwendung?
Und um auf Multitasking-Systeme nochmal zu sprechen zu kommen: Dort wird
nicht auf das Verstreichen einer Zeitspanne gewartet, sondern zu mehr
oder weniger fixen Zeiten der Task gewechselt. Und dazu dient eigentlich
immer eine firmwareinterne Uhr auf Interrupt-Basis. Also auch (oder
gerade) hier kein Herumtrampeln auf der Stelle.
W.S.
Kaj schrieb:> Wenn sich der TO einfach an die Datentypen halten wuerde, haette er das> Problem gar nicht.
Mein Reden.
P.S.: allerdings ist die Frage vom TO auch etwas unglücklich gestellt:
er scheint gar nicht zu realisieren, dass er C++/Arduino-Code gepostet
hat, sondern redet von C.
W.S. schrieb:> So wie ich das sehe, gibt es zum Warten um einige Mikrosekunden nur 2> praktisch gangbare Wege:> 1. ne reine Software-Trampelschleife, wo man irgend eine Variable> herunterzählt bis sie 0 ist. Vorteil: sehr simpel, Nachteil: ungenau und> speziell beim GCC offenbar verhaßt und automatisch eliminiert, wenn man> nix dagegen tut - jedenfalls nach diversen Beiträgen von GCC-Benutzern.
Die ist nicht "verhaßt", sondern fällt halt dem Optimizer zum Opfer. Was
soll er auch machen? Du sagt ihm, er soll den Code so schnell wie
möglich machen, und dann findet er eine Schleife, die 100.000 mal nichts
tut. Da denkt der sich eben: "Das geht auch schneller". Oder man kann es
auch als loop-unrolling sehen. Der Inhalt der Schleife wird einfach
100.000 mal hintereinander kopiert. Da der Inhalt leer ist, bleibt
nichts übrig.
Das Problem ist eben, dass bei einer leeren Schleife das einzige, was
eine Laufzeit hat, der Schleifen-Overhead ist.
Solche Schleifen wären aber eh nicht sinnvoll, da sie, wie du schon
sagst, ungenau sind. In C weiß man nicht, wie es der Compiler umsetzt,
und es ist auch von den Optimierungseinstellungen abhängig. Auf dem AVR
kann man sie in Assembler taktzyklengenau machen, sofern sie nicht durch
Interrupts unterbrochen werden, aber bei größeren Prozessoren ist auch
schon durch die Hardware eine gewisse Unschärfe drin (z.B. durch Cache).
> Und um auf Multitasking-Systeme nochmal zu sprechen zu kommen: Dort wird> nicht auf das Verstreichen einer Zeitspanne gewartet, sondern zu mehr> oder weniger fixen Zeiten der Task gewechselt. Und dazu dient eigentlich> immer eine firmwareinterne Uhr auf Interrupt-Basis. Also auch (oder> gerade) hier kein Herumtrampeln auf der Stelle.
Aber gerade für Wartezeien von ein paar µs, die also vermutlich deutlich
kürzer als ein Scheduler-Zyklus sind, wird einem nicht viel anderes
übrig bleiben. Außerdem hat auch der Scheduler einen Overhead, wenn er
einen Taskwechsel durchführen muss. Ist halt blöd, wenn die zwei
Taskwechsel zusammen schon mehr Zeit brauchen, als man eigentlich warten
wollte.
W.S. schrieb:> Nachteil: ungenau und speziell beim GCC offenbar verhaßt
Der wesentlichste Nachteil sind mal wieder Deine mangelhaften
C-Kenntnisse.
> Naja, und für das Herumwarten im Millisekunden-Bereich sieht das Ganze> wieder völlig anders aus.
Daß man das "while" auch durch ein "if" ersetzen könnte, wenn man eine
allgemeine main-Schleife hat, ist schon zuviel des Mitdenkens? Denn es
ging hier nicht um das "while", sondern um den Ausdruck dahinter.
Rolf M. schrieb:> Was> soll er auch machen? Du sagt ihm, er soll den Code so schnell wie> möglich machen, und dann findet er eine Schleife, die 100.000 mal nichts> tut. Da denkt der sich eben: "Das geht auch schneller". Oder man kann es> auch als loop-unrolling sehen. Der Inhalt der Schleife wird einfach> 100.000 mal hintereinander kopiert. Da der Inhalt leer ist, bleibt> nichts übrig.
Ähem.. nun ja, man sagt dem Compiler, er soll den Code, den er aufgrund
der Quelldatei erzeugen soll optimieren. Das heißt nicht, daß er auf
Biegen und Brechen den erzeugten Code "so schnell wie möglich" machen
soll. Das ist prinzipiell ein Unterschied. Hier würde es nur heißen, daß
er die Schleife optimieren soll, von wegputzen war nirgends die Rede.
Aber ich benutze den GCC nicht. Ich hatte ihn nur einmal vor Jahren bei
der Lernbetty benutzt, fand aber, daß er (damals) deutlich
umfänglicheren Maschinencode erzeugte als der Keil. Ob sich das nun in
den vergangenen Jahren gebessert hat, weiß ich nicht. Naja, ist auch
nicht so wichtig.
W.S.
W.S. schrieb:> Aber ich benutze den GCC nicht.
musst Du ja nicht.
Aber wenn Du irgendeinen C-Compiler nutzt, der sich an den Standard
hält, musst Du damit rechnen, dass der genau dasselbe macht.
W.S. schrieb:
> Nachteil: ungenau und speziell beim GCC offenbar verhaßt
Sachma?
Das wurde dir jetzt schon wie oft erklärt, dass das das richtige
verhalten ist?
Du machst dich wieder nur zur Lachnummer des Forums wenn du nach
zichfacher aufklärung durch viele verschiedene Forennutzer den selben
Quark schreibst!
W.S. schrieb:> Ähem.. nun ja, man sagt dem Compiler, er soll den Code, den er aufgrund> der Quelldatei erzeugen soll optimieren. Das heißt nicht, daß er auf> Biegen und Brechen den erzeugten Code "so schnell wie möglich" machen> soll.
Doch, genau das heißt es. Es sollen Instruktionen entfernt werden, die
zum Ergebnis nichts beitragen und nur Zeit kosten. Und das ist bei einer
leeren Schleife der Fall.
Wobei ich hier nicht von "auf Biegen und Brechen" sprechen würde. Das
passiert bei der Optimierung ganz nebenbei. Man müsste das extra als
Sonderfall berücksichtigen, wenn das hier nicht wegoptimiert werden
sollte.
> Das ist prinzipiell ein Unterschied. Hier würde es nur heißen, daß> er die Schleife optimieren soll, von wegputzen war nirgends die Rede.
Was verstehst du denn unter "die Schleife optimieren"? Die Schleife ist
leer, tut also nichts außer Zeit verbraten.
Wie gesagt: Wenn er eine Optimierung durch loop-unrolling macht und
damit den Overhead für die Schleifenwiederholung entfernt, wäre das eine
ganz normale Optimierung. Da die Schleife in diesem Fall aber
ausschließlich aus diesem Overhead besteht, bleibt halt nix mehr übrig.
Um das Wegoptimieren von "leeren Schleifen" zu vermeiden, hilft der
(hoffentlich wohlüberlegte) Einsatz des volatile qualifiers. Ob das
allerdings eine gute Idee ist, steht auf einem anderen Blatt.
Markus F. schrieb:> Um das Wegoptimieren von "leeren Schleifen" zu vermeiden, hilft der> (hoffentlich wohlüberlegte) Einsatz des volatile qualifiers.
Zur Not ein einfaches
1
asmvolatile(""::);
> Ob das allerdings eine gute Idee ist, steht auf einem anderen Blatt.
Ja. Wesentlich besser ist, die in der Regel bereits verfügbaren
Delay-Funktionen zu verwenden.
Rolf M. schrieb:> Wenn man sich auf ein ganz bestimmtes Überlaufverhalten verlässt, muss> man an der Stelle besonders genau hinschauen.
Und genau da liegt der eigentliche Fehler. Wenn man nicht immer
vollkommen sicher ist wie die Auswertung erfolgt, dann lässt man solchen
Blödsinn.
Tim T. schrieb:> Wenn man nicht immer vollkommen sicher ist wie die Auswertung erfolgt,> dann
... lernt man mal die Programmiersprache, die man benutzt. Das war
einfach.
Nop schrieb:> Tim T. schrieb:>> Wenn man nicht immer vollkommen sicher ist wie die Auswertung erfolgt,>> dann>> ... lernt man mal die Programmiersprache, die man benutzt. Das war> einfach.
Sein wir mal ehrlich, wer hat denn schon von Anfang an gewusst das es
die Integer Promotion gibt und wo die ganzen Fallstricke liegen könnten?
Viele schreiben so einige Programme ohne dass, das fehlende Wissen um
diese, zu einem Problem wird und bei entsprechend defensiver
Programmierung würde ich fast behaupten, dass diese eigentlich nie zum
Problem wird. Solche Sachen werden meiner Erfahrung nach oft erst zum
Problem wenn man irgendwelche Tricks benutzen will und sich letztlich
selber ein Bein stellt.
Wobei ich natürlich auch empfehle sich mit den Feinheiten der
Programmiersprache zu beschäftigen, aber auch da zeigt meine Erfahrung,
dass es bei Vielen reicht wenn etwas irgendwie funktioniert und die
Details kaum noch interessieren.
Tim T. schrieb:> Viele schreiben so einige Programme ohne dass fehlendes Wissen um diese
und andere notwendige Kenntnisse in C. Was nichts daran ändert, daß es
ohne nicht geht.
Programmiersprachen muß man lernen. Jede, ohne Ausnahme.
Oliver
Oliver S. schrieb:> Programmiersprachen muß man lernen. Jede, ohne Ausnahme.
C hat die ein oder andere Überraschung auf Lager. Wer hat schon mal
probiert, was bei
1
const char c = 'A';
2
printf("sizeof(c) = %d\n", sizeof(c));
3
printf("sizeof 'A' = %d\n", sizeof 'A');
4
unsigned char c2 = 0xff;
5
printf("%02x %02x\n", c2, (char) c2);
rauskommt (und warum)?
Extrapunkte, wenn man dieselbe Frage für C++ beantworten kann.
Wer alle Ausgaben ohne Ausprobieren (oder im Standard spickeln) auf
Anhieb fehlerfrei beantwortet: meine uneingeschränkte Hochachtung!
Markus F. schrieb:> Anhieb fehlerfrei beantwortet: meine uneingeschränkte Hochachtung!
1, 4, ff, ffffffff (wenn char = signed char).
>Extrapunkte, wenn man dieselbe Frage für C++ beantworten kann.
1, 1, ff, ffffffff (wenn char = signed char).
Komm ich jetzt ins Fernsehn?
Markus F. schrieb:> Oliver S. schrieb:>> Programmiersprachen muß man lernen. Jede, ohne Ausnahme.>> C hat die ein oder andere Überraschung auf Lager. Wer hat schon mal> probiert, was bei>>
1
> const char c = 'A';
2
> printf("sizeof(c) = %d\n", sizeof(c));
3
> printf("sizeof 'A' = %d\n", sizeof 'A');
4
> unsigned char c2 = 0xff;
5
> printf("%02x %02x\n", c2, (char) c2);
6
>
7
>
8
>
>> rauskommt (und warum)?> Extrapunkte, wenn man dieselbe Frage für C++ beantworten kann.
So eben aus der Hüfte:
1, weil ein char eben 1 Byte groß ist.
4, weil der Wert des Zeichen 'A' als int definiert ist und der aus 4
byte besteht.
0xff, weil es eben der Wert ist.
0xffffffff, weil unsigned char auf signed char gecastet wird und
anschließend auf unsigned int. Beim Erweitern des Datentyps wird bei
signed Variablen das Vorzeichen erhalten, also mit ff gefüllt.
Für C++ hab ich keine Ahnung, würde aber vermuten das dort Zeichen
wirklich als Char definiert sind, also bei sizeof 'A' 1 rauskommt.
Tim T. schrieb:> auf signed char gecastet wird und> anschließend auf unsigned int
warum? Integer Promotion kommt bei Arithmetik ins Spiel - hier ist keine
Arithmetik beteiligt...
MaWin schrieb:> nein.> Integer Promotion kommt immer ins Spiel, wenn etwas kleiner ist als int.
Bei einem Funktionsaufruf? Welchen Sinn macht dann ein
Funktionsparameter, der kleiner als int ist?
Hier spielt noch was anderes mit rein...
Markus F. schrieb:> Tim T. schrieb:>> auf signed char gecastet wird und>> anschließend auf unsigned int>> warum? Integer Promotion kommt bei Arithmetik ins Spiel - hier ist keine> Arithmetik beteiligt...
Falsch!
MaWin schrieb:> Markus F. schrieb:>> Integer Promotion kommt bei Arithmetik ins Spiel>> nein.> Integer Promotion kommt immer ins Spiel, wenn etwas kleiner ist als int.
Auch Falsch! (aber deutlich weniger)
Markus F. schrieb:> Bei einem Funktionsaufruf? Welchen Sinn macht dann ein> Funktionsparameter, der kleiner als int ist?
Die Frage ergibt für mich keinen Sinn.
Hast du ein Beispiel, damit ich verstehen kann was du meinst?
mh schrieb:> Auch Falsch! (aber deutlich weniger)
Ja richtig. Wer es genau wissen will, kann es einfach im Standard
nachlesen.
Den tippe ich hier sicher nicht ab.
Markus F. schrieb:> MaWin schrieb:>> nein.>> Integer Promotion kommt immer ins Spiel, wenn etwas kleiner ist als int.>> Bei einem Funktionsaufruf? Welchen Sinn macht dann ein> Funktionsparameter, der kleiner als int ist?> Hier spielt noch was anderes mit rein...
Dann mach dich mal schlau was und erklär es uns.
W.S. schrieb:> Das heißt nicht, daß er auf> Biegen und Brechen den erzeugten Code "so schnell wie möglich" machen> soll.
Du solltest Dich erstmal mit den Grundlagen der Sprache befassen, bevor
Du solchen Blödsinn schreibst - konkret in diesem Fall der "as
if"-Regel. Für C-Anfänger wie Dich ist es essentiell, sowas zu lernen.
Markus F. schrieb:> C hat die ein oder andere Überraschung auf Lager. Wer hat schon mal> probiert, was bei> const char c = 'A';> printf("sizeof(c) = %d\n", sizeof(c));> printf("sizeof 'A' = %d\n", sizeof 'A');> unsigned char c2 = 0xff;> printf("%02x %02x\n", c2, (char) c2);>> rauskommt (und warum)?
Der Code hat undefiniertes Verhalten, da du den falschen
Format-Specifier angegeben hast. sizeof gibt ein size_t zurück, und
dafür wäre %zu korrekt.
Markus F. schrieb:> MaWin schrieb:>> nein.>> Integer Promotion kommt immer ins Spiel, wenn etwas kleiner ist als int.>> Bei einem Funktionsaufruf? Welchen Sinn macht dann ein> Funktionsparameter, der kleiner als int ist?> Hier spielt noch was anderes mit rein...
Ja, die variadische Parameterliste. Da gibt's nix kleineres als int.
Markus F. schrieb:> Rolf M. schrieb:>> Ja, die variadische Parameterliste. Da gibt's nix kleineres als int.>> so sieht's aus ;)
Jepp so sieht es aus. Dummerweise wird an der Stelle laut Standard
integer promotion angewendet.
mh schrieb:> Markus F. schrieb:>>> Rolf M. schrieb:>>> Ja, die variadische Parameterliste. Da gibt's nix kleineres als int.>>>> so sieht's aus ;)>> Jepp so sieht es aus. Dummerweise wird an der Stelle laut Standard> integer promotion angewendet.
Warum gilt das für das erste C2 nicht? Ist printf erst variadisch ab dem
2. Parameter (denke nicht, weil alle optional sind oder)? Oder tritt das
nur beim casten auf? Wenn ja warum das?
Jojo schrieb:> mh schrieb:>> Markus F. schrieb:>>>>> Rolf M. schrieb:>>>> Ja, die variadische Parameterliste. Da gibt's nix kleineres als int.>>>>>> so sieht's aus ;)>>>> Jepp so sieht es aus. Dummerweise wird an der Stelle laut Standard>> integer promotion angewendet.>> Warum gilt das für das erste C2 nicht? Ist printf erst variadisch ab dem> 2. Parameter (denke nicht, weil alle optional sind oder)? Oder tritt das> nur beim casten auf? Wenn ja warum das?
Das gilt genauso für das erste c2. Bloss: das ist ein unsigned char
(0xff = 255). Das wird nach unsigned promoted und ist immer noch 255.
Das 2. c2 ist ein char (per default signed auf den meisten Plattformen).
0xff = -1. Das wird nach signed promoted und ist immer noch -1.
Markus F. schrieb:> Das gilt genauso für das erste c2. Bloss: das ist ein unsigned char> (0xff = 255). Das wird nach unsigned promoted und ist immer noch 255.
Wieder falsch! Es wird nach integer promoted.
Hier noch ein zweiter Versuch, der auch mit Wartezeiten länger als 65ms
umgehen kann (sofern int > 16bit). Darf halt nicht länger als 65ms
unterbrochen werden.
1
// definiert irgendwo, liest wahrscheinlich timer wert aus,
2
// könnte aber auch als HAL_GetTick() * 1000 implementiert sein
A. S. schrieb:> Mag sein, dass es mit int schneller ist. Aber mit dem nativen> typen von> micros ist es m.E. einfacher, weil es keine casts etc. braucht.> void delayMicros(unsigned int usecs)> {> [...]> }>> Zudem kann waited nicht wie bei Dir überlaufen.
Es tauchen keine explizite casts auf. Die zugehörigen conversions finden
aber weiterhin statt, nur sind sie versteckt, da implizit. Und wenn man
feststellt dass eine conversion stattfindet, muss man die ganze Funktion
analysieren, ob jede conversion sicher ist.
mh schrieb:> Die zugehörigen conversions finden aber weiterhin statt, nur sind sie> versteckt, da implizit. Und wenn man feststellt dass eine conversion> stattfindet, muss man die ganze Funktion analysieren, ob jede conversion> sicher ist.
Du meinst diese Zeile?
A. S. schrieb:> usecs -= delta
Und was prüfst Du da dann?
A. S. schrieb:> mh schrieb:>> Die zugehörigen conversions finden aber weiterhin statt, nur sind sie>> versteckt, da implizit. Und wenn man feststellt dass eine conversion>> stattfindet, muss man die ganze Funktion analysieren, ob jede conversion>> sicher ist.>> Du meinst diese Zeile?>> A. S. schrieb:>> usecs -= delta>> Und was prüfst Du da dann?
Ob delta > usecs sein kann, was hier relativ einfach ist.
Aber in deinem
A. S. schrieb:> delta = now - last;
Findet die Subtraktion in int statt und das Ergebnis wird nach uint16_t
konvertiert. Das ist die conversion, die dem TO gefehlt hat und mit
einem expliziten cast hinzufügen musste.
mh schrieb:> Findet die Subtraktion in int statt und das Ergebnis wird nach uint16_t> konvertiert. Das ist die conversion, die dem TO gefehlt hat und mit> einem expliziten cast hinzufügen musste.
Mmh, ja. Weil der TO Vergleich und Überlauf zusammen hatte (getrennt
hatte es auch keines casts bedurft).
Warum soll das rechnen in uint16_t (bei mir, ohne cast) schlechter sein
als die (fehlerhafte) Lösung davor?
A. S. schrieb:> mh schrieb:>> Findet die Subtraktion in int statt und das Ergebnis wird nach uint16_t>> konvertiert. Das ist die conversion, die dem TO gefehlt hat und mit>> einem expliziten cast hinzufügen musste.>> Mmh, ja. Weil der TO Vergleich und Überlauf zusammen hatte.>> Warum soll das rechnen in uint16_t (bei mir, ohne cast) schlechter sein> als die (fehlerhafte) Lösung davor?
Ich halte deine Lösung für schlechter, da sie die, für die Funktion,
wichtigen Umwandlungen implizit und "heimlich" macht. Diese
Umwalndlungen sollten hervorgehoben werden als ein "hey hier passiert
was" und "ja diese Verhalten ist gewollt".
A. S. schrieb:> Warum soll das rechnen in uint16_t (bei mir, ohne cast) schlechter sein> als die (fehlerhafte) Lösung davor?
Nicht unbedingt schlechter. Bei dir hat der Compiler zumindest die
Möglichkeit eine Warnung auszugeben. ;-)
1
GCC: warning: conversion from ‘int’ to ‘uint16_t’ {aka ‘short unsigned int’} may change value [-Wconversion]
Grundsätzlich ist das "Problem" nicht die Promotion/Conversion in C/++,
sondern dass die micros() Funktion einen Wrap-Around durchführt, und
dadurch eine (normale) Subtraktion im Bereich der nat. Zahlen nicht
zulässig ist.
Eine mögliche "Verbesserung" ist micros mit einer diff-Funktion zu
paaren:
1
uint16_tdiff(uint16_tt0,uint16_tt1)
2
{
3
return(uint16_t)(t1-t0);
4
}
Dann funktioniert auch der (gewohnte) Ansatz des TO.
1
voiddelayMicrosWrong(uint16_tusecs)
2
{
3
uint16_tstart=micros();
4
while(diff(micros(),start)<usecs){}
5
}
Dadurch wird der Code imho etwas besser lesbar. Das grundsätzliche
Problem der begrenzten Domain kann so natürlich auch nicht gelöst werden
und sollte zumindest kommentiert werden.
Man kann auch soweit gehen und zur Typsicherheit einen eigenen Typen
basteln:
1
structTime16{uint16_ti;}
2
3
Time16micros16(){returnTime16{micros()};};
4
5
uint16_tdiff(Time16t0,Time16t1)
6
{
7
return(uint16_t)(t1.i-t0.i);
8
}
Dadurch wird ausgeschlossen, dass Aufrufer selbst versehentlich
Operationen auf den Timestamps anwendet.
Mikro 7. schrieb:> Bei dir hat der Compiler zumindest die Möglichkeit eine Warnung> auszugeben. ;-)
In welcher Zeile sollte das sein? Oder darf ich überhaupt keine Rechnung
mit Überlauf auf unsigned char oder short machen?
Das Problem entsteht, wenn Rechnung und Vergleich in einem Ausdruck
erfolgen.
Weist man das Ergebnis der Rechnung wieder einer Variablen gleichen Typs
zu, erfolgt der Vergleich korrekt.
Markus F. schrieb:> Kaj schrieb:>> Wenn sich der TO einfach an die Datentypen halten wuerde, haette er das>> Problem gar nicht.>> Mein Reden.>> P.S.: allerdings ist die Frage vom TO auch etwas unglücklich gestellt:> er scheint gar nicht zu realisieren, dass er C++/Arduino-Code gepostet> hat, sondern redet von C.
Ich verstehe das Grundlegende Problem nicht so ganz. Wenn ich etwas
berechne und da Zahl-Konstanten in den Code einfüge, wird das int.
Also im Prinzip ist alles, was nicht explizit als was Anderes deklariert
wurde, ein int und der ist je nach Archtitektur eben unterschiedlich
gross.
Wenn man das so im Kopf behält dürften sich da wohl kaum Probleme
ergeben und ich sehe es gar nicht erst.
ein if(a+88) wird mindestens als int gerechnet. Auf real mode DOS sind
das 16bit, x86_64 bringt da 64bit ins Spiel.
MaWin schrieb:> Keiler schrieb:>> x86_64 bringt da 64bit ins Spiel.>> falsch
Ein Satz hierzu hätte auch nicht geschadet...
Int ist auf x86_64 ebenfalls 32 Bit groß, erst bei long geht x86_64 mit
64 Bit andere Wege, zumindest auf Betriebssystemen die nicht aus Redmond
kommen (ja, gibt sicherlich wieder irgendeinen Compiler auf irgendeinem
exotischen Betriebssystem der das wieder anders sieht).
Ansonsten garantieren die Datentypen laut Standard auch nur eine
Mindestgröße, 8 Bit bei char, 16 bei short, 16(!) bei int, 32 bei long
sowie 64 bei long long.
Tim T. schrieb:> Ich verstehe das Grundlegende Problem nicht so ganz.
Stimmt.
Tim T. schrieb:> Also im Prinzip ist alles, was nicht explizit als was Anderes deklariert> wurde, ein int und der ist je nach Archtitektur eben unterschiedlich> gross.
Das Problem des TO ist nicht primär die unterschiedliche Größe
irgendwelcher Typen, sondern die durch Integer Promotion erfolgte
unsigned zu signed Umwandlung, die das Overflow-Verhalten grundlegend
ändert.
Oliver
Oliver S. schrieb:> Tim T. schrieb:>> Ich verstehe das Grundlegende Problem nicht so ganz.>> Stimmt.
Das "Problem" welches natürlich keins ist, verstehe ich sehr wohl, nur
was du jetzt wieder sagen willst nicht.
> Tim T. schrieb:>> Also im Prinzip ist alles, was nicht explizit als was Anderes deklariert>> wurde, ein int und der ist je nach Archtitektur eben unterschiedlich>> gross.>> Das Problem des TO ist nicht primär die unterschiedliche Größe> irgendwelcher Typen, sondern die durch Integer Promotion erfolgte> unsigned zu signed Umwandlung, die das Overflow-Verhalten grundlegend> ändert.
Habe ich auch nicht behauptet, meine letzte Antwort bezog sich lediglich
auf die einsilbige Antwort von MaWin auf die zugegeben falsche
Behauptung vom Keiler das auf x86_64 eine integer promotion auf 64 Bit
zielt.
Meine Aussage zum "Problem" des TO findest du in
Beitrag "Re: C arithmetic promotion Fallstricke"
Oliver S. schrieb:> sondern die durch Integer Promotion erfolgte> unsigned zu signed Umwandlung, die das Overflow-Verhalten grundlegend> ändert.
zwar wird zu signed umgewandelt. Bei unsigned ergäben sich aber gleiche
Probleme.
Das Problem ist, dass der Überlauf nur bei 16-Bit funktioniert und mit
32-Bit gerechnet wurde. Beispiele für 100 Ticks: Bei 105-5 kommt in
allen Fällen 100 heraus. Beim Überlauf, z.B. 1-65437:
* in 16-Bit unsigned: --> 100, die einzige Variante die OK ist.
* in 32-Bit unsigned: --> >4 Milliarden
* in 32-Bit signed: --> -65435
(ob da manche Zahlen jetzt 1 oder 2 mehr oder weniger sind, habe ich
jetzt nicht geprüft)
Tim T. schrieb:> Int ist auf x86_64 ebenfalls 32 Bit groß, erst bei long geht x86_64 mit> 64 Bit andere Wege, zumindest auf Betriebssystemen die nicht aus Redmond> kommen
Abgekürzt als LP64 bei Unixoiden und LLP64 bei Windows. Aber ILP64 hat
es auch schon gegeben, also 64-Bit "int":
https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
(prx) A. K. schrieb:> Tim T. schrieb:>> Int ist auf x86_64 ebenfalls 32 Bit groß, erst bei long geht x86_64 mit>> 64 Bit andere Wege, zumindest auf Betriebssystemen die nicht aus Redmond>> kommen>> Abgekürzt als LP64 bei Unixoiden und LLP64 bei Windows. Aber ILP64 hat> es auch schon gegeben, also 64-Bit "int":> https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
Und genau darum hatte ich die Aussage auf x86_64 beschränkt.
Tim T. schrieb:> Und genau darum hatte ich die Aussage auf x86_64 beschränkt.
Das hat an sich nur indirekt mit der CPU-Architektur zu tun. Eher mit
dem Compiler. Man könnte auch einen mit ILP64 für x86_64 machen,
allerdings ist ILP64 generell nicht sehr populär. Obwohl ein int mit 64
Bit der Empfehlung des C-Standards entspricht, bleibt man meist lieber
bei 32 Bit, weil man sonst das Problem hat, dass einem die Datentypen
ausgehen. Man will ja in der Regel Integer-Typen mit 8, 16, 32 und 64
Bit haben, aber es gibt nur zwei Intger-Typen, die kleiner sein dürfen
als int, nämlich char und short. Damit hätte man mit ILP64 einen zu
wenig. Es passt nur, wenn int entweder 16 oder 32 Bit groß ist.
Keiler schrieb:> Also im Prinzip ist alles, was nicht explizit als was Anderes deklariert> wurde, ein int und der ist je nach Archtitektur eben unterschiedlich> gross.>> Wenn man das so im Kopf behält dürften sich da wohl kaum Probleme> ergeben und ich sehe es gar nicht erst.
Ansich schon, man erwartet aber auch logischerweise (ohne die Promotion
Regel zu kennen), dass bei
1
(uint16-uint16)>uint16
der Vergleich auf uint16 Basis stattfindet und nicht auf int Basis.
unerwartet schrieb:> Ansich schon, man erwartet aber auch logischerweise (ohne die Promotion> Regel zu kennen), dass bei(uint16 - uint16) > uint16>> der Vergleich auf uint16 Basis stattfindet und nicht auf int Basis.
Nur weil du das erwartest, ist es nicht logisch. Warum sollte die
Subtraktion mit einem Typ durchgeführt werden, der nicht jedes Ergebnis
abbilden kann?
mh schrieb:> Warum sollte die> Subtraktion mit einem Typ durchgeführt werden, der nicht jedes Ergebnis> abbilden kann?
Weil das bei
(uint32 - uint32) > uint32
auch so ist.
MaWin schrieb:> mh schrieb:>> Warum sollte die>> Subtraktion mit einem Typ durchgeführt werden, der nicht jedes Ergebnis>> abbilden kann?>> Weil das bei> (uint32 - uint32) > uint32> auch so ist.
Das macht seine Aussage nicht logischer.
Was ist bei
(uint16 - uint16) > int32
logisch?
Die Frage, was man als richtig empfindet, war anfangs unentschieden. Als
C vom ANSI standardisiert wurde, gab es unter den Compilern beide
Varianten, denn das ging aus K&R nicht hervor. Bei "sign preserving"
blieb unsigned erhalten, gewonnen hat aber "value preserving".
(prx) A. K. schrieb:> Als C vom ANSI standardisiert wurde, gab es unter den Compilers beide> Varianten, denn das ging aus K&R nicht hervor. Bei "sign preserving"> blieb unsigned erhalten, gewonnen hat aber "value preserving".
Und als nächstes behauptest du, dass sie Gründe für ihre Entscheidung
hatten. Das kann nicht sein! ;-)
mh schrieb:> Und als nächstes behauptest du, dass sie Gründe für ihre Entscheidung> hatten. Das kann nicht sein! ;-)
Ich hielt mich raus und implementierte beides, wählbar. ;-)
"The unsigned preserving rules greatly increase the number of situations
where unsigned int confronts signed int to yield a questionably signed
result, whereas the value preserving rules minimize such confrontations.
Thus, the value preserving rules were considered to be safer for the
novice, or unwary, programmer. After much discussion, the Committee
decided in favor of value preserving rules, despite the fact that the
UNIX C compilers had evolved in the direction of unsigned preserving."
https://newbedev.com/why-do-unsigned-small-integers-promote-to-signed-int
(prx) A. K. schrieb:> Ich hielt mich raus und implementierte beides, wählbar. ;-)
Nicht die schlechteste Idee (zumindest mittelfristig) ;-)
Gab es damit die Möglichkeit, sich alle Stellen, an denen beide
Varianten unterschiedliche Ergebnisse liefern, anzeigen zu lassen?
mh schrieb:> Gab es damit die Möglichkeit, sich alle Stellen, an denen beide> Varianten unterschiedliche Ergebnisse liefern, anzeigen zu lassen?
Nein. Diese Arbeit hatte ich mir nicht gemacht. Nur entstand der
Compiler in den 80ern in jener Phase, in der ANSI-C am Horizont
erschien, aber noch nicht verabschiedet war.
Der heutige GCC ermittelt in Ausdrücken, welchen möglichen Wertebereich
Zwischenergebnisse haben können. Anhand dessen wird optimiert und
gewarnt. Von diesem Stadium waren damalige C Compiler weit entfernt.