Forum: Mikrocontroller und Digitale Elektronik C programmieren wie die grossen Jungs


von Oktoberfestbesucher (Gast)


Lesenswert?

Habe mal meine eigene ultoa Routine programmiert siehe:
Beitrag "Zahl ausgeben mit sprintf frisst Resourcen"

Den Algorithmus habe ich mir selber ausgedacht,
ich wollte kein div(10), %10 oder /10 drin haben!
1
unsigned char i;
2
char Ziffer[10];
3
for(i=0;i <= 10;i++) Ziffer[i] = 0;
4
5
for(i=0;i < 32;i++)
6
{
7
  unsigned char Carry_flag;
8
  unsigned char n;
9
  if(Zahl & 0x80000000) Carry_flag = 1; else Carry_flag = 0;
10
  Zahl <<= 1;
11
  for(n=9;n <= 9;n--)
12
  {
13
    Ziffer[n] <<= 1;
14
    if(Carry_flag) Ziffer[n]++;
15
    if(Ziffer[n] >= 10)
16
    {
17
      Ziffer[n] -= 10;
18
      Carry_flag = 1;
19
    }
20
    else
21
    {
22
      Carry_flag = 0;
23
    }
24
  }
25
}
26
// Führende Nullen sind noch zu unterdrücken  ;-)
Ist getestet, funktioniert 1A
Was kann ich das stilistisch noch besser machen?
Wie geht das mit Pointern?
Bei mir irgendwie nicht, in dem Punkt verstehe ich mein C-Buch nicht...

von Pete K. (pete77)


Lesenswert?

Dann lies den Rest des Buches auch noch einmal durch ...

Die erste For-Schleife ist schon fehlerhaft.

von Zwie B. (zwieblum)


Lesenswert?

Mann, bist du pingelig! Ein off-by-one ist doch kein Fehler, das ist ein 
Feature, so was wie eine Kinderüberraschung für die grossen Jungs ;-)

von Oktoberfestbesucher (Gast)


Lesenswert?

Pete K. schrieb:
> Die erste For-Schleife ist schon fehlerhaft.

Bin mir nicht sicher, ob ich verstehe was du meinst.
Das Programm funktioniert so, ist mit einer manigfaltigkeit von Zahlen 
getestet!

Meinst du ich hätte das Array grösser machen sollen?!
1
char Ziffer[11];

von ... (Gast)


Lesenswert?

Nö, die funktioniert nicht!
Die hinterläßt einen kaputten Stack.

von Karl H. (kbuchegg)


Lesenswert?

char Ziffer[10];
for(i=0;i <= 10;i++) Ziffer[i] = 0;



Dein Array umfasst 10 Elemente. Da das erste den Index 0 hat, hat das 
letzte daher welchen Index?

Die for-Schleife: Welche Werte für i generiert sie?

Auf welche Indexnummern in Ziffern wird daher zugegriffen?

von Karl H. (kbuchegg)


Lesenswert?

Oktoberfestbesucher schrieb:

> Das Programm funktioniert so

Aber nur wenn man nicht zu genau hinschaut :-)

von Markus -. (mrmccrash)


Lesenswert?

Nein, es geht um die Abbruchbedingung der ersten for-Schleife. Dein 
Array ist 10 Stellen groß. was passiert, wenn du nun von 0 aufwärts nach 
10 zählst?

_.-=: MFG :=-._

von Pete K. (pete77)


Lesenswert?

Mich wundert, dass der Überlauf nicht zum Programmabbruch führt. Hat da 
der Compiler (welcher wird benutzt?) etwas wegoptimiert?

Die for(n) Schleife wird auch nur einmal durchlaufen -> überflüssig.

Außerdem fehlen die Kommentare. Das gehört zum Programmieren auch dazu 
:-)

von Stefan E. (sternst)


Lesenswert?

Oktoberfestbesucher schrieb:
> Das Programm funktioniert so, ist mit einer manigfaltigkeit von Zahlen
> getestet!

Wichtige Programmier-Lektion:

"Programm verhält sich wie erwartet"
ist ganz und gar nicht das Gleiche wie
"Programm ist fehlerfrei"

von jl (Gast)


Lesenswert?

