Forum: Compiler & IDEs for Loop in C / die 10000ste dumme Frage


von Sprudelstrudel (Gast)


Lesenswert?

Hallo, ich habe mal eine Frage zur for Loop in C:

Macht es einen Unterschied ob ich

for(uint8_t i = 0, i < 80, ++i)

oder


for(uint8_t i = 0, i < 80, i++)

schreibe? Also ++i oder i++

von Nicht"Gast" (Gast)


Lesenswert?

Das ist völlig egal

von MaWin (Gast)


Lesenswert?

Nein.

Dein i ist ja keine Klasse, sondern nur ein uint8_t.
Und du benutzt das Ergebnis des ++ nicht in einer Formel weiter, wie bei 
j=++i;

von PittyJ (Gast)


Lesenswert?

Ein Unterschied würde es machen, wenn du ein Semikolon benutzen würdest. 
Dann könnte sogar eine richtige Schleife entstehen.

von Walter S. (avatar)


Lesenswert?

Sprudelstrudel schrieb:
> Macht es einen Unterschied ob ich
>
> for(uint8_t i = 0, i < 80, ++i)
>
> oder
>
> for(uint8_t i = 0, i < 80, i++)

kein Unterschied, ist beides falsch
( , willst du da nicht )

von Programmierer (Gast)


Lesenswert?

Angenommen es sollte ";" sein und nicht "," ...

In dem konkreten Fall macht es keinen Unterschied. Aber:
- "++i" bedeutet "inkrementiere i, und gebe das Ergebnis zurück"
- "i++" bedeutet "lege eine temporäre Kopie von i an, inkrementiere i, 
und gebe die temporäre Kopie zurück"

Letzteres ist offensichtlich die komplexere Operation. Wenn man diese 
aber nicht braucht (d.h. der vorherige Wert gar nicht benötigt wird), 
ist es sinnlos sie anzufordern; es reicht die einfachere erstere 
Operation, was man dem Leser des Codes durch Schreiben von "++i" klar 
macht.

Beim Inkrementieren/Dekrementieren von Zahlen erkennt der Compiler ggf. 
automatisch, dass tatsächlich nur die einfachere Operation nötig ist, 
und optimiert das entsprechend, d.h. es gibt keinerlei 
Performance-Nachteil.

In C++ kann man aber den "++" Operator überladen, typischerweise bei 
Iteratoren. Und da kann es durchaus einen Performance-Unterschied 
machen, ob da noch eine temporäre Kopie gemacht wird, die der Compiler 
ggf. nicht wegoptimiert. D.h. sollte man hier sofern möglich das "++i" 
nutzen.

Um nicht jedes Mal überlegen zu müssen ob der Compiler das "i++" zu 
"++i" optimieren kann (insb. auch wenn man den Typ von "i" von einem 
Integer zu einem Iterator ändert), kann man sich angewöhnen, einfach 
immer "++i" zu schreiben, es sei denn man braucht eben explizit den 
alten Wert.

von Sprudelstrudel (Gast)


Lesenswert?

Programmierer schrieb:
> Um nicht jedes Mal überlegen zu müssen ob der Compiler das "i++" zu
> "++i" optimieren kann (insb. auch wenn man den Typ von "i" von einem
> Integer zu einem Iterator ändert), kann man sich angewöhnen, einfach
> immer "++i" zu schreiben, es sei denn man braucht eben explizit den
> alten Wert.

Vielen Dank. Ich habe eine Libary angeschaut, um zu ergründen, wie das 
funktioniert. Und da bin ich eben über diese ++i gestolpert. Dieser Teil 
des Programms war allerdings nicht zeitkritisch, ein anderer schon

von Markus F. (mfro)


Lesenswert?

Du hast nach C (und nicht C++) gefragt.

Und da (in C) ist es völlig unerheblich, ob Prä- oder Postincrement.
In C++ nicht (jedenfalls nicht immer).

von Programmierer (Gast)


Lesenswert?

Markus F. schrieb:
> Du hast nach C (und nicht C++) gefragt.

