Mathias Brodala: (C) Überschüssiges Leerzeichen

Hallo.

Begleitend zu unserem Programmierungs-Lehrfach gibt es mehrere Übungen, welche in den Übungsveranstaltungen oder auch privat absolviert werden können.

Hier nun einmal die Aufgabe und meine Lösung:

/*  
 * Aufgabe 1: Würfel  
 *  
 * Erstellen Sie ein Programm zur Berechnung von Eigenschaften eines Würfels,  
 * das bei gegebener (d.h. in Ihrem Fall einzugebender) Kantenlänge a  
 *   - den Flächenumfang (=6a*a),  
 *   - das Volumen (=a*a*a) und  
 *   - die Länge der Raumdiagonale (=a*sqrt(3))  
 * berechnet und ausgibt.  
 */  
  
#include <stdio.h>  
#include <math.h>  
#include <string.h>  
#include "../input1.h" // für input1F, gibt float-Wert zurück  
  
int main() {  
  
  int i = 0, s = 0;  
  float a = 0;  
  char f[1024];  
  typedef struct {  
    char *type;  
    int value;  
  } calc;  
  
  a = input1F("Kantenlänge des Würfels: ");  
  
  calc c[3]; // Array mit drei Elementen des Typs »calc«  
  
  c[0].type = "Flächenumfang";  
  c[0].value = 6 * a * a;  
  
  c[1].type = "Volumen";  
  c[1].value = a * a * a;  
  
  c[2].type = "Länge der Raumdiagonale";  
  c[2].value = a * sqrt(3);  
  
  int clength = sizeof(c) / sizeof(calc);  
  
  for (i = 0; i < clength; ++i) {  
  
    if (strlen(c[i].type) > s) {  
  
      s = strlen(c[i].type); // Maximale Auffüllungsbreite ermitteln  
    }  
  
  }  
  
  sprintf(f, "%%%ds: %%d\n", s); // Formatstring vorbereiten, z. B. »%24s: %d\n«  
  printf("\n");  
  
  for (i = 0; i < clength; ++i) {  
  
    printf(f, c[i].type, c[i].value);  
  }  
  
}

Einmal abgesehen davon, dass sicher einiges zu verbessern ist, gibt mir die Ausgabe Rätsel auf:

$ ./aufg1
Kantenlänge des Würfels: 4

Flächenumfang: 96
                 Volumen: 64
Länge der Raumdiagonale: 6

Hat jemand eine nachvollziehbare Erklärung für das zusätzliche Leerzeichen in der Zeile „Volumen“?

Einen schönen Sonntag noch.

Gruß, Mathias

