Forum: Compiler & IDEs C arithmetic promotion Fallstricke


von unerwartet (Gast)


Lesenswert?

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_t micros();
2
3
void delayMicrosWrong(uint16_t usecs)
4
{
5
   uint16_t start = micros();
6
   while (micros() - start < usecs) {}
7
}
8
9
void delayMicrosCorrect(uint16_t usecs)
10
{
11
   uint16_t start = 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.

von mh (Gast)


Lesenswert?

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?

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

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 ;-)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von MaWin (Gast)


Lesenswert?

unerwartet schrieb:
> Na wer hätte gewusst

Ich

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Markus F. (mfro)


Lesenswert?

unerwartet schrieb:
> Na wer hätte gewusst, dass die erste Variante nicht wie erwartet
> funktioniert (auf alles wo int >= 32bit)?

Jeder der die "Deep C Secrets" (muss man nicht kaufen: 
https://github.com/kgashok/CodeOrDie/blob/master/CoDE/docs/Expert%20C%20Programming%20-%20Deep%20C%20Secrets.pdf 
) gelesen und verinnerlicht hat.

Dort gibt's noch mehr hübsche Fallstricke zu lesen. Alt, aber immer noch 
aktuell.

von W.S. (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

b)

von W.S. (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Nop (Gast)


Lesenswert?

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.

von Jan K. (jan_k776)


Lesenswert?

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.

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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.

von Jan K. (jan_k776)


Lesenswert?

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

von W.S. (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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).

von mh (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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?

von Peter D. (peda)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

Markus F. schrieb:
> einfach als unsigned deklariert?

Z.b. weil millis einen 16 bit-Timer ausliest.

von Bauform B. (bauformb)


Lesenswert?

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
void delayMicrosWrong (uint8_t us)
2
{
3
   uint32_t  start = 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.

: Bearbeitet durch User
von Jojo (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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.

von Kaj (Gast)


Lesenswert?

Markus F. schrieb:
> Warum ist millis() et al. nicht einfach als unsigned deklariert?
Sie sind als unsigned deklariert, einfach mal in die Doku gucken.
1
Returns the number of microseconds since the Arduino board began running the current program.
2
Data type: unsigned long
https://www.arduino.cc/reference/en/language/functions/time/micros/
1
Number of milliseconds passed since the program started. 
2
Data type: unsigned long.
https://www.arduino.cc/reference/en/language/functions/time/millis/

Wenn sich der TO einfach an die Datentypen halten wuerde, haette er das 
Problem gar nicht.

von W.S. (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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!

von Rolf M. (rmagnus)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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
    asm volatile (""::);

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

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

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

von Markus F. (mfro)


Lesenswert?

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!

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

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?

von Markus F. (mfro)


Lesenswert?

MaWin schrieb:
> Komm ich jetzt ins Fernsehn?

Nee. Die Frage nach dem "warum" wurde noch nicht beantwortet.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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

von MaWin (Gast)


Lesenswert?

Markus F. schrieb:
> Integer Promotion kommt bei Arithmetik ins Spiel

nein.
Integer Promotion kommt immer ins Spiel, wenn etwas kleiner ist als int.

von MaWin (Gast)


Lesenswert?

Markus F. schrieb:
> Die Frage nach dem "warum" wurde noch nicht beantwortet.

Weil das halt so definiert ist.

von Markus F. (mfro)


Lesenswert?

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

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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)

von MaWin (Gast)


Lesenswert?

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?

von MaWin O. (mawin_original)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

Rolf M. schrieb:
> Ja, die variadische Parameterliste. Da gibt's nix kleineres als int.

so sieht's aus ;)

von mh (Gast)


Lesenswert?

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.

von Jojo (Gast)


Lesenswert?

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?

von Markus F. (mfro)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

mh schrieb:

>... Es wird nach integer promoted.

Stimmt. Mein Fehler.

von unerwartet (Gast)


Lesenswert?

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
3
uint16_t micros();
4
5
void delayMicros(unsigned int usecs)
6
{
7
   unsigned int now = micros();
8
   unsigned int usecsWaited = 0;
9
   unsigned int lastTimestamp = now;
10
11
   while (usecsWaited < usecs) {
12
      unsigned int delta = (now - lastTimestamp) & uint16_t(-1);
13
      usecsWaited += delta;
14
      // todo if usecsWaited overflows, set to max value
15
      lastTimestamp = now;
16
      now = micros();
17
   }
18
}

von A. S. (Gast)


Lesenswert?

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.
1
void delayMicros(unsigned int usecs)
2
{
3
uint16_t now;
4
uint16_t last = micros();
5
uint16_t delta = 0;
6
7
   while(usecs > delta) {
8
      usecs -= delta;
9
      now = micros();
10
      delta = now - last;
11
      last = now;
12
   }
13
}

Zudem kann waited nicht wie bei Dir überlaufen.

von mh (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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?

von mh (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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?

von mh (Gast)


Lesenswert?

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".

von Mikro 7. (mikro77)


Lesenswert?

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_t diff(uint16_t t0,uint16_t t1)
2
{
3
  return (uint16_t) (t1 - t0) ;
4
}

Dann funktioniert auch der (gewohnte) Ansatz des TO.
1
void delayMicrosWrong(uint16_t usecs)
2
{
3
   uint16_t start = 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
struct Time16 { uint16_t i ; }
2
3
Time16 micros16() { return Time16 { micros() } ; } ;
4
5
uint16_t diff(Time16 t0,Time16 t1)
6
{
7
  return (uint16_t) (t1.i - t0.i) ;
8
}

Dadurch wird ausgeschlossen, dass Aufrufer selbst versehentlich 
Operationen auf den Timestamps anwendet.

von A. S. (Gast)


Lesenswert?

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?

von Peter D. (peda)


Lesenswert?

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.

von Keiler (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

Keiler schrieb:
> x86_64 bringt da 64bit ins Spiel.

falsch

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

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"

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

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)

von (prx) A. K. (prx)


Lesenswert?

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

: Bearbeitet durch User
von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

(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.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von unerwartet (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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?

von MaWin (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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".

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

(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! ;-)

von (prx) A. K. (prx)


Lesenswert?

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

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

(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?

von (prx) A. K. (prx)


Lesenswert?

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.

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