> for(i=0;i < 32;i++)
> {
>   unsigned char Carry_flag;
>   unsigned char n;


dafür hätte ich dich "verprügelt" würdest du sowas in meinem Projekt 
abgeben.
Deklarationen gehören bei mir immer an den Begin einer Funktion oder an 
den beginn eines C-Files. Auch wenns nach C&R überall elaubt ist suchst 
du dich bei Fehlern tot.

von Karl H. (kbuchegg)


Lesenswert?

Pete K. schrieb:
> Mich wundert, dass der Überlauf nicht zum Programmabbruch führt.

Hängt davon ab, wie die genaue Sitation am Stack bzw. im Speicher 
aussieht.

> Die for(n) Schleife wird auch nur einmal durchlaufen -> überflüssig.

Doch die wird ausgeführt.
Er benutzt einen Unterlauf als Abbruchkriterium.
Sie wird sogar ziemlich oft ausgeführt. Genau 288 mal.

von Karl H. (kbuchegg)


Lesenswert?

jl schrieb:
>> for(i=0;i < 32;i++)
>> {
>>   unsigned char Carry_flag;
>>   unsigned char n;
>
>
> dafür hätte ich dich "verprügelt" würdest du sowas in meinem Projekt
> abgeben.

Dann werden wir nie in einem gemeinsamen Projekt arbeiten.

> den beginn eines C-Files. Auch wenns nach C&R überall elaubt ist suchst
> du dich bei Fehlern tot.

Das genaue Gegenteil ist der Fall

von Mano W. (Firma: ---) (manow)


Lesenswert?

Oktoberfestbesucher schrieb:
> ...
>
1
> ...
2
> 
3
> for(i=0;i < 32;i++)
4
> {
5
>   ...
6
>   if(Zahl & 0x80000000) Carry_flag = 1; else Carry_flag = 0;
7
>   Zahl <<= 1;
8
>  ...
9
> }
10
> ...
11
>

Sehe ich das richtig, dass Zahl min. 32 Bit groß ist und das schiebst Du 
immer wieder mal nach links? - Das geht ja sicher total flott.

von Peter D. (peda)


Lesenswert?


von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

1
  if(Zahl & 0x80000000) Carry_flag = 1; else Carry_flag = 0;
Voll umständlich, das hier. Große machen das so:
1
  Carry_flag=(Zahl&0x80000000)?1:0;
Das spart Speicherplatz auf deinem Rechner... ;-)



> Deklarationen gehören bei mir ... an den beginn eines C-Files.
Ja, finde ich auch:
nur eine volatile globale Variable ist eine richtige Variable :-)
Und wozu braucht man eigentlich einen Stack?

von Pink S. (pinkshell)


Lesenswert?

Lothar Miller schrieb:
  Carry_flag=(Zahl&0x80000000)?1:0;

Oder gleich so ?
Carry_flag=(Zahl&0x80000000);

Diese x?y:z stören imho die Lesbarkeit.

von Karl H. (kbuchegg)


Lesenswert?

Pink Shell schrieb:
> Lothar Miller schrieb:
>   Carry_flag=(Zahl&0x80000000)?1:0;
>
> Oder gleich so ?
> Carry_flag=(Zahl&0x80000000);

Das macht aber etwas anderes :-)

> Diese x?y:z stören imho die Lesbarkeit.

Aber es kann den Unterschied ausmachen zwischen funktioniert und 
funktioniert nicht :-)

Aber bitte, wenns dich stört:

Carry_flag = !!(Zahl&0x80000000);

Besser?

von Peter D. (peda)


Lesenswert?

jl schrieb:

> Deklarationen gehören bei mir immer an den Begin einer Funktion oder an
> den beginn eines C-Files. Auch wenns nach C&R überall elaubt ist suchst
> du dich bei Fehlern tot.

Nö.

Lokale Variablen schreibt man immer an den Anfang des kleinsten Blocks, 
in dem sie gültig sein müssen.

Der Compiler merkt zwar oft selber, wie lange eine Variable benötigt 
wird, aber die Lesbarkeit wird deutlich besser, wenn man das auch klar 
sieht und nicht erst 4 Seiten einer Funktion durchsuchen muß, ob die 
Variable woanders noch auftaucht.

Die Schreibweise "for( uint8_t i = ..."
benutze ich sehr gerne, damit jeder sofort weiß, "i" wird nach der 
Schleife nicht mehr benötigt.


Der Techniker sagt: Nur so genau, wie nötig.
Und der Programmierer sagt: Nur solange gültig, wie nötig.


Peter

von Experte (Gast)


Lesenswert?

Diese Kombination ist auch sehr lustig:
1
  unsigned char n;
2
  ...
3
  for(n=9;n <= 9;n--)
4
  ...

Schon spannend, wie falsch man "programmieren" kann und es doch noch 
irgendwie funktioniert...

Aber solbald man die Optimierung im Compiler einschaltet und der 
Compiler "XYZ ist nicht definiert" aus dem Standard ernst nimmt, kracht 
es.

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Experte schrieb:
> Diese Kombination ist auch sehr lustig:

Wieso?
n läuft dabei doch von 9 bis 0 und die Schleife endet dann.
Nach Schleifenende ist n gleich 255 (falls "unsigned char" gleich 
"uint8_t" ist).

Oder sehe ich das jetzt etwas falsch?

Karl heinz Buchegger schrieb:
> Sie wird sogar ziemlich oft ausgeführt. Genau 288 mal.

Das verstehe ich jedenfalls nicht.

von Karl H. (kbuchegg)


Lesenswert?

Christian H. schrieb:

