Würde mich gern mal mit C befassen

Es gibt 196 Antworten in diesem Thema, welches 21.162 mal aufgerufen wurde. Der letzte Beitrag (24. Mai 2023 um 15:33) ist von Axellander.

  • Wenn ich ernsthaft mit Geldbeträgen rechnen würde (im Sinne einer Bankanwendung), dann würde ich weder int noch float verwenden, sondern BCD, bzw. eine Library die speziell dafür gemacht ist um den Fehlern zu entgehen die durch die Binärdarstellung im Speicher entstehen.

    Ja, wobei BCD genauso numerisch fehlerbehaftet (im Sinne von Rundung und Co.) ist - diese Eigenschaft ist ja unabhängig von der Basis.

    Z.B. geht 1/3 nicht genau mit endlich vielen Nachkommastellen, binär oder dezimal egal. =)

    Aber es geht dann darum, genau so zu runden, wie es in der Finanzwelt üblich ist - eben kaufmännisch und auf Dezimal-Nachkommastellen bezogen.

  • printf("%d\n", sizeof(int)); liefert in sascc, HiSoft und dem Online Compiler 4.

    Das bedeutet, dass ein int vier Byte = 32 Bit gross ist.

    In den Headerfile math.h und float.h sind (unter anderem) auch verschiedene Konstanten definiert. In float.h, z.B. die größte und kleinste float oder double Zahl oder auch das schon erwähnte epsilon, was der kleinsten Änderung entspricht, um die sich floats oder doubles unterscheiden können. In math.h sind eher mathematische Konstanten, wie pi oder e definiert.

    Bitte melde dich an, um dieses Bild zu sehen.
    Bitte melde dich an, um diesen Link zu sehen.

    Einmal editiert, zuletzt von flowerking (28. Februar 2023 um 11:11)

  • Jup. Bei Geldbeträgen würde ich außerhalb von Börsenpapieren und Zinsen auch immer in Integern (Cents) rechnen. Floats würde ich erst anfassen wenn wirklich nötig.

    Wenn ich ernsthaft mit Geldbeträgen rechnen würde (im Sinne einer Bankanwendung), dann würde ich weder int noch float verwenden, sondern BCD, bzw. eine Library die speziell dafür gemacht ist um den Fehlern zu entgehen die durch die Binärdarstellung im Speicher entstehen.

    Ja klar, arbitrary precision wäre da schon gut. Geht aber hier ja ums lernen.

    C64C mit 8580, C64 mit 6581 und C64G mit ARMSID,

    C16, VC20, PET3016+32K, 3x1541, 2x1541-II, Pi1541, Philips CM8833-II, 1084S

    Bitte melde dich an, um diesen Link zu sehen.

  • Hallo,

    ich lerne ja jetzt fleißig C mit dem Tutorial:

    Wie siehts hiermit aus?: Bitte melde dich an, um diesen Link zu sehen.

    Nun bin ich gerade bei dieser Übung, wo bei zwei Strings die unterschiedlichen Zeichen in einem gesonderten String gespeichert werden sollen: Bitte melde dich an, um diesen Link zu sehen. (Musterlösung).

    Hier mal meine Variante:

    Die funktioniert auch, aber meine Frage ist, ist das eher unsauber oder ist das auch üblich?

    Dann bin ich mir nicht sicher, ob die Deklaration von res in main() ok ist (Zeile 11). Wird da Speicher reserviert oder müßte man das selbst erledigen?

  • Unsauber? Das sollte einem eigentlich sofort um die Ohren fliegen.

    result ist nie initialisiert, zeigt nicht auf einen beschreibbaren Speicher, aber es wird reingeschrieben.

    Leider ist das genau die Art Bug, weswegen C verrufen ist. Das wird vermutlich laufen, und irgendwann später fliegt dir irgendetwas eigentlich total unabhängiges auseinander.

    Da wird kein Speicher reserviert, das muss man da selbst erledigen. Und idealerweise groß genug :)

    C64Studio: Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen. --- C64Studio WIP: Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen. --- Bitte melde dich an, um diesen Link zu sehen.

  • Am besten lokal auf eine Linux-Maschine nativ bauen und "Valgrind" drüber laufen lassen. Wenn das meldet, alles ist OK, dann kann man das auf beliebiger Hardware laufen lassen.

  • Leider ist das genau die Art Bug, weswegen C verrufen ist.

    Na ja, "verrufen". :)

    Moderne Compiler warnen ausgiebig bei solchen Dingen (und es gibt Linter für weitere statische Code-Analyse), ansonsten ist C halt mit Absicht wenig pingelig.

    Wird ja klassischerweise als "Makroassembler" bezeichnet.

  • result ist nie initialisiert, zeigt nicht auf einen beschreibbaren Speicher, aber es wird reingeschrieben.

    Ok - deswegen hatte ich ja gefragt. Hätte mich auch gewundert, wenn da automatisch Speicher reserviert würde.

    Ich hab die Funktion stringCompareDif mal abgewandelt - funktioniert auch. Wäre das ok so?

  • Müsste eigentlich, die Anzahl der unterschiedlichen Zeichen plus die 0 sind ja <= 40.

    C64Studio: Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen. --- C64Studio WIP: Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen. --- Bitte melde dich an, um diesen Link zu sehen.

  • Müsste eigentlich, die Anzahl der unterschiedlichen Zeichen plus die 0 sind ja <= 40.

    Ich meinte eher vom Stil her. Sollte man lieber mit Indizierung arbeiten (also str1[i] != str2[i] usw.) oder wie in diesem Beispiel?

  • Am besten lokal auf eine Linux-Maschine nativ bauen und "Valgrind" drüber laufen lassen.

    Warnungen einschalten Bitte melde dich an, um diesen Link zu sehen. wenn der Compiler nicht zu alt ist:

    Code
    <source>: In function 'main':
    <source>:10:5: warning: 'res' is used uninitialized [-Wuninitialized]
       10 | printf("Diffs: %s", stringCompareDif(str1, str2, res));
          | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    <source>:9:11: note: 'res' was declared here
        9 | char *res;
          | ^~~
    Compiler returned: 0

    Bei gcc oder clang würde ich "-Wall -Wextra -Werror" empfehlen.

    Sollte man lieber mit Indizierung arbeiten (also str1[i] != str2[i] usw.) oder wie in diesem Beispiel?

    Pointerarithmetik ist relativ idiomatisch in C, daher kommt aber auch der Ruf als fehleranfällige Programmiersprache. Ein gut optimierender Compiler würde wahrscheinlich für beides den gleichen Code erzeugen.

    10 x=rnd(-1963):fori=1to81:y=rnd(1):next
    20 forj=1to5:printchr$(rnd(1)*16+70);:next
    30 printint(rnd(1)*328)-217

    Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen.

  • Am besten lokal auf eine Linux-Maschine nativ bauen und "Valgrind" drüber laufen lassen.

    Warnungen einschalten Bitte melde dich an, um diesen Link zu sehen. wenn der Compiler nicht zu alt ist:


    Bei gcc oder clang würde ich "-Wall -Wextra -Werror" empfehlen.

    Und vor Valgrind (memcheck) würde ich die Sanitizer, insbesondere -fsanitize=address, benutzen. =)

  • Ich meinte eher vom Stil her. Sollte man lieber mit Indizierung arbeiten (also str1[i] != str2[i] usw.) oder wie in diesem Beispiel?

    Das ist eigentlich egal. Kann man so oder so machen. Allerdings ist ein Index ineffezienter, da der bei jedem Durchlauf berechnet werden muss. Also ist der Pointerincrement besser.

    Was mich persönlich auch stören würde ist, dass du strlen mehrfach aufrufst. Das könnte man auch so machen:

    Code
    int len = strlen(str1);
    int len2 = strlen(str2);
    if (len2 < len)
       len = len2;

    Auf einem modernen Compiler ist das egal weil der Optimizer das von selbst macht. Wenn du auf dem Amiga arbeiten willst und nicht mit gcc crosscompilesd, dann dürften die nativen Compiler das eventuell noch nicht können. In dem Fall schaue ich mir meistens den erzeugten Assemblercode an um zu sehen was der Compiler draus macht.

  • Auf einem modernen Compiler ist das egal weil der Optimizer das von selbst macht. Wenn du auf dem Amiga arbeiten willst und nicht mit gcc crosscompilesd, dann dürften die nativen Compiler das eventuell noch nicht können. In dem Fall schaue ich mir meistens den erzeugten Assemblercode an um zu sehen was der Compiler draus macht.

    Moderne Compiler sind da noch "schlimmer", clang 15 kann die ganze Berechnung Bitte melde dich an, um diesen Link zu sehen. - stringCompareDif ist zwar im Compilat noch drin, wird aber von main aus nicht aufgerufen. Wenn man es als static deklariert lässt der Compiler es ganz weg, ohne kann er nicht sicher sein ob es nicht doch von einer anderen Datei aus aufgerufen wird.

    10 x=rnd(-1963):fori=1to81:y=rnd(1):next
    20 forj=1to5:printchr$(rnd(1)*16+70);:next
    30 printint(rnd(1)*328)-217

    Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen.

  • Code
        char *str1 = "Hallo, das ist jetzt das Original!";

    Da bist Du gerade an einer wichtigen Stelle von C. Über die ich sehr lange nachgegrübelt hab', weil ich dazu lange keine gute Dokumentation finden konnte (einschließlich "K&R", das ich habe).

    "Hallo, das ist jetzt das Original!" ist ein sog. "String literal", das beim Kompilieren in einem Read-Only-Speicherbereich abgelegt wird (weil der Text nunmal "literal" im Source-Code steht und deshalb vom Compiler irgendwo im Speicher abgelegt werden muß). Auf dieses "String literal" im Read-Only-Speicherbereich setzt Du mit "char *str1 = ...;" einen Zeiger. Weiter geschieht nichts. Das heißt, Du hast für den String keinen eigenen Speicher in einem beschreibbaren Bereich reserviert. Solltest Du versuchen, das als normalen String zu betrachten und den String zu ändern (was Du hier nicht getan hast), würde das nicht funktionieren. 'char *str1 = "Hallo";' ist daher gefährlich, wenn man sich dieser Hintergründe nicht bewußt ist.

    Und woher sollte man sie kennen? C erzählt sie einem ja nicht. Das ist das Problem.

    Ich hatte das ganze Thema mal Bitte melde dich an, um diesen Link zu sehen. für mich aufgeschrieben, aber die Seite ist unter Vorbehalt, weil ich leider immer noch kein großer C-Programmierer geworden bin und eigentlich nicht gut genug darin bin, um ein Tutorial darüber zu schreiben. Aber vielleicht hilft's ja trotzdem.

    In Deinem Beispiel hast Du es meiner Meinung nach richtig gemacht, vielleicht bewußt, vielleicht nur zufällig: Speicher für den String in der aufrufenden Funktion reservieren ("char res[40];"), und einen Zeiger aus der aufgerufenen Funktion auf diesen reservierten Speicherbereich zurückgeben ("return p;").

    Viel Glück, vielleicht kommst Du mit C ja weiter als ich.

    Gerade auch die Amiga-spezifische C-Programmierung würde mich auch interessieren, darüber wüßte ich auch gern mehr.

  • Auf dieses "String literal" im Read-Only-Speicherbereich setzt Du mit "char *str1 = ...;" einen Zeiger.

    Es liegt im Ermessen des Compilers, wo er Stringliterale ablegt. Können also auch veränderbar sein.

    In Deinem Beispiel hast Du es meiner Meinung nach richtig gemacht, vielleicht bewußt, vielleicht nur zufällig: Speicher für den String in der aufrufenden Funktion reservieren ("char res[40];"), und einen Zeiger aus der aufgerufenen Funktion auf diesen reservierten Speicherbereich zurückgeben ("return p;").

    Nein, das Stringarray ist eine lokale Variable und belegt somit Speicherplatz, den der Compiler nach dem return wieder anderweitig vergeben kann.

  • Nein, das Stringarray ist eine lokale Variable und belegt somit Speicherplatz, den der Compiler nach dem return wieder anderweitig vergeben kann.

    Das "return" war aber in der aufgerufenen Funktion, der Speicher für das Stringarray wurde dagegen in der aufrufenden Funktion ("main") reserviert. Deshalb bin ich der Meinung, daß er in "main" auch nach diesem "return" aus der untergeordneten Funktion noch vorhanden ist.

  • Es liegt im Ermessen des Compilers, wo er Stringliterale ablegt. Können also auch veränderbar sein.

    Dass diese readonly sind hatte ich auch irgendwo gelesen, deshalb hat's mich gewundert, dass ich die in HiSoft C++ trotzdem verändern kannn.

    Aber wie du schriebst, scheint das Compiler-abhängig zu sein.

  • Dass diese readonly sind hatte ich auch irgendwo gelesen, deshalb hat's mich gewundert, dass ich die in HiSoft C++ trotzdem verändern kannn.

    Genaugenommen ist es "nur" undefiniertes Verhalten solche Stringliterale zu verändern, der Compiler darf da machen was er will - selbst Formatieren der Festplatte beim Compilieren wäre aus Spec-Sicht ok. ;) Auf dem Amiga gibts keinen Speicherschutz, deshalb kann ein Programm da beliebig "rumsauen" und alles verändern, nur das Kickstart-ROM ist da wirklich read-only.

    10 x=rnd(-1963):fori=1to81:y=rnd(1):next
    20 forj=1to5:printchr$(rnd(1)*16+70);:next
    30 printint(rnd(1)*328)-217

    Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen. - Bitte melde dich an, um diesen Link zu sehen.