--
sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
debian/rules
  1. Hallo Mathias,

    *   - den Flächenumfang (=6a*a),

    den Ausdruck "Flächenumfang" lese ich zum ersten Mal; für mich hieß das bisher einfach "Oberfläche".

    int clength = sizeof(c) / sizeof(calc);

    Tip: Die Anzahl der Elemente in einem Array braucht man öfter mal. Es ist praktisch, sich dafür ein Makro zu definieren und das in einem persönlichen Standard-Include unterzubringen:

    #define ARRAYSIZE(x)  (sizeof(x)/sizeof(x[0]))

    Dann im Programmcode:

    int clength = ARRAYSIZE(c);

    Flächenumfang: 96
                     Volumen: 64
    Länge der Raumdiagonale: 6

    Hat jemand eine nachvollziehbare Erklärung für das zusätzliche Leerzeichen in der Zeile „Volumen“?

    Ja: Mein ewiger Krieg gegen UTF-8. ;-)
    Ich vermute, dein C-Sourcecode ist in UTF-8 gespeichert? In der ersten und dritten Zeile enthalten die Strings je ein Sonderzeichen, das in UTF-8 mit zwei Bytes codiert wird. Davon weiß die Standardfzunktion strlen() aber nichts, sie zählt die zwei Bytes als zwei Zeichen.

    Viel Spaß und einen schönen Abend noch,
     Martin

    --
    Ich stehe eigentlich gern früh auf.
    Außer morgens.
    1. Hallo Martin.

      *   - den Flächenumfang (=6a*a),

      den Ausdruck "Flächenumfang" lese ich zum ersten Mal; für mich hieß das bisher einfach "Oberfläche".

      Ja, ich konnte mir auch keinen Reim darauf machen; bei mir heißt das „Oberflächeninhalt“.

      int clength = sizeof(c) / sizeof(calc);

      Tip: Die Anzahl der Elemente in einem Array braucht man öfter mal. Es ist praktisch, sich dafür ein Makro zu definieren und das in einem persönlichen Standard-Include unterzubringen:

      #define ARRAYSIZE(x)  (sizeof(x)/sizeof(x[0]))

      Dann im Programmcode:

      int clength = ARRAYSIZE(c);

      Gute Idee, danke.

      Flächenumfang: 96
                       Volumen: 64
      Länge der Raumdiagonale: 6

      Hat jemand eine nachvollziehbare Erklärung für das zusätzliche Leerzeichen in der Zeile „Volumen“?

      Ja: Mein ewiger Krieg gegen UTF-8. ;-)
      Ich vermute, dein C-Sourcecode ist in UTF-8 gespeichert?

      Ja, wie alles auf meinem System.

      In der ersten und dritten Zeile enthalten die Strings je ein Sonderzeichen, das in UTF-8 mit zwei Bytes codiert wird. Davon weiß die Standardfzunktion strlen() aber nichts, sie zählt die zwei Bytes als zwei Zeichen.

      Treffend korrekt und nachvollziehbar, danke! Ich werde mich nach Alternativen zu strlen umschauen.

      Viel Spaß und einen schönen Abend noch,

      Danke, ebenfalls.

      Gruß, Mathias

      --
      sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
      debian/rules
      1. Hallo nochmal.

        [strlen] zählt die zwei Bytes als zwei Zeichen.

        Treffend korrekt und nachvollziehbar, danke! Ich werde mich nach Alternativen zu strlen umschauen.

        Was einfacher gesagt als getan ist. Zwar stieß ich bei meiner Suche auf mblen(), doch ist mir die Anwendung mangels guter Anwendungsbeispiele hierbei ein Rätsel.

        Vorschläge und gute Links nehme ich dankend entgegen.

        Einen schönen Sonntag noch.

        Gruß, Mathias

        --
        sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
        debian/rules
        1. echo $begrüßung;

          Was einfacher gesagt als getan ist. Zwar stieß ich bei meiner Suche auf mblen(), doch ist mir die Anwendung mangels guter Anwendungsbeispiele hierbei ein Rätsel.

          Vorschläge und gute Links nehme ich dankend entgegen.

          Wie wär es damit? http://www.google.com/codesearch?q=mblen

          echo "$verabschiedung $name";

          1. Hallo dedlfix.

            Was einfacher gesagt als getan ist. Zwar stieß ich bei meiner Suche auf mblen(), doch ist mir die Anwendung mangels guter Anwendungsbeispiele hierbei ein Rätsel.

            Vorschläge und gute Links nehme ich dankend entgegen.

            Wie wär es damit? http://www.google.com/codesearch?q=mblen

            Konnte mir auch nicht wirklich weiterhelfen, da es sich bei den Suchergebnissen vorrangig um Definitionen der Funktion mblen handelt.

            Doch letztendlich bin ich dann doch über weitere Suchen auf die Lösung gestoßen:

            #include <stdio.h>  
            #include <stdlib.h>  
            #include <locale.h> // Für LC_CTYPE  
              
            int main(void) {  
              
              setlocale(LC_CTYPE, "");  
              
              char a[] = "Ä";  
              char b[] = "B";  
              
              printf("a = %s (%d Bytes)\n", a, mblen(a, MB_CUR_MAX));  
              printf("b = %s (%d Bytes)\n\n", b, mblen(b, MB_CUR_MAX));  
              
              return EXIT_SUCCESS;  
            }
            

            Nun erhalte ich eine vielversprechende Ausgabe:

            $ ./test
            a = Ä (2 Bytes)
            b = B (1 Bytes)

            Nun muss ich das ganze nur noch für längere Zeichenketten hinbekommen …

            Einen schönen Sonntag noch.

            Gruß, Mathias

            --
            sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
            debian/rules
            1. Hallo Mathias,

              char a[] = "Ä";
                char b[] = "B";
              [...]

              a = Ä (2 Bytes)
              b = B (1 Bytes)

              sehr schön - genau dieses Ergebnis hätte dir strlen() auch geliefert.
              Bist du jetzt also wirklich einen Schritt weiter?

              So long,
               Martin

              --
              Die letzten Worte des stotternden Beifahrers:
              Frei... frei... frei... freilich kommt da was!!
              1. Hallo Martin.

                char a[] = "Ä";
                  char b[] = "B";
                [...]

                a = Ä (2 Bytes)
                b = B (1 Bytes)

                sehr schön - genau dieses Ergebnis hätte dir strlen() auch geliefert.

                Womit du natürlich recht hast, doch …

                Bist du jetzt also wirklich einen Schritt weiter?

                … hat mich dies nun auf eine brauchbare Fährte geführt:

                #include <stdio.h>  
                #include <stdlib.h>  
                #include <string.h>  
                #include <locale.h>  
                  
                int main(void) {  
                  
                  int i, ml;  
                  char s[] = "Oberfläche";  
                  setlocale(LC_CTYPE, "");  
                  
                  for (i = 0; s[i] != '\0'; ++i) {  
                  
                    ml = mblen(&s[i], MB_CUR_MAX);  
                    printf("s[%d] = %c (%d Bytes)\n", i, s[i], ml);  
                  }  
                  
                  return EXIT_SUCCESS;  
                }  
                
                

                Ausgabe:

                $ ./test
                s[0] = O (1 Bytes)
                s[1] = b (1 Bytes)
                s[2] = e (1 Bytes)
                s[3] = r (1 Bytes)
                s[4] = f (1 Bytes)
                s[5] = l (1 Bytes)
                s[6] = � (2 Bytes)
                s[7] = � (-1 Bytes)
                s[8] = c (1 Bytes)
                s[9] = h (1 Bytes)
                s[10] = e (1 Bytes)

                Damit werde ich nun sicher eine Möglichkeit finden, die Anzahl der Zeichen in einer Zeichenkette zu ermitteln.

                Einen schönen Sonntag noch.

                Gruß, Mathias

                --
                sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
                debian/rules
                1. n'Abend Mathias,

                  sehr schön - genau dieses Ergebnis hätte dir strlen() auch geliefert.
                  Womit du natürlich recht hast, doch …

                  *fg*

                  char s[] = "Oberfläche";

                  s[0] = O (1 Bytes)
                  s[1] = b (1 Bytes)
                  s[2] = e (1 Bytes)
                  s[3] = r (1 Bytes)
                  s[4] = f (1 Bytes)
                  s[5] = l (1 Bytes)
                  s[6] = ? (2 Bytes)

                  Aha, ich glaube, ich erkenne das System und wie man es ausnutzen kann: Bis hierher waren es alles Zeichen, die in einem Byte codiert werden, und nun kommt eines, das zwei Bytes braucht; mblen() scheint hier nicht nur ein Byte zu betrachten, sondern s[6] und s[7], die zusammen ein UTF8-Zeichen bilden.

                  s[7] = ? (-1 Bytes)

                  Und s[7] allein ist ein Bytewert, der *kein* gültiges UTF8-Zeichen sein kann und wird daher vermutlich mit -1 bezeichnet.

                  s[8] = c (1 Bytes)
                  s[9] = h (1 Bytes)
                  s[10] = e (1 Bytes)

                  Okay, der Rest ist wieder klar. Scheint so, als müsste man zur Ermittlung der Stringlänge in Zeichen, nicht Bytes, den String von Anfang bis Ende durchmarschieren, die Schritte mitzählen, und den Index dabei immer um das Ergebnis von mblen(s[i]) weitersetzen. Die Anzahl der Schritte, nicht der Index, ist dann die Stringlänge.

                  Damit werde ich nun sicher eine Möglichkeit finden, die Anzahl der Zeichen in einer Zeichenkette zu ermitteln.

                  [X] Done.  :-)

                  Na dann erstmal gute Nacht,
                   Martin

                  --
                  Es gibt Dinge, die sind sooo falsch, dass nicht einmal das Gegenteil stimmt.
                  1. Hallo Martin.

                    […] Scheint so, als müsste man zur Ermittlung der Stringlänge in Zeichen, nicht Bytes, den String von Anfang bis Ende durchmarschieren, die Schritte mitzählen, und den Index dabei immer um das Ergebnis von mblen(s[i]) weitersetzen. Die Anzahl der Schritte, nicht der Index, ist dann die Stringlänge.

                    Nicht ganz. Was ist, wenn in Mitten normaler Zeichen ein fehlerhaft kodiertes Zeichen auftritt? Ich habe mich hier entschieden, dieses dennoch bei der Ermittlung der Gesamtlänge zu berücksichtigen, weshalb meine etwas umständlichere Lösung nun wie folgt aussieht:

                    int mstrlen(const char*);  
                    int mstrlen(const char *s) {  
                      
                      int i, k, l = 0, t;  
                      
                      for (i = 0; s[i] != '\0'; ++i) {  
                      
                        t = mblen(&s[i], MB_CUR_MAX);  
                      
                        // Bei ungültigem Byte Länge nicht erhöhen  
                        if (t > -1) {  
                      
                          l += t;  
                        }  
                      
                        // Bei Auftreten eines Multibyte-Zeichens  
                        if (t > 1) {  
                      
                          // Nachfolgende Bytes durchlaufen  
                          for (k = 1; k < t; ++k) {  
                      
                            /* Nachfolgende unvollständige Bytes gehören  
                               zum Multibyte-Zeichen */  
                            if (mblen(&s[i + k], MB_CUR_MAX) == -1) {  
                      
                              --l;  
                            }  
                          }  
                        }  
                      }  
                      
                      return l;  
                    }
                    

                    (Ich hätte hier im Übrigen nie gedacht, dass mir einmal ein Programmablaufplan Schwächen im Algorithmus offenbart.)

                    Beispielhaft angewandt:

                    int main(void) {  
                      
                      char s[] = "Oberfläche←?";  
                      setlocale(LC_CTYPE, "");  
                      
                      printf("s = %s\n", s);  
                      printf("\nstrlen: %d, mstrlen: %d\n", strlen(s), mstrlen(s));  
                      
                      return EXIT_SUCCESS;  
                    }
                    

                    $ ./test
                    s = Oberfläche←𝄞

                    strlen: 18, mstrlen: 12

                    (𝄞 soll laut Wikipedia ein Notenschlüssel sein und war das einzige UTF-8-kodierte Zeichen von 4 Byte Länge, was ich auftreiben konnte.)

                    Was hältst du von meiner Lösung?

                    Einen schönen Montag noch.

                    Gruß, Mathias

                    --
                    sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
                    debian/rules
  2. Hi,

    mir fällt da gerade vom mathematischen Aspekt her noch ein Fauxpas auf:

    *   - die Länge der Raumdiagonale (=a*sqrt(3))

    Die Raumdiagonale ist also in aller Regel nicht ganzzahlig, es sei denn, die Kantenlänge wäre ein Vielfaches von 1/√3.

    typedef struct {
        char *type;
        int value;
      } calc;

    c[2].type = "Länge der Raumdiagonale";
      c[2].value = a * sqrt(3);

    Hier gehen aber die Nachkommastellen der berechneten Raumdiagonalen verloren. Die Multiplikation mit float a ist noch okay, aber die Zuweisung an int x.value erzwingt einen impliziten Typecast float->int.

    Hast du das bewusst in Kauf genommen? Oder einfach übersehen?
    So long,
     Martin

    --
    Wenn man keine Ahnung hat - einfach mal Fresse halten.
      (Dieter Nuhr, deutscher Kabarettist)
    1. Hallo Martin.

      Die Raumdiagonale ist also in aller Regel nicht ganzzahlig, […]

      Hier gehen aber die Nachkommastellen der berechneten Raumdiagonalen verloren. Die Multiplikation mit float a ist noch okay, aber die Zuweisung an int x.value erzwingt einen impliziten Typecast float->int.

      Hast du das bewusst in Kauf genommen? Oder einfach übersehen?

      Es ist noch ein Überbleibsel einer älteren Fassung des Programmes. In Kombination mit %g wurden mir dabei stets nur irrsinnig große Zahlen in wissenschaftlicher Notation ausgegeben, was mir missfiel.

      Merkwürdigerweise bekomme ich nun trotz der Typenänderung von calc.value auf float und des Formatierungsstringes von %d auf %g humane Zahlen. Wer weiß, was da vorher noch quer hing.

      Einen schönen Sonntag noch.

      Gruß, Mathias

      --
      sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
      debian/rules
  3. Hallo nochmal.

    Irgendwie mögen mich die Leerzeichen nicht.

    Meine Ausgabe sieht nun wie folgt aus:

    $ ./aufg1
    Kantenlänge des Würfels: 4

    Oberflächeninhalt: 96
                    Volumen: 64
    Länge der Raumdiagonale: 6.93

    Als Maximalbreite wird korrekterweise der Wert 23 ermittelt. Und dennoch fehlt bei „Oberflächeninhalt“ ein Leerzeichen, ungeachtet dessen, welchen Index in „c“ es hat. (Der Formatstring ist natürlich auch bei allen dreien identisch.)

    Für eine Erleuchtung wäre ich auch hier sehr dankbar; meine Kontrollausgaben haben nichts Auffälliges ergeben.

    Einen schönen Samstag noch.

    Gruß, Mathias

    --
    sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
    debian/rules
    1. Hallo Mathias,

      Als Maximalbreite wird korrekterweise der Wert 23 ermittelt. Und dennoch fehlt bei „Oberflächeninhalt“ ein Leerzeichen, ungeachtet dessen, welchen Index in „c“ es hat. (Der Formatstring ist natürlich auch bei allen dreien identisch.)

      Tjor, *printf hat das gleiche Problem, wie strlen() - wenn Du %23d machst, dann füllt er es auf 23 Zeichen auf - ein Zeichen ist für *printf() jedoch immer ein Oktett, weswegen printf() denkt, der String »Oberflächeninhalt« müsste mit einem Zeichen weniger gepaddet werden, als er es eigentlich müsste. "Länge der Raumdiagonale" hat dagegen in den Augen von *printf() 25 Zeichen (statt 23), weswegen printf() nichts mehr paddet, weil der String sowieso schon zu lang ist, deswegen tritt dort das Problem nicht auf.

      Du musst Dir das Padding wohl selbst zusammenbasteln, damit das wie gewünscht funktioniert.

      Viele Grüße,
      Christian

      --
      "I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone." - Bjarne Stroustrup
      1. Hallo Christian.

        Als Maximalbreite wird korrekterweise der Wert 23 ermittelt. Und dennoch fehlt bei „Oberflächeninhalt“ ein Leerzeichen, ungeachtet dessen, welchen Index in „c“ es hat. (Der Formatstring ist natürlich auch bei allen dreien identisch.)

        Tjor, *printf hat das gleiche Problem, wie strlen() […]

        Ärgerlich, dass C offenbar nicht wirklich multibyte-fähig ist.

        Du musst Dir das Padding wohl selbst zusammenbasteln, damit das wie gewünscht funktioniert.

        Ist zwar dann nicht mehr sonderlich schön, bringt aber wohl eher die von mir gewünschte Funktionalität.

        Danke für die Aufklärung und einen schönen Sonntag noch.

        Gruß, Mathias

        --
        sh:( fo:} ch:? rl:( br: n4:~ ie:{ mo:| va:) de:> zu:} fl:( ss:) ls:[ js:|
        debian/rules