> Karl heinz Buchegger schrieb:
>> Sie wird sogar ziemlich oft ausgeführt. Genau 288 mal.
>
> Das verstehe ich jedenfalls nicht.


Die äussere Schleife 32 mal, bei jeder Iteration läuft die innere 
Schleife 9 mal. 32*9 = 288. D.h. der Schleifenrumpf der inneren Schleife 
wird 288 mal durchgehechelt.

von Kenji N. (shaitan)


Lesenswert?

Peter Dannegger schrieb:
> Nö.
>
> Lokale Variablen schreibt man immer an den Anfang des kleinsten Blocks,
> in dem sie gültig sein müssen.
>
> Der Compiler merkt zwar oft selber, wie lange eine Variable benötigt
> wird, aber die Lesbarkeit wird deutlich besser, wenn man das auch klar
> sieht und nicht erst 4 Seiten einer Funktion durchsuchen muß, ob die
> Variable woanders noch auftaucht.
>
> Die Schreibweise "for( uint8_t i = ..."
> benutze ich sehr gerne, damit jeder sofort weiß, "i" wird nach der
> Schleife nicht mehr benötigt.

Jupp sehe ich auch so. Wenn man Variablen nur in einem lokalen Scope 
braucht, dann sollten sie auch dort deklariert werden, aber dann auch am 
anfang und nicht irgendwo in der mitte. Dann weis man gleich das man die 
sich nicht die ganze Zeit merken muss, gleiches für die deklaration der 
Zählervariable der For-Schleife. Der Compiler ist vielleicht so 
intelligent und bekommt das hin, aber der Mensch ist kein Compiler...

von Martin (Gast)


Lesenswert?

>> for(i=0;i < 32;i++)
>> {
>>   unsigned char Carry_flag;
>>   unsigned char n;


>dafür hätte ich dich "verprügelt" würdest du sowas in meinem Projekt
>abgeben.
>Deklarationen gehören bei mir immer an den Begin einer Funktion oder an
>den beginn eines C-Files. Auch wenns nach C&R überall elaubt ist suchst
>du dich bei Fehlern tot.

ich könnte wetten du hast mit Fortran angefangen.
vielleicht dann dazwischen auf Basic umgestiegen :)

Man sucht sich höchstens dann tot, wenn man alle Variablen
überall sichtbar macht. Im schlimmsten Fall global. Information
hiding ist die Divise. Ich selbst verwende manchmal eine Variable
doppelt und dreifach im Körper einer Funktion um einfach die
Zwischenergebnisse ohne sinnvollen Namen festzuhalten. Dann
mache ich eher

int tmp;
if() {
tmp=...
}
...
if() {
tmp=...
}

statt

if() {
int tmp;
tmp=...
}
...
if() {
int tmp;
tmp=...
}


was man dann aber nicht unbeding machen sollte ist shadowing ala
int x;
if(bla) {
    double x;
    x=...
}

das verwirrt oft wiederum.

von Andreas F. (aferber)


Lesenswert?

Christian H. schrieb:
> Wieso?
> n läuft dabei doch von 9 bis 0 und die Schleife endet dann.
> Nach Schleifenende ist n gleich 255 (falls "unsigned char" gleich
> "uint8_t" ist).
>
> Oder sehe ich das jetzt etwas falsch?

Ja. Das Verhalten von
1
unsigned char n;
2
n = 0; n--;
ist undefiniert. Dass es mit einem bestimmten Compiler auf einer 
bestimmten Architektur immer 255 ergibt ist (aus Sicht von C) reiner 
Zufall.

Andreas

von Karl H. (kbuchegg)


Lesenswert?

Andreas Ferber schrieb:

>> Oder sehe ich das jetzt etwas falsch?
>
> Ja. Das Verhalten von
>
1
> unsigned char n;
2
> n = 0; n--;
3
>
> ist undefiniert.

Ähm. Nein.
Woher hast du diese Erkentnis?

Gerade dieser Fall ist exakt definiert. Für signed Typen ist 
undefiniert, was passiert, aber für unsigned Typen ist festgelegt was 
passieren muss

> Dass es mit einem bestimmten Compiler auf einer
> bestimmten Architektur immer 255 ergibt ist (aus Sicht von C) reiner
> Zufall.

Der Zufall heißt C-Standard. Und darin ist festgelegt, dass unsigned 
Typen sich wie bei einem Rollover zu verhalten haben. Nach der 
kleinesten Zahl, geht es mit der größten weiter.

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:
> Die äussere Schleife 32 mal,

Ok, ich hatte gedacht, dass nur die innere gemeint war.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:

> Der Zufall heißt C-Standard. Und darin ist festgelegt, dass unsigned
> Typen sich wie bei einem Rollover zu verhalten haben. Nach der
> kleinesten Zahl, geht es mit der größten weiter.

