Hallo Besucher, der Thread wurde 9,8k mal aufgerufen und enthält 23 Antworten

letzter Beitrag von ThomBraxton am

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

  • So, endlich ist der Abend 3 ist fertig.


    http://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ß!

  • Hi,


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


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


    Schönen Gruß, Worf

  • Aufgabe 2:

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

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


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


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

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


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

  • Aufgabe 2:

    Code
    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 http://www.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):

    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.

  • 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):

    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:

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

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


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

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

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

    Code
    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:


    Code
    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 http://www.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 ???

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


    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: http://www.freebsd.org/cgi/man…x%2Fi386+11.0&format=html oder http://www.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:
    http://www.freebsd.org/cgi/man…x%2Fi386+11.0&format=html
    (mit Syntax coloring, aber sperrige Links, die Einstellmöglichkeiten verwirren den Anfänger vielleicht)


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


    http://www.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?

  • http://www.freebsd.org/cgi/man…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 ???

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