C-Code hat die Tendenz, gelegentlich zu C++ umgebaut zu werden, oder in 
einem C++-Projekt inkludiert zu werden. C-Programmierer nutzen 
gelegentlich auch C++, und daher ist es durchaus sinnvoll, auch in C 
wann immer möglich "++i" zu nutzen, auch wenn es in den konkreten Fällen 
keinen direkten Vorteil hat; so gewöhnt man sich dran und der Code ist 
ggf. später besser zu C++ und Iteratoren portierbar. Schließlich hat es 
keinerlei Nachteil, ist nichtmals länger.

von Markus F. (mfro)


Lesenswert?

Programmierer schrieb:
> C-Code hat die Tendenz, gelegentlich zu C++ umgebaut zu werden, oder in
> einem C++-Projekt

warum heißt dann C++ C++ (und nicht ++C)?

von Carl D. (jcw2)


Lesenswert?

Markus F. schrieb:
> Programmierer schrieb:
>> C-Code hat die Tendenz, gelegentlich zu C++ umgebaut zu werden, oder in
>> einem C++-Projekt
>
> warum heißt dann C++ C++ (und nicht ++C)?

Weil das die Sonderzeichen-Phoben noch mehr verwirren würde.

von Nick M. (Gast)


Lesenswert?

Markus F. schrieb:
> warum heißt dann C++ C++ (und nicht ++C)?

Lt. Stroustrup:
Immer wenn man glaubt es verstanden zu haben, kommt wieder was dazu.

Das wäre bei einem pre-increment nicht so.

von theadib@gmail.com (Gast)


Lesenswert?

Übrigens spielt es auch eine Rolle welchen Datentyp man als 
Schleifenzähler nimmt.

Möglichst in der Verarbeitungsbreite des Prozessors.

Uint8_t nur bei AVR sinnvoll.
Ansonsten (unsigned) int.

Grüße, Adib.

von Bernd K. (prof7bit)


Lesenswert?

theadib@gmail.com schrieb:
> Übrigens spielt es auch eine Rolle welchen Datentyp man als
> Schleifenzähler nimmt.
>
> Möglichst in der Verarbeitungsbreite des Prozessors.

Darüber soll (und wird) sich der Compiler Gedanken machen, das ist 
schließlich sein Job. Wenn man sich um so einen Kleinkram noch selber 
kümmern müsste könnte man auch gleich in Assembler programmieren.

von Programmierer (Gast)


Lesenswert?

Bernd K. schrieb:
> Darüber soll (und wird) sich der Compiler Gedanken machen, das ist
> schließlich sein Job

Leider nein! Wenn man uint8_t schreibt, muss der Compiler auch das 8bit 
Overflow Verhalten garantieren. Das ist z.B. beim ARM ineffizient. Daher 
sollte man sowas wie uint_fast8_t nutzen, wenn man diesen speziellen 
Overflow nicht braucht. Leider gibt es keinen uint8_overflow_mir_egal_t 
...

von Wilhelm M. (wimalopaan)


Lesenswert?

theadib@gmail.com schrieb:
> Möglichst in der Verarbeitungsbreite des Prozessors.

Nein.
So groß wie nötig, und so schnell / klein wie möglich.

von Bernd K. (prof7bit)


Lesenswert?

Programmierer schrieb:
> Wenn man uint8_t schreibt, muss der Compiler auch das 8bit
> Overflow Verhalten garantieren.

Aber nicht in einer Zählschleife mit literalen Konstanten wie im OP, 
egal was ich da hinschreibe, mein Compiler hier zum Beispiel verwendet 
einfach ein 32 bit Register. Ein AVR-Compiler würde wahrscheinlich immer 
8 Bit verwenden, es sei denn die Variable kann größer werden.

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

Bernd K. schrieb:
> Aber nicht in einer Zählschleife mit literalen Konstanten wie im OP,
> egal was ich da hinschreibe, mein Compiler hier zum Beispiel verwendet
> einfach ein 32 bit Register.

Ja. Aber anstatt jedes Mal zu überlegen, ob das Maximum jetzt eine 
constant expression ist, kann man auch einfach immer uint_least8_t o.ä. 
nutzen. Meistens wird man für sowas wahrscheinlich eh size_t nehmen.