Funktioniert allerdings nur so lange, wie unsigned char identisch zu
uint8_t ist.  Wäre also ein typischer Anwendungsfall, gleich uint8_t
zu schreiben, damit es auf Maschinen, die sowas nicht haben, dann
einen Compilefehler gibt.

Allerdings würde ich mal behaupten, dass diese uint8_t-Überlauf-
Variante für eine Anzahl von Prozessoren eher eine Pessimierung
darstellt, und von guter Lesbarkeit (stand da nicht was von "wie
die großen Jungs"?) schweigen wir lieber.  Eine solche krückige
Konstruktion gehört zumindest in ihrer Wirkungsweise kommentiert,
wobei der Kommentar auch gleich noch beinhalten sollte (am besten
mit der Zahl eingesparter Taktzyklen belegt), warum man solch
eine Konstruktion gewählt hat.

Ob Implementierung einer Division durch 10 in Form einer fortlaufenden
Subtraktion wirklich irgendeinen Gewinn bringt gegenüber einer normalen
Division, wage ich auch zu bezweifeln.

von Peter D. (peda)


Lesenswert?

Jörg Wunsch schrieb:
> Ob Implementierung einer Division durch 10 in Form einer fortlaufenden
> Subtraktion wirklich irgendeinen Gewinn bringt gegenüber einer normalen
> Division, wage ich auch zu bezweifeln.

Auf CPUs ohne Division ist die Subtraktionsmethode deutlich schneller.
Da eine Dezimalwandlung aber nur für den viel langsameren Menschen nötig 
ist, merkt man das nicht.


Diese Routine benutzt aber garnicht die Subtraktionsmethode, sondern die 
dezimale Multiplikation mit 2.
Das sind hier schon stattliche 320 Schleifendurchläufe, da könnte die 
Division mit Rest sogar schneller sein.


Peter

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

>>>> Carry_flag = (Zahl&0x80000000)?1:0;
>>> Diese x?y:z stören imho die Lesbarkeit.
Karl heinz Buchegger schrieb:
>> Carry_flag = !!(Zahl&0x80000000);

Bitteschön, noch ein paar Varianten:
1
   uint32_t a,b,c,d,e;
2
    uint8_t z,y,x,w,v;
3
4
    z = (a&0x80000000)?1:0;
5
6
    y = (b&0x80000000)?!0:0;
7
8
    x = !((c&(1UL<<31))?0:!0);
9
10
    w = !!(d&0x80000000);
11
12
    v = !!((e>>24)&0x80);
Der AVR-GCC-Compiler macht aus allen Beispielen insgesamt das selbe, nur 
bei der letzten Variante wird das Ergebnis nochmal verundet:
1
 lds  r24, 0x0079
2
 lds  r25, 0x007A
3
 lds  r26, 0x007B
4
 lds  r27, 0x007C
5
 eor  r24, r24
6
 sbrc  r27, 7
7
 inc  r24
8
 eor  r25, r25
9
 eor  r26, r26
10
 eor  r27, r27
11
 andi  r24, 0x01  ; nur bei der letzten Variante
12
 sts  0x006F, r24

Leider muß ich zugeben, dass die Variante mit der if-Abfrage am 
effizientesten und schnellsten implementiert wird, weil nicht der ganze 
long-Wert manipuliert wird  :-(
1
    if (f&0x80000000) u=1; 
2
    else              u=0;
Das ergibt:
1
 lds  r24, 0x0065
2
 lds  r25, 0x0066
3
 lds  r26, 0x0067
4
 lds  r27, 0x0068
5
 sbrs  r27, 7
6
 rjmp  .+8        ; 0x162 <main+0xbe>
7
 ldi  r24, 0x01  ; 1
8
 sts  0x0081, r24
9
 rjmp  .+4        ; 0x166 <main+0xc2>
10
 sts  0x0081, r1

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch schrieb:

> Allerdings würde ich mal behaupten, dass diese uint8_t-Überlauf-
> Variante für eine Anzahl von Prozessoren eher eine Pessimierung
> darstellt, und von guter Lesbarkeit (stand da nicht was von "wie
> die großen Jungs"?) schweigen wir lieber.

Absolut ACK

Die "großen Jungs" achten lieber auf Lesbarkeit anstatt auf den letzten 
Taktzyklus :-)

> Ob Implementierung einer Division durch 10 in Form einer fortlaufenden
> Subtraktion wirklich irgendeinen Gewinn bringt gegenüber einer normalen
> Division, wage ich auch zu bezweifeln.

Das müsste man genauer durchleuchten.
Um ehrlich zu sein, habe ich diese Idee so noch nie irgendwo gesehen. 
Wenn es tatsächlich um das Vermeiden von Divisionen geht, würde ich das 
so machen, wie es im Ass Tutorial überall gemacht wird: Subtraktion von 
10000, 1000, 100, 10 und mitzählen. 32 Bit Divisionen sind schon nicht 
ohne, wenn man sie komplett in Software machen muss.

Die andere Seite der Medaille sieht so aus:
Wo wird denn so eine Umwandlung typischerweise gemacht?
Ich glaube ich lehne mich hier nicht zuweit aus dem Fenster, wenn ich 
sage: In einem Prozentsatz von ungefähr 98% (sag niemals 100%, man kann 
dich darauf festnageln), fällt diese Operation vor einer I/O Operation 
an. Und damit ist sie in den wenigsten Fällen wirklich zeitkritisch.

Aber ich wollte den TO nicht frustrieren. Denn immerhin: Er hat sich 
etwas überlegt, er hat es implementiert (von dem Schnitzer abgesehen) 
und das finde ich gut. Weiter so!

von Thomas B. (escamoteur)


Lesenswert?

Du wolltest das doch mal mit Pointern haben, oder nicht unda dazu noch 
optimiert.
1
unsigned char i;
2
char Ziffer[10];
3
for(i=0;i <= 10;i++) Ziffer[i] = 0;
1
unsigned char i;
2
char Ziffer[10];
3
char* p = &Ziffer[0];
4
for(i=10;i != 0;i--)
5
{
6
   *p++= 0;
7
}

Überprüfung mit 0 ist schneller als <=10. Bei *p++  muss nicht jedesmal 
die Adresse Ziffer[i] neu berechnet werden, da das konkret bedeutet:

*(&Ziffer[0] + i * sizeof(i))

je nach Prozessor kann *p++ in einem einzigen Befehl abgearbeitet 
werden.

Gruß
Tom
P.S.: Hoffe ich hab da jetzt nicht daneben gehaunen

von jl (Gast)


Lesenswert?

Martin schrieb:
> ich könnte wetten du hast mit Fortran angefangen.
>
> vielleicht dann dazwischen auf Basic umgestiegen :)

Ne Fortran nicht, aber 87 mit Basic und Assembler auf einem Z80.
Dann C für uC etwas Pascal/Delphi für die PC Seite.

Von Pascal hab ich etwas für mich mitgenommen wie ich etwas 
strukturierter in C organisieren kann und C-Files als Objekte betrachte. 
Zur lesbarkeit gibt es bei mir am Fileanfang die Deklarationen der 
globalen Variablen, und alle variablen die nur in einer Funktion lokal 
benutzt werden am Funktionsanfang. Da eine Funktion sowieso nicht über 
1000Zeilen gehen soll braucht es nur einen kurzen Blick an den Anfang 
der Funktion.

Gerade bei verteilter Entwicklung und extremer Fluktuation der 
Entwickeler ist eine absolute simple Struktur von Nöten. Da dann 
manchmal auch noch unerfahrene ins Projekt reinkommen aber eine 
Anpassung erfolgen soll werden damit kleine Denkfehler vermindert.

Ebenso verfluche ich den Konstrukt a ? b : c, Selektive in einezelnen 
Funktionen wenns klar ist und eindeutig ist ist es OK, aber was da 
manchmal in geschachtelten Makros zusammengebastelt wird (das schlimmste 
waren bei mir 7 ? Konstrukte über verschiedenste Makros ineinander 
gesetzt) ist schon extrem Fehleranfällig.

Aber das ist hier keine Schönschreibdiskussion.



JL

von Stefan W. (wswbln)


Lesenswert?

"bei verteilter Entwicklung und extremer Fluktuation der
Entwickeler" würde ich mir eh' überlegen, ob C die richtige 
Programmiersprache ist oder nicht eine mit strengeren (Interface-)Regeln 
wie Modula oder Ada vielleicht besser wäre. Aber die Tools dürfen ja 
immer nichts kosten (so gesehen die Crux von GCC). Dann reicht es auch 
oft nicht mal für statische Analysetools, so deren Existenz im 
"Management" überhaupt bekannt ist...

von Rene K. (draconix)


Lesenswert?

Oktoberfestbesucher schrieb:
>
1
for(n=9;n <= 9;n--)...

Verstehe ich nicht?! Das dürfte doch genau einmal durchlaufen...
Direkt nach dem ersten Aufruf wird N kleiner gleich 9.. somit dürfte die 
Schleife beendet sein?!

von Thomas B. (escamoteur)


Lesenswert?

Ähm, die Bedingung sagt "solange n<= als9 ist soll die Schleife 
ausgeführt werden.!

von Rene K. (draconix)


Lesenswert?

Thomas Burkhart schrieb:
> Ähm, die Bedingung sagt "solange n<= als9 ist soll die Schleife
> ausgeführt werden.!

Achso... ja... meine Güte so verdreht hab ichs noch nie gesehen o.O

von Hannes L. (hannes)


Lesenswert?

Rene K. schrieb:
> Oktoberfestbesucher schrieb:
>>
1
for(n=9;n <= 9;n--)...
>
> Verstehe ich nicht?! Das dürfte doch genau einmal durchlaufen...
> Direkt nach dem ersten Aufruf wird N kleiner gleich 9.. somit dürfte die
> Schleife beendet sein?!

Schon vor dem ersten Durchlauf ist n kleinergleich 9, nämlich 9, also 
gleich 9. Demnach ist die Logik wohl andersherum (while <-> until).

;-)

