C-Kurs, Abend 3 - Lösungen und Fragen

    • C-Kurs, Abend 3 - Lösungen und Fragen

      So, endlich ist der Abend 3 ist fertig.

      skoe.de/wiki/doku.php?id=ckurs:03-abend3

      Ist eine ganze Menge geworden, aber weil wir ja auch nicht sooo schnell sind, kann man's ja auch auf zwei Abende verteilen :)

      Vielen Dank auch an ogd, der auch große Teile verfasst und wieder korrigiert hat.

      Fragen und die Lösungen zu den Aufgaben könnt ihr hier posten und diskutieren. Wie viele Dinge kann man auch C nur durch Praxis lernen. Wenn was nicht klappt, einfach fragen.

      Für Verbesserungsvorschläge mach ich wieder einen extra Thread auf.

      Viel Spaß!
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      Bau Dir ein eigenes Modul! EasyFlash
    • Hi,

      damit das hier mal weiter geht, poste ich einfach mal meine Lösung zu Aufgabe 3:

      Quellcode

      1. #include <stdio.h>
      2. /******************************************************************************/
      3. /*
      4. * Hauptprogramm
      5. */
      6. int main(void)
      7. {
      8. unsigned long zahl;
      9. /* Mit 1 anfangen, dann immer verdreifachen bis unter 60000 */
      10. for (zahl = 1lu; zahl < 60000lu; zahl = zahl * 3lu)
      11. {
      12. printf("%lu\n", zahl);
      13. }
      14. }
      Alles anzeigen

      Vorher hatten wir einen klassischen Überlauf, der mit einem größeren Typ umgangen wird.

      Schönen Gruß, Worf
    • Richtiges Progamm und richtige Begründung. :zustimm:

      Du hast sicher schon gesehen, dass Teil 4 online ist. Wahrscheinlich noch nicht das, auf was Du wartest, aber ein paar C64/cc65-spezifische Sachen sind schon drin.

      Gruß,

      Thomas
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      Bau Dir ein eigenes Modul! EasyFlash
    • Aufgabe 2:

      Quellcode

      1. include<stdio.h>
      2. int main(void)
      3. {
      4. unsigned char a=2, b=0, c;
      5. c=a/b;
      6. printf("%d", c);
      7. };


      Ich finde es hierbei erstaunlich, dass beim Ausführen der Wert 255 (also der maximal darstellbare "unsigned char") ausgegeben wird, und keine Fehlermeldung, wie etwa bei BASIC. Wie ist das zu erklären ???
      Teilnahme geplant:
      24.02. - 26.02. BCC#11 Party - Berlin
      15.09. - 17.09. DoReCo#54 Party - Anröchte
    • helmutx schrieb:

      Ich finde es hierbei erstaunlich, dass beim Ausführen der Wert 255 (also der maximal darstellbare "unsigned char") ausgegeben wird, und keine Fehlermeldung, wie etwa bei BASIC. Wie ist das zu erklären ???

      wenn man es positiv ausdrücken möchte, könnte man sagen, dass c den programmierer nicht so sehr bevormundet wie basic.

      die kehrseite dieser freiheit ist aber, dass die verantwortung bei dir liegt, ob das ganze sinn macht ...
    • Hallo helmutx,

      schön, dass du viel mit C rumspielst. Wollte ich in dem anderen Thread auch schon schreiben. Nur so kann man's wirklich kapieren.

      Das Verhalten bei Division durch 0 ist in C nicht genau festgelegt. Das hat man wahrscheinlich so gemacht, dass der Code schnellstmöglich in Assemblercode der jeweiligen CPU übersetzt werden kann. Hätte man das Verhalten definiert, müssten manche CPU zur Laufzeit zusätzliche Checks machen => langsamer.

      Viele Sachen sind in C so festgelegt (oder eben nicht festgelegt), dass sie sich simpel auch Assembler abbilden lassen.

      z.B. auf x86 (Windows, Linux) stürzt das Program ab.
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      Bau Dir ein eigenes Modul! EasyFlash
    • ogd schrieb:


      wenn man es positiv ausdrücken möchte, könnte man sagen, dass c den programmierer nicht so sehr bevormundet wie basic.

      die kehrseite dieser freiheit ist aber, dass die verantwortung bei dir liegt, ob das ganze sinn macht ...


      Ne Fehlermeldung ist manchmal schon praktisch, um die Sachen zu finden, die keinen Sinn machen.
      Andererseits ist's sicherlich eh sinnvoll, an solchen Stellen, vorher im Code selbst auf Null prüfen zu lassen, um eine Division durch Null zu verhindern....

      skoe schrieb:

      Hallo helmutx,

      schön, dass du viel mit C rumspielst. Wollte ich in dem anderen Thread auch schon schreiben. Nur so kann man's wirklich kapieren.

      Du hast die Aufgabe gestellt, aber es wurde bisher noch keine "Lösung"/ keine Probleme gepostet...

      Ich probiere mal diesem Kurs zu folgen, habe allerdings kaum Programmiererfahrung, kenne nur die Regeln (Befehle) in BASIC und ASM. Assembler war und ist mir aber zu kryptisch.... In C lässt es sich ja scheinbar schön übersichtlich programmieren, vielleicht klappts ja damit...
      Teilnahme geplant:
      24.02. - 26.02. BCC#11 Party - Berlin
      15.09. - 17.09. DoReCo#54 Party - Anröchte
    • helmutx schrieb:

      Ne Fehlermeldung ist manchmal schon praktisch, um die Sachen zu finden, die keinen Sinn machen. Andererseits ist's sicherlich eh sinnvoll, an solchen Stellen, vorher im Code selbst auf Null prüfen zu lassen, um eine Division durch Null zu verhindern....

      ich muss sagen, dass du immer das wesentliche von c erkennst. bleib weiter am ball ...

      helmutx schrieb:

      Ich probiere mal diesem Kurs zu folgen, habe allerdings kaum Programmiererfahrung, kenne nur die Regeln (Befehle) in BASIC und ASM. Assembler war und ist mir aber zu kryptisch.... In C lässt es sich ja scheinbar schön übersichtlich programmieren, vielleicht klappts ja damit...

      c versucht ja das beste aus beiden welten zu vereinen: die schnelligkeit von assembler und die lesbarkeit von basic ...
    • helmutx schrieb:

      Aufgabe 2:

      Quellcode

      1. #include <stdio.h>
      2. int main(void) {
      3. unsigned char a=2, b=0, c;
      4. c=a/b;
      5. printf("%d", c);
      6. };
      Ich finde es hierbei erstaunlich, dass beim Ausführen der Wert 255 (also der maximal darstellbare "unsigned char") ausgegeben wird, und keine Fehlermeldung, wie etwa bei BASIC. Wie ist das zu erklären???
      Wenn Du es ganz genau wissen möchtest: Die Implementierung der Division in Assembler findet sich in libsrc/runtime/udiv.s in den Quellen vom cc65. Dort verweist Uz auf ffd2.com/fridge/math/mult-div.s als Quelle der Implementierung. Das ist eine Routine für 16-Bit-Zahlen.

      Damit es einfacher zu verstehen ist, habe ich es mal in eine Routine für 8-Bit-Zahlen umgesetzt (die reale Routine ist noch ein bisschen komplizierter, da sie außerdem noch die gleichen Speicherstellen für a und c verwendet):

      Quellcode

      1. ; a/b -> c, Rest im Akkumulator
      2. div8 LDA #$00 ; Akkumulator := 0
      3. STA c ; Ergebnis := 0
      4. LDY #$08 ; Schleife acht mal ausführen
      5. - ASL c ; Ergebnis := Ergebnis * 2
      6. ASL a ; Dividend a Bit-weise in
      7. ROL ; Akkumulator schieben
      8. CMP b ; größer als Divisor b?
      9. BCC + ; Überspringe wenn nein
      10. SBC b ; b abziehen
      11. INC c ; Ergebnis um eins erhöhen
      12. + DEY
      13. BNE -
      14. RTS
      Alles anzeigen
      Diese Routine macht im Prinzip nichts anderes als eine schriftliche Division: Man guckt sich den Dividenden von vorne beginnend an (entspricht dem von rechts nach links in den Akkumulator Schieben). Sobald der betrachtete Teil größer als der Divisor ist, teilt man und schreibt eine Ziffer des Ergebnisses. Bei Binär-Zahlen kann diese Ziffer nur 0 oder 1 sein. Dann wird das durch die Ziffer erreichte abgezogen und weitere Ziffern des Dividenden „nach unten geholt“ usw. usf.

      Beim Teilen durch 0 ist nun in jedem Durchgang der Akkumulator (das betrachtete Anfangsstück) größer gleich der 0. Es wird also im Ergebnis jedes Bit gesetzt. Und am Ende ist immer noch die ganze Zahl als Rest im Akkumulator, weil ja jedes Mal nichts abgezogen wurde.

      Das ganze macht also nicht wirklich Ärger beim Teilen durch 0. Wenn Du einen Fehler haben möchtest, musst Du, wie gesagt, selbst überprüfen. Ansonsten kommt die größtmögliche Zahl und als Rest die ursprünglich reingesteckte Zahl raus.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von heptasean ()

    • heptasean schrieb:

      Damit es einfacher zu verstehen ist, habe ich es mal in eine Routine für 8-Bit-Zahlen umgesetzt (die reale Routine ist noch ein bisschen komplizierter, da sie außerdem noch die gleichen Speicherstellen für a und c verwendet):

      Quellcode

      1. ; a/b -> c, Rest im Akkumulator
      2. div8 LDA #$00 ; Akkumulator := 0
      3. STA c ; Ergebnis := 0
      4. LDY #$08 ; Schleife acht mal ausführen
      5. - ASL c ; Ergebnis := Ergebnis * 2
      6. ASL a ; Dividend a Bit-weise in
      7. ROL ; Akkumulator schieben
      8. CMP b ; größer als Divisor b?
      9. BCC + ; Überspringe wenn nein
      10. SBC b ; b abziehen
      11. INC c ; Ergebnis um eins erhöhen
      12. + DEY
      13. BNE -
      14. RTS
      Alles anzeigen
      Diese Routine macht im Prinzip nichts anderes als eine schriftliche Division: Man guckt sich den Dividenden von vorne beginnend an (entspricht dem von rechts nach links in den Akkumulator Schieben). Sobald der betrachtete Teil größer als der Divisor ist, teilt man und schreibt eine Ziffer des Ergebnisses. Bei Binär-Zahlen kann diese Ziffer nur 0 oder 1 sein. Dann wird das durch die Ziffer erreichte abgezogen und weitere Ziffern des Dividenden „nach unten geholt“ usw. usf.

      Danke für die Erklärung. Habe jetzt zwar Stunden gebraucht, die ASM-Routine zu verstehen (wie gesagt, ASM ist mir zu kryptisch), habe sie aber verstanden :roll2: , und jetzt ist's auch ganz logisch.
      Aber es müsste doch eigentlich in der Kommentierung heißen:

      Quellcode

      1. CMP b ; kleiner als Divisor b?
      2. BCC + ; Überspringe wenn ja

      Oder ??? Denn wenn beide gleich sind (AKKU also nicht größer als b) ist das Carry-Flag doch nicht gelöscht, und es wird nicht nach "+" verzweigt. ?(
      Teilnahme geplant:
      24.02. - 26.02. BCC#11 Party - Berlin
      15.09. - 17.09. DoReCo#54 Party - Anröchte
    • helmutx schrieb:

      Danke für die Erklärung. Habe jetzt zwar Stunden gebraucht, die ASM-Routine zu verstehen (wie gesagt, ASM ist mir zu kryptisch), habe sie aber verstanden :roll2: , und jetzt ist's auch ganz logisch.
      Glückwunsch! Hat mich halt auch interessiert, warum fast kein Problem mit Division durch 0 entsteht. (Der völlig naive Algorithmus „Ziehe so lange wie möglich b ab und zähle in c.“ hätte ja doch ein deftiges Problem mit b=0.)

      Das finde ich das schöne an C auf dem CeVi: Man kann, wenn man gerade möchte, schon noch nachvollziehen, wie das ganze bis aufs Bit funktioniert. Auf modernen PCs ist das halt doch eher 'ne Lebensaufgabe, bis zur letzten Kernel-Routine vorzudringen.

      helmutx schrieb:

      Aber es müsste doch eigentlich in der Kommentierung heißen:

      Quellcode

      1. CMP b ; kleiner als Divisor b?
      2. BCC + ; Überspringe wenn ja

      Oder ??? Denn wenn beide gleich sind (AKKU also nicht größer als b) ist das Carry-Flag doch nicht gelöscht, und es wird nicht nach "+" verzweigt.?
      Ja, genau oder halt „größer oder gleich“ statt nur „größer“.
    • helmutx schrieb:

      Aber es müsste doch eigentlich in der Kommentierung heißen:

      Quellcode

      1. CMP b ; kleiner als Divisor b?
      2. BCC + ; Überspringe wenn ja

      Oder ??? Denn wenn beide gleich sind (AKKU also nicht größer als b) ist das Carry-Flag doch nicht gelöscht, und es wird nicht nach "+" verzweigt. ?(

      ich sage mal ganz spontan: du hast recht : )

      als gedächnisstütze definieren ich immer diese beiden macros:

      Quellcode

      1. .macro blt arg
      2. bcc arg
      3. .endmacro
      4. .macro bge arg
      5. bcs arg
      6. .endmacro
    • Aufgabe 3

      in der Lösung von Worf heißt es:
      printf("%lu\n", zahl);

      ich hatte zunächst:
      printf("%ul\n", zahl);

      ,was nicht funktioniert hat. Warum ???

      Ich wollte mal auf der empfohlenen URL rt.com/man/printf.3.html nachschauen, ob ich da den Grund finde. Leider wird die Seite nicht richtig dargestellt (s.Bild).
      Weiß evtl. jemand, wie ich die Seite mit dem IE richtig dargestellt bekommen ???
      Bilder
      • Darstellungsfehler IE.JPG

        75,88 kB, 510×468, 11 mal angesehen
      Teilnahme geplant:
      24.02. - 26.02. BCC#11 Party - Berlin
      15.09. - 17.09. DoReCo#54 Party - Anröchte
    • helmutx schrieb:

      in der Lösung von Worf heißt es:
      printf("%lu\n", zahl);

      ich hatte zunächst:
      printf("%ul\n", zahl);

      was nicht funktioniert hat. Warum ???
      l ist ein „length modifier“, der sich auf die folgende „integer conversion“ bezieht, muss also vor dem u stehen, auf das es sich bezieht.

      helmutx schrieb:

      Leider wird die Seite nicht richtig dargestellt (s.Bild).
      Weiß evtl. jemand, wie ich die Seite mit dem IE richtig dargestellt bekommen ???
      Sieht bei mir (Opera unter Linux) auch kaputt aus. Die haben wahrscheinlich das verwendete Charset nicht richtig kodiert. Außerdem ist die dargestellte Manpage auch ein bisschen alt.

      Du könntest mal folgendes versuchen: freebsd.org/cgi/man.cgi?query=…x%2Fi386+11.0&format=html oder linuxmanpages.com/man3/printf.3.php (Das FreeBSD-Ding hat den Vorteil, dass man sich die Manpages unterschiedlichster Distributionen angucken kann.)

      Aber bitte beachten: Das ist jeweils die Dokumentation zu den „echten“ Funktionen von Linux. Die cc65-Implementierungen müssen nicht unbedingt alles und genau so implementieren.
    • Mhh, ist mir beim schnellen rüberschauen damals gar nicht aufgefallen. Ich wollte ja die Links zu den man pages sowieso noch überarbeiten. Jetzt habe ich endlich einen richtigen Grund dafür. Gerne würde ich konsistenterweise immer die gleiche Site verlinken.

      Der Link zu FreeBSD gefällt mir gut. Die Links sind wegen der Parameter ziemlich sperrig. Außerdem ist bei einer solchen Engine auf eine (größere?) Gefahr da, dass die Links irgendwann nicht mehr funktionieren, weil die was dran rumgeschraubt haben. Die Gefahr ist bei anderen Seiten natürlich auch da.

      Also, das sind die Kandidaten:
      freebsd.org/cgi/man.cgi?query=…x%2Fi386+11.0&format=html
      (mit Syntax coloring, aber sperrige Links, die Einstellmöglichkeiten verwirren den Anfänger vielleicht)

      linuxmanpages.com/man3/printf.3.php
      linux.die.net/man/3/printf
      (handlich, aber mit dickem Werbebanner)

      rt.com/man/printf.3.html
      (handlich, aber Kodierung fehlerhaft)

      Ich hatte sogar mal eine deutschsprachige verlinkt, die im Fall von printf schlicht und ergreifend inhaltlich falsch übersetzt war.

      Welche gefällt Euch am besten?
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      Bau Dir ein eigenes Modul! EasyFlash
    • skoe schrieb:

      freebsd.org/cgi/man.cgi?query=…x%2Fi386+11.0&format=html
      (mit Syntax coloring, aber sperrige Links, die Einstellmöglichkeiten verwirren den Anfänger vielleicht)

      Ich finde diese "colorisierte" Seite schön übersichtlich. Die Man-Pages verwirren einen Anfänger sowieso, aber da beiße ich mich schon durch. :)
      Worin liegt denn genau das Problem mit den sperrigen Links ???
      Teilnahme geplant:
      24.02. - 26.02. BCC#11 Party - Berlin
      15.09. - 17.09. DoReCo#54 Party - Anröchte
    • Übrigens,
      Aufgabe 4:

      Quellcode

      1. #include <stdio.h>
      2. int main(void)
      3. {
      4. unsigned char x, y;
      5. for(y=1; y<=9; y=y+1)
      6. {
      7. for(x=1; x<=9; x=x+1)
      8. {
      9. printf("%2d * %2d = %2d\n", x, y, x*y);
      10. };
      11. printf("\n");
      12. };
      13. };
      Alles anzeigen
      Teilnahme geplant:
      24.02. - 26.02. BCC#11 Party - Berlin
      15.09. - 17.09. DoReCo#54 Party - Anröchte
    • ogd schrieb:

      nur die semikola nach den geschweiften klammern sind überflüssig ...

      Kann es denn dait Probleme geben ???
      Ist mit Semikola irgendwie übersichtlicher, da dadurch der "for" Befehl (bzw der Block) irgendwie Abgeschlossen wird...
      Teilnahme geplant:
      24.02. - 26.02. BCC#11 Party - Berlin
      15.09. - 17.09. DoReCo#54 Party - Anröchte