von Rolf M. (rmagnus)


Lesenswert?

Programmierer schrieb:
> Bernd K. schrieb:
>> Darüber soll (und wird) sich der Compiler Gedanken machen, das ist
>> schließlich sein Job
>
> Leider nein! Wenn man uint8_t schreibt, muss der Compiler auch das 8bit
> Overflow Verhalten garantieren. Das ist z.B. beim ARM ineffizient. Daher
> sollte man sowas wie uint_fast8_t nutzen, wenn man diesen speziellen
> Overflow nicht braucht.

An den meisten Stellen sind die *_fast_t und *_least_t eigentlich die am 
besten geeigneten Typen, denn man braucht in der Praxis nur sehr selten 
eine Variable, die zwingendermaßen eine ganz bestimmte exakte Bitbreite 
hat. Das kommt nur bei so Sachen wie Übertragungsprotokollen und 
Hardware-Registern vor, und vielleicht hin und wieder mal, wenn man ein 
bestimmtes Überlaufverhalten benötigt.
Eigentlich braucht man auch keine Mindestbreite in Bits, sondern viel 
mehr einen garantierten Wertebereich. Ich habe mir deswegen irgendwann 
mal Templates für die Integer-Typen geschrieben, wo man den benötigten 
Wertebereich angeben kann, a la int_least<-100, 100>. Dabei wird nicht 
dafür gesorgt, dass nur dieser Wertebereich genutzt werden kann, sondern 
es wird lediglich der kleinste oder schnellste der 
Standard-Integer-Typen gewählt, der diesen Wertebereich garantiert 
abdeckt. Ob der dann am Ende 8 oder 12 oder 37 Bits breit ist, ist mir 
ja eigentlich sehr oft egal.

Wilhelm M. schrieb:
> theadib@gmail.com schrieb:
>> Möglichst in der Verarbeitungsbreite des Prozessors.
>
> Nein.
> So groß wie nötig, und so schnell / klein wie möglich.

Auf 32-Bit-Prozessoren sind 32-Bit-Variablen meistens schneller als 
kleinere. Deshalb gibt es ja die Unterscheidung zwischen int_least*_t 
und int_fast*_t.

Programmierer schrieb:
> Aber anstatt jedes Mal zu überlegen, ob das Maximum jetzt eine
> constant expression ist, kann man auch einfach immer uint_least8_t o.ä.
> nutzen.

Ich würde dann eher uint_fast8_t nehmen. Denn Speicherverbrauch spielt 
für einen Schleifenzähler eher keine übergeordnete Rolle. Also braucht 
man nicht den kleinstmöglichen, sondern den schnellstmöglichen Typ.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Auf 32-Bit-Prozessoren sind 32-Bit-Variablen meistens schneller als
> kleinere. Deshalb gibt es ja die Unterscheidung zwischen int_least*_t
> und int_fast*_t.

Genau das meinte ich.

Allerdings kann es sein, dass 32-Bit zu klein ist.

von Oliver S. (oliverso)


Lesenswert?

Rolf M. schrieb:
>> constant expression ist, kann man auch einfach immer uint_least8_t o.ä.
>> nutzen.
>
> Ich würde dann eher uint_fast8_t nehmen.

Wobei mit an Sicherheit grenzender Wahrscheinlichkeit hinter beiden eh 
der selbe Datentyp steckt. Im Falle eines mingw-x86_64-gcc auf Windows 
10 z.B. ein unsigned char. Da ist es also schonmal nichts mit einem 
schnelleren 32-Bit-Typ.

Die ganze _least, _fast, und sonstigen Typen sind halt nur für portablen 
Code, der auf verschiednen Plattformen läuft, sinnvoll.

Innerhalb einer Implementierung sind das reine typedefs, da kann man 
dann auch gleich die dahinterliegenden Originaltypen nehmen.

Oliver

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Die ganze _least, _fast, und sonstigen Typen sind halt nur für portablen
> Code, der auf verschiednen Plattformen läuft, sinnvoll.

Und portabler Code ist immer sinnvoll.

von Volle (Gast)


Lesenswert?