...

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Die GROSSEN Jungs machen GROSSE Programme, die passen nur in GROSSE CPUs 
mit ARM-32-Bit Architektur oder ähnlich leistungsfähigen Typen mit 
GROSSEM Flash Speicher.
Damit erübrigt sich das Problem mit Division automatisch, weil die CPUs 
das alle samt gut können.

von Andreas F. (aferber)


Lesenswert?

Karl heinz Buchegger schrieb:
>> Ja. Das Verhalten von
>>
1
>> unsigned char n;
2
>> n = 0; n--;
3
>>
>> ist undefiniert.
> Ähm. Nein.
> Woher hast du diese Erkentnis?

Yepp, my fault. Das im Hinterkopf verankerten "Vorsicht bei Overflow" zu 
stark verschärft. IIRC hatte der GCC da in letzter Zeit auch ein paar 
Optimizer-Bugs rund um das Overflow-Verhalten, so dass er 
sicherheitsrelevante Range-Checks wegoptimiert hat.

Andreas

von Oktoberfestbesucher (Gast)


Lesenswert?

Bin nach den Osterferien noch mal drübergegangen:
1
// unsigned long int Zahl an einer bestimmten Position auf LCD ausgeben
2
void LCD_Anzeige_Zahl(unsigned char pos,unsigned long int Zahl)
3
{
4
  char Ziffer[10]; // Array mit Ziffer[0] bis Ziffer[9]
5
  char* P = &Ziffer[0]; // Pointer auf Ziffer[0]
6
7
  LCD_Instruction(pos); // Zeichen-position an LCD
8
9
  for(unsigned char i = 10;i;i--)  *P++ = 0; // Array initialisieren
10
11
  for(unsigned char i = 32;i;i--) // unsigned long int hat 32 Bit
12
  {
13
    P = &Ziffer[9];
14
    unsigned char Carry_flag = (Zahl & 0x80000000) ? 1 : 0;// Test MSB
15
    Zahl <<= 1;
16
17
    for(unsigned char n = 10;n;n--)
18
    {
19
      *P <<= 1;
20
      if(Carry_flag)  (*P)++;
21
      if(*P >= 10)
22
      {
23
        *P -= 10;
24
        Carry_flag = 1;
25
      }
26
      else Carry_flag = 0;
27
      P--; // Zeiger auf nächste Ziffer
28
    }
29
  }
30
31
  // führende Nullen als Leerzeichen ausgeben
32
  unsigned char leading_zero_flag = 0;
33
  P = &Ziffer[0];
34
  for (unsigned char i = 10;i;i--) 
35
  {
36
    if(leading_zero_flag || *P || i == 1) 
37
      {
38
        LCD_Zeichen(*P + '0'); // Ausgabe Ziffer + ASCII '0'
39
        leading_zero_flag = 1;
40
      }
41
      else LCD_Zeichen(' ');
42
    P++;
43
  }
44
}
So besser?

