mikrocontroller.net

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


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Sprudelstrudel (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
-4 lesenswert
nicht lesenswert
Das ist völlig egal

von MaWin (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
2 lesenswert
nicht 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)


Bewertung
2 lesenswert
nicht 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)


Bewertung
3 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
4 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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. (muellernick)


Bewertung
1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
-3 lesenswert
nicht 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)


Bewertung
3 lesenswert
nicht 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)


Bewertung
-4 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
2 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
-2 lesenswert
nicht 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)


Bewertung
1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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 A. K. (prx)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


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

meiner nicht.

von Rolf M. (rmagnus)


Bewertung
1 lesenswert
nicht 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)


Bewertung
-1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Und zwar sowohl in C als auch in C++.

Oliver

von Rolf M. (rmagnus)


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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.