Da jede CPU gut auf 0 prüfen kann wird ein guten Compiler das eh auf 
Abwärtszählen umbauen  und auch einen evtl vorhandene HW-Loop nutzen.

Nutzt man den Schleifenzähler für Operationen in der Schleife, werden 
solche Optimierungen aber verhindert.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Und portabler Code ist immer sinnvoll.

Das mag sein, Ich bezog mich aber hierauf:

Rolf M. schrieb:
> Programmierer schrieb:
>> Aber anstatt jedes Mal zu überlegen, ob das Maximum jetzt eine
>> constant expression ist, kann man auch einfach immer uint_least8_t o.ä.
>> nutzen.
>
> Ich würde dann eher uint_fast8_t nehmen.

Das klingt so, als ob der Compiler je nach Verwendung sich einen 
passenden Typ aussuchen würde, also mal für einen uint_least8_t einen 
uint8_t, und an einer anderen Stelle (Schleifenzähler) einen uint32_t. 
Das tut er aber eben nicht.

Oliver

: Bearbeitet durch User
von Mathias A. (mrdelphi)


Lesenswert?

Oliver S. schrieb:
> Das klingt so, als ob der Compiler je nach Verwendung sich einen
> passenden Typ aussuchen würde, also mal für einen uint_least8_t einen
> uint8_t, und an einer anderen Stelle (Schleifenzähler) einen uint32_t.
> Das tut er aber eben nicht.

Nicht von Fall zu Fall, aber dass er den auf der Plattform schnellsten 
Typ, der mindestens den Wertebereich von uint8_t abdeckt nimmt, hätte 
ich schon erwartet...

Oliver S. schrieb:
> Im Falle eines mingw-x86_64-gcc auf Windows 10 z.B. ein unsigned char.
> Da ist es also schonmal nichts mit einem schnelleren 32-Bit-Typ.

...von daher würde ich daraus schließen, dass entweder auf x86_64 
unsigned char nicht langsamer als 32- oder 64-Bit-Int ist, oder die 
mitgelieferten typedefs nicht so ganz optimal sind...

von Rolf M. (rmagnus)


Lesenswert?

#Oliver S. schrieb:
> Im Falle eines mingw-x86_64-gcc auf Windows 10 z.B. ein unsigned char. Da
> ist es also schonmal nichts mit einem schnelleren 32-Bit-Typ.

Ich glaube, auf x86 ist 8 Bit auch nicht langsamer als 32 Bit. Bei 16 
Bit sieht's wohl anders aus, da das einen zusätzlichen Befehlspräfix 
braucht. Deshalb ist zumindest auf meinem Linux für x86_64 int_fast16_t 
64 Bit breit.
Bei der ARM-Toolchain, die ich für meine STM32 habe, ist uint_fast8_t 32 
Bit breit.

> Die ganze _least, _fast, und sonstigen Typen sind halt nur für portablen
> Code, der auf verschiednen Plattformen läuft, sinnvoll.
>
> Innerhalb einer Implementierung sind das reine typedefs, da kann man
> dann auch gleich die dahinterliegenden Originaltypen nehmen.

Klar, wenn man nur für eine einzige Plattform programmiert und sich 
absolut sicher ist, dass der Code auch in Zukunft niemals je auf einer 
anderen Plattform laufen soll, verlieren sie an Bedeutung.

: Bearbeitet durch User
von Andreas M. (amesser)


Lesenswert?

Oliver S. schrieb:
> Rolf M. schrieb:
>>> constant expression ist, kann man auch einfach immer uint_least8_t o.ä.
>>> nutzen.
>>
>> Ich würde dann eher uint_fast8_t nehmen.
>
> Wobei mit an Sicherheit grenzender Wahrscheinlichkeit hinter beiden eh
> der selbe Datentyp steckt. Im Falle eines mingw-x86_64-gcc auf Windows
> 10 z.B. ein unsigned char. Da ist es also schonmal nichts mit einem
> schnelleren 32-Bit-Typ.

Gerade unter Linux geprüft: dort auch. Witzigerweise gilt das nur für 
den uint_fast8_t typ. der uint_fast16_t wird auf 64 bit gebogen...

Wieder was gelernt.

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Ich glaube, auf x86 ist 8 Bit auch nicht langsamer als 32 Bit.

Doch, das kann leicht passieren. In
   add eax, mem  (1)
   mov ..., eax
   mov al, ...   (2)
ist (2) auf modernen Prozessoren von (1) abhängig, da Register immer im 
Ganzen durch die execution units wandern. (2) ist also genau genommen
   ... = rax bits 8:63 <merged with> loaded data bits 0:7

Muss (1) aufgrund eines nicht im L1 Cache enthaltenen Speicherzugriffs 
warten, könnte (2) im Rahmen der OoO execution vorgezogen werden, wäre 
da nicht die Abhängigkeit.

Schneller als (2) kann dann beispielsweise
   xor eax, eax
   mov al, ...
sein, weil das XOR die Abhängigkeit von (1) entfernt. Die CPU behandelt 
diesen Befehl speziell, wissend, dass er stets 0 ergibt.

Bei 32-Bit Operationen tritt dieser Effekt nicht auf, da die Bits 32:63 
dabei implizit genullt werden.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Im echten Leben wird ja eine Schleife nicht nur Selbstzweck haben, 
sondern auch noch irgendwas machen sollen. Daher tritt in der Regel der 
Code- und Zeitbedarf des Schleifenzählers in den Hintergrund.
Den Unterschied zwischen uint_8 und uint_fast8_t oder ++i und i++ wird 
daher der Anwender nicht bemerken.

: Bearbeitet durch User
von Dumdi D. (dumdidum)


Lesenswert?

Volle schrieb:
> Da jede CPU gut auf 0 prüfen kann wird ein guten Compiler das eh auf
> Abwärtszählen umbauen

Wär spannend, wär mir sicher, dass das bei keinem Compiler auch nicht 
mit -O3 passiert.

von Markus F. (mfro)


Lesenswert?

Programmierer schrieb:
> C-Code hat die Tendenz, gelegentlich zu C++ umgebaut zu werden

meiner nicht.

von Rolf M. (rmagnus)


Lesenswert?

Markus F. schrieb:
> Programmierer schrieb:
>> C-Code hat die Tendenz, gelegentlich zu C++ umgebaut zu werden
>
> meiner nicht.

Bei mir hat älterer C-Code diese Tendenz. Neuerer nicht mehr. Der hat 
eher die Tendenz, gar nicht erst in C, sondern gleich in C++ geschrieben 
zu werden.

von Wilhelm M. (wimalopaan)


Lesenswert?

Ich schreibe grundsätzlich nur in C++.

Viel wichtiger ist die Entscheidung, ob man

- prozedural
- generisch
- meta-programmatisch
- funktional
- objekt-orientiert

als Schwerpunkt in C++ wählt.

von Meister E. (edson)


Lesenswert?

Markus F. schrieb:
> Du hast nach C (und nicht C++) gefragt.
>
> Und da (in C) ist es völlig unerheblich, ob Prä- oder Postincrement.
> In C++ nicht (jedenfalls nicht immer).

Stimmt auch nicht. Es kommt immer darauf an, was der Compiler für die 
spezifische Hardware daraus macht.

von Markus F. (mfro)


Lesenswert?

Meister E. schrieb:
> Stimmt auch nicht. Es kommt immer darauf an, was der Compiler für die
> spezifische Hardware daraus macht.

Dann weih' uns mal in dein umfangreiches Wissen ein: welcher Compiler 
macht da welchen Unterschied?

Oder machen nicht vielleicht doch alle einfach das, was dasteht: eine 
Variable inkrementieren?

von Rolf M. (rmagnus)


Lesenswert?

Wenn der Wert des Ausdrucks so wie hier nicht benutzt wird, dann sollte 
eigentlich jeder halbwegs brauchbare Compiler aus diesem Jahrtausend 
exakt das gleiche draus machen.

von Oliver S. (oliverso)


Lesenswert?

Und zwar sowohl in C als auch in C++.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Für Typen wie int natürlich, ja. Bei Klassen ist es den Compiler je nach 
Implementation aber ggf. nicht möglich.

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.