von Karl H. (kbuchegg)


Lesenswert?

Du entwickelst für meinen Geschmack da eine Vorliebe für SChleifen, die 
von einem Endwert runterzählen, obwohl sie das gar nicht müssten.

Was spricht gegen
1
  for(unsigned char i = 0; i < 10; ++i )
2
    *P++ = 0; // Array initialisieren

Jeder C-Programmierer weltweit erkennt so ein Konstrukt sofort als 
Schleife, die 10-mal durchlaufen wird. Bei jeglicher Array-Arbeit kommt 
so etwas vor, es ist etwas woran sich alle gewöhnt haben und das sie aus 
20 Meter Entfernung, direkt nach dem Aufstehen mit dem linken Fuss mit 
verschlafenen Augen noch vor dem ersten Kaffee sofort wiedererkennen.

Bei
1
  for(unsigned char i = 10;i;i--)  *P++ = 0; // Array initialisieren

müssen die meisten (inklusive mir) wahrscheinlich erst mal eine 
Gedenkminute einlegen, um sich davon zu überzeugen, dass das auch nur 
eine Zählschleife ist, die 10 mal ausgeführt wird.

von Oktoberfestbesucher (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> eine Vorliebe für SChleifen, die
> von einem Endwert runterzählen, obwohl sie das gar nicht müssten.

Mache ich schon lange so, kommt von einer Vorliebe für 8051-Assembler.
Dort sieht das z.B. so aus
1
    PUSH   Loopcounter
2
    MOV    Loopcounter,#10 'Diese Schleife wird 10 mal durchlaufen
3
my_weird_loop:
4
5
6
7
8
    DJNZ   Loopcounter,my_weird_loop
9
    POP    Loopcounter
der Test ob die Schleife zuende ist ist hier sehr einfach.

Ich bin nicht beratungsresistent => also werde ich es in Zukunft so 
machen wie du es schreibst.

von Peter D. (peda)


Lesenswert?

Karl heinz Buchegger schrieb:
> Bei  for(unsigned char i = 10;i;i--)  *P++ = 0; // Array initialisieren
>
> müssen die meisten (inklusive mir) wahrscheinlich erst mal eine
> Gedenkminute einlegen,

Dann gehöre ich nicht zu Deinen "meisten".
Ich finde nämlich diese Schreibweise deutlich besser zu lesen.
Bei Deiner Schreibweise kann man leicht reinfallen, wenn da i <= 10; 
steht, läuft sie 11 mal. Oder wenn sie bei 1 startet, nur 9-mal.
Ich hab daher so schon mehrmals falsche Schleifenzahlen gehabt, bis ich 
die schönere Schleife entdeckte.

Außerdem erzeugt das i-- den kürzeren Code, da der Vergleich gespart 
wird.
Beim 8051 gibts dafür sogar den speziellen 3-fach Befehl DJNZ 
(runterzählen, testen, springen), der auch keine Flags zerstört.


Peter

von Walter T. (nicolas)


Lesenswert?

Unter Matlab kann ich mir durch das runterzählen in Schleifen oft das 
reservieren von Arrays sparen :-).

Viele Grüße
Nicolas

von moo (Gast)


Lesenswert?

// for(unsigned char i = 10;i;i--)  *P++ = 0;

ich schreib's sehr oft so:

i=10;
while(i--)
{
   *p++ = 0;
}

von Peter D. (peda)


Lesenswert?

Manchmal wird die Schleifenzahl auch als Paramter übergeben, dann kann 
man diesen gleich runterzählen und muß keine extra Hochzählvariable 
verschwenden.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Peter Dannegger schrieb:
> Karl heinz Buchegger schrieb:
>> Bei  for(unsigned char i = 10;i;i--)  *P++ = 0; // Array initialisieren
>>
>> müssen die meisten (inklusive mir) wahrscheinlich erst mal eine
>> Gedenkminute einlegen,
>
> Dann gehöre ich nicht zu Deinen "meisten".

Mag sein.
Ich hab in all den Jahren schon viele Fremdlibraries studiert. Derartige 
aufwärts for-Schleifen gibt es zu Millionen. Aber anders rum, hmm, da 
brauch ich noch nicht mal alle 10 Finger um diejenigen der letzten 20 
Jahre zu zählen :-)

> Ich finde nämlich diese Schreibweise deutlich besser zu lesen.
> Bei Deiner Schreibweise kann man leicht reinfallen, wenn da i <= 10;
> steht, läuft sie 11 mal.

Die Kriterien sind einfach
  Startwert 0
  Vergleich: kleiner
  einfaches Inkrement
  und natürlich überall dieselbe Variable.

dann ist es eine Zählschleife die genau so oft zählt, wie es in der 
Abbruchbedingung steht.

> Außerdem erzeugt das i-- den kürzeren Code, da der Vergleich gespart
> wird.
> Beim 8051 gibts dafür sogar den speziellen 3-fach Befehl DJNZ
> (runterzählen, testen, springen), der auch keine Flags zerstört.

Das überlass ich dem Compiler :-)
Im Ernst: Einige Optimizer, bei denen ich mir das angesehen habe, 
implementieren derartige Schleifen auch durch runterzählen, wenn die 
Schleifenvariable im Rumpf nicht benutzt wird. Genauso wie ein 
ordentlicher Compiler

   for( i = 0; i < 10; ++i )
     Ziffer[i] = '\0'

das selbsttätig auf Pointerzugriff umstellt :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:

> Ich hab in all den Jahren schon viele Fremdlibraries studiert. Derartige
> aufwärts for-Schleifen gibt es zu Millionen. Aber anders rum, hmm, da
> brauch ich noch nicht mal alle 10 Finger um diejenigen der letzten 20
> Jahre zu zählen :-)

Kommt sicher daher, dass sie halt im K&R auch immer in dieser Form
geschrieben standen.  Mir geht's hier so wie dir, und wenn man eine
Weile C programmiert, kommt man eigentlich nie wieder auf die Idee,
die Schleife bei 1 anfangen zu lassen oder <= als Endbegrenzer zu
benutzen...

>> Beim 8051 gibts dafür sogar den speziellen 3-fach Befehl DJNZ
>> (runterzählen, testen, springen), der auch keine Flags zerstört.

> Das überlass ich dem Compiler :-)

Du vergisst, dass Peter einen schlechten Compiler gewöhnt ist,
bei dem man als Programmierer noch voroptimieren musste. ;-)

> Im Ernst: Einige Optimizer, bei denen ich mir das angesehen habe,
> implementieren derartige Schleifen auch durch runterzählen, wenn die
> Schleifenvariable im Rumpf nicht benutzt wird.
1
#include <stdint.h>
2
3
uint16_t
4
arraysum(uint8_t i, const uint16_t * restrict ary)
5
{
6
        uint16_t sum = 0;
7
#ifdef DOWN
8
        while (i-- != 0)
9
#else
10
        for (uint8_t j = 0; j < i; j++)
11
#endif
12
                sum += *ary++;
13
14
        return sum;
15
}

(mit -std=c99 compilieren, mal mit, mal ohne -DDOWN)

wird vom AVR-GCC zu zwar nicht identischem aber praktisch gleichem
Code compiliert, der sich nur leicht in der Anordnung der Assembler-
anweisungen unterscheidet.

von Karl H. (kbuchegg)


Lesenswert?

gcc entschliesst sich bei
1
int main()
2
{
3
  for( unsigned char i = 0; i < 10; ++i )
4
    Ziffer[i] = '\0';
5
6
  foo( Ziffer );
7
}

das i zu eliminieren und das ganze mittels Z-Pointer und Abfrage auf das 
Ende des Speicherbereichs zu implementieren
1
int main()
2
{
3
  46:  e0 e6         ldi  r30, 0x60  ; 96
4
  48:  f0 e0         ldi  r31, 0x00  ; 0
5
  for( unsigned char i = 0; i < 10; ++i )
6
    Ziffer[i] = '\0';
7
  4a:  11 92         st  Z+, r1
8
{
9
}
10
11
int main()
12
{
13
  for( unsigned char i = 0; i < 10; ++i )
14
  4c:  80 e0         ldi  r24, 0x00  ; 0
15
  4e:  ea 36         cpi  r30, 0x6A  ; 106
16
  50:  f8 07         cpc  r31, r24
17
  52:  d9 f7         brne  .-10       ; 0x4a <main+0x4>
18
    Ziffer[i] = '\0';


Interessant, was er aus
1
int main()
2
{
3
  for( unsigned char i = 10; i; --i )
4
    Ziffer[i] = '\0';

macht:
1
int main()
2
{
3
  46:  ea e6         ldi  r30, 0x6A  ; 106
4
  48:  f0 e0         ldi  r31, 0x00  ; 0
5
  for( unsigned char i = 10; i; --i )
6
    Ziffer[i] = '\0';
7
  4a:  10 82         st  Z, r1
8
  4c:  31 97         sbiw  r30, 0x01  ; 1
9
{
10
}
11
12
int main()
13
{
14
  for( unsigned char i = 10; i; --i )
15
  4e:  80 e0         ldi  r24, 0x00  ; 0
16
  50:  e0 36         cpi  r30, 0x60  ; 96
17
  52:  f8 07         cpc  r31, r24
18
  54:  d1 f7         brne  .-12       ; 0x4a <main+0x4>
19
    Ziffer[i] = '\0';

Die Abfrage auf das Schleifenede durch Adressvergleich ist geblieben, 
aber den  st -Z, r1 dürfte er nicht kennen.

von Karl H. (kbuchegg)


Lesenswert?

Wohingegen er in
1
int main()
2
{
3
  char* P = &Ziffer[0]; 
4
5
  for(unsigned char i = 10;i;i--)  *P++ = 0;
die Absicht offenbar erkannt hat
1
int main()
2
{
3
  46:  e0 e6         ldi  r30, 0x60  ; 96
4
  48:  f0 e0         ldi  r31, 0x00  ; 0
5
  char* P = &Ziffer[0]; 
6
7
  for(unsigned char i = 10;i;i--)  *P++ = 0;
8
  4a:  11 92         st  Z+, r1
9
  4c:  80 e0         ldi  r24, 0x00  ; 0
10
  4e:  ea 36         cpi  r30, 0x6A  ; 106
11
  50:  f8 07         cpc  r31, r24
12
  52:  d9 f7         brne  .-10       ; 0x4a <main+0x4>

identischer Code zum Fall
1
int main()
2
{
3
  for( unsigned char i = 0; i < 10; ++i )
4
    Ziffer[i] = '\0';

von Karl H. (kbuchegg)


Lesenswert?

Karl heinz Buchegger schrieb:

> Interessant, was er aus
>
1
> int main()
2
> {
3
>   for( unsigned char i = 10; i; --i )
4
>     Ziffer[i] = '\0';
5
>
>
> macht:

Ganz abgesehen davon, dass ich da einen schönen Bock geschossen habe. 
Array access out of bounds :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Man sollte als geübter C-Programmierer halt keine Schleifen schreiben,
die rückwärts zählen. :-)

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Ich meine auch irgendwo mal gelesen zu haben das man mit unsigned 
int/char sonstwas komisches dem Compiler keinen Gefallen tut und 
statdessen einfach int für den Schleifenzähler verwenden sollte (der 
Optimizer kann sich dann schon das beste raus suchen für den 
Wertbereich).

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.