Ada: Zahlenformatierung in C

Hallo,

wenn ich in C eine Zahl für die Ausgabe formatieren möchte, printf dafür aber nicht ausreicht, wie würde ich die Speicherverwaltung für eine selbstgeschriebene Funktion vernünftigerweise anlegen?

1. Ich kann z.B. intern static char* benutzen und meine Funktion würde immer nur in diesen Bereich schreiben. Hat allerdings den Nachteil, daß ich sowas hier nicht machen kann

  
printf("x1 = %s, x2 = %s", myfunc(x1), myfunc(x2));  

weil der Speicher durch den zweiten Funktionsaufruf direkt überschrieben wird.

2. Ich kann andererseits in meiner Funktion malloc verwenden und einen char** als Parameter erwarten, mit dem nachher noch free aufgerufen werden muß. Dazu muß ich aber für jede Zahl, die ich formatieren möchte, meine Funktion mit einem neuen Pointerparameter aufrufen bzw. den Speicher aus vorigen Aufrufen "free"en, bevor ich einen neuen Aufruf mache.

3. Ich kann einfach auf den Parameter aus 2. verzichten und das Speicherleck ignorieren. Macht natürlich überhaupt keinen Spaß, wenn die Ausgabeformatierung diverse Male im Programm aufgerufen wird.

Bisher verwende ich 2., bin mir aber noch nicht sicher, daß das bereits der Weisheit letzter Schluß ist. Was könnt Ihr an Alternativen empfehlen?

Grüße
Ada

  1. Hi,

    wenn ich in C eine Zahl für die Ausgabe formatieren möchte, printf dafür aber nicht ausreicht, wie würde ich die Speicherverwaltung für eine selbstgeschriebene Funktion vernünftigerweise anlegen?

    1. Ich kann z.B. intern static char* benutzen ...
    2. Ich kann andererseits in meiner Funktion malloc verwenden und einen char** als Parameter erwarten, ...
    3. Ich kann einfach auf den Parameter aus 2. verzichten und das Speicherleck ignorieren.

    4. Du kannst die Formatierungsfunktion um einen Parameter vom Typ char* erweitern, so dass die aufrufende Funktion selbst einen ausreichend großen Speicherplatz zur Verfügung stellen muss. Wenn die maximale Länge des formatierten Strings nicht mit Sicherheit vorhersagbar ist, dann zusätzlich einen Parameter vom Typ int, der der Formatierungsfunktion angibt, wie groß der zur Verfügung stehende Puffer ist.

    Das ist ja im Prinzip auch das Verfahren, das sprintf() anwendet - nur dass sprintf() eben *nicht* überprüft, ob der Puffer ausreichend groß ist.

    So long,
     Martin

    --
    Die letzten Worte des Polizisten:
    Ich hab mitgezählt, Leute: Sechs Schuss, jetzt hat er keine Munition mehr!
    Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
    1. Hallo Martin,

      Wenn die maximale Länge des formatierten Strings nicht mit Sicherheit vorhersagbar ist, dann zusätzlich einen Parameter vom Typ int, der der Formatierungsfunktion angibt, wie groß der zur Verfügung stehende Puffer ist.

      Der korrekte Typ für eine Puffergröße wäre size_t anstelle von int. Oder ssize_t wenn man auch negative Werte für Fehlercodes o.ä. braucht.

      Das ist ja im Prinzip auch das Verfahren, das sprintf() anwendet - nur dass sprintf() eben *nicht* überprüft, ob der Puffer ausreichend groß ist.

      Weswegen es im aktuellen C-Standard auch snprintf() gibt, was die länge kontrolliert. Insofern sollte man inzwischen eigentlich immer snprintf() verwenden außer es kommt extrem auf die Performance an und man hat anderweitig sichergestellt, dass der Puffer in dem konkreten Fall immer ausreicht.

      Viele Grüße,
      Christian

      1. Hi,

        Wenn die maximale Länge des formatierten Strings nicht mit Sicherheit vorhersagbar ist, dann zusätzlich einen Parameter vom Typ int, der der Formatierungsfunktion angibt, wie groß der zur Verfügung stehende Puffer ist.
        Der korrekte Typ für eine Puffergröße wäre size_t anstelle von int.

        klar, aber int ist der Basistyp für ganzzahlige Werte. Die Größe eines Speicherbereichs in Byte *ist* ein ganzzahliger Wert. Deswegen ist size_t ja identisch zu int deklariert. Allein schon das Bestreben, hier einen eigenen Typ zu definieren, ist in meinen Augen eine unnötige Verkomplizierung.
        In der Gemüseabteilung im Supermarkt unterscheide ich ja auch nicht zwischen Tomaten für Salat, Tomaten für Auflauf und Tomaten für kalte Platten, sondern ich will einfach nur Tomaten.

        Oder ssize_t wenn man auch negative Werte für Fehlercodes o.ä. braucht.

        Das wäre mit int bereits abgedeckt.

        Das ist ja im Prinzip auch das Verfahren, das sprintf() anwendet - nur dass sprintf() eben *nicht* überprüft, ob der Puffer ausreichend groß ist.
        Weswegen es im aktuellen C-Standard auch snprintf() gibt

        Schon mal davon gehört, ist allerdings immer noch nicht in meinen aktiven Wortschatz eingedrungen.

        Ciao,
         Martin

        --
        Elefant zum Kamel: "Sag mal, wieso hast du denn den Busen auf dem Rücken?"
        Kamel:             "Ziemlich freche Frage für einen, der den Penis im Gesicht hat."
        Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
        1. Hallo Martin,

          Der korrekte Typ für eine Puffergröße wäre size_t anstelle von int.

          klar, aber int ist der Basistyp für ganzzahlige Werte. Die Größe eines Speicherbereichs in Byte *ist* ein ganzzahliger Wert. Deswegen ist size_t ja identisch zu int deklariert.

          Nein, ist es nicht. Auf bestimmten Systemen stimmt das im Spezialfall, aber nimm zum Beispiel Linux oder Windows auf x86_64 (64 bit) und da ist size_t äquivalent zu einer 64bit-Ganzzahl (unter Linux/x86_64 wäre das also äquivalent zu long, unter Windows/x86_64 äquivalent zu long long) während int auf diesen Systemen weiterhin eine 32bit-Ganzzahl ist.

          Und diese Beispiele sind nicht irgendwie exotisch. Wenn Du Dir einen aktuellen Computer kaufst, ist da in der Regel nur noch 64bit-Windows dabei. Da läuft zwar auch noch 32bit-Code drauf, aber langfristig geht die Entwicklung weg davon.

          Allein schon das Bestreben, hier einen eigenen Typ zu definieren, ist in meinen Augen eine unnötige Verkomplizierung.

          Es geht hier um Portabilität: Der Typ size_t ist laut Standard so festgelegt, dass das *immer* für Puffergrößen u.ä. verwendet werden kann, während z.B. ptrdiff_t *immer* für Differenzen zwischen zwei Pointern verwendet werden kann. Genauso gibt's off_t für Positionen in Dateien (was z.B. auch auf einem 32bit-OS eine 64bit-Größe sein kann weil Dateien ja > 4 Gig sein können) und so weiter. Wenn man diese Typen an den korrekten Stellen verwendet, stellt man sicher, dass man den Code überall dort verwenden kann, wo es eine standardkonforme C-Implementierung gibt.

          Außerdem haben diese speziellen Typen in meinen Augen einen weiteren wichtigen Vorteil: Sie geben klar zu erkennen, was man eigentlich für eine Art Variable an der Stelle wollte. Wenn ich irgendwo eine Variable des Typs "int" sehe, dann kann das alles mögliche sein. Bei size_t weiß ich aber, dass es sich um eine Puffergröße handeln soll, etc. Damit erkenne ich leichter an Hand des Typs, welche Annahmen ein bestimmtes Stück Code macht.

          Viele Grüße,
          Christian

          1. Hallo,

            Die Größe eines Speicherbereichs in Byte *ist* ein ganzzahliger Wert. Deswegen ist size_t ja identisch zu int deklariert.
            Nein, ist es nicht. Auf bestimmten Systemen stimmt das im Spezialfall

            ja, stimmt. Nämlich dann, wenn Daten- und Adressbus die gleiche Breite haben.

            aber nimm zum Beispiel Linux oder Windows auf x86_64 (64 bit) und da ist size_t äquivalent zu einer 64bit-Ganzzahl (unter Linux/x86_64 wäre das also äquivalent zu long, unter Windows/x86_64 äquivalent zu long long) während int auf diesen Systemen weiterhin eine 32bit-Ganzzahl ist.

            Dann wären 64bit-Windows oder 64bit-Linux Mogelpackungen - denn die "Bittigkeit" leitet sich ja von der Datenbusbreite ab, und int soll die "natürliche" Datenbreite eines Systems wiedergeben. x86-64 sind echte 64bit-Prozessoren mit einem 64bit-Datenbus und 64bit-Registern. Wenn ein Compiler für diese Prozessoren int mit nur 32bit definiert, würde man sie de facto wieder auf 32bit-Systeme degradieren (ich hatte noch nie einen 64bit-Compiler in den Fingern und kann das nicht nachprüfen).

            Allein schon das Bestreben, hier einen eigenen Typ zu definieren, ist in meinen Augen eine unnötige Verkomplizierung.
            Es geht hier um Portabilität:

            Dass wir hier grundsätzlich unterschiedliche Ansichten haben, ist ja bekannt.

            Der Typ size_t ist laut Standard so festgelegt, dass das *immer* für Puffergrößen u.ä. verwendet werden kann, während z.B. ptrdiff_t *immer* für Differenzen zwischen zwei Pointern verwendet werden kann. Genauso gibt's off_t für Positionen in Dateien (was z.B. auch auf einem 32bit-OS eine 64bit-Größe sein kann weil Dateien ja > 4 Gig sein können) und so weiter. Wenn man diese Typen an den korrekten Stellen verwendet, stellt man sicher, dass man den Code überall dort verwenden kann, wo es eine standardkonforme C-Implementierung gibt.

            Das heißt aber auch, dass derselbe Code, wenn er auf unterschiedlichen Plattformen compiliert wird, auch unterschiedliche Datenstrukturen erzeugt. Das ist nicht das, was ich unter Portabilität verstehe.

            So long,
             Martin

            --
            Wenn der Computer wirklich alles kann,
            dann kann er mich mal kreuzweise.
            Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
            1. Hallo,

              aber nimm zum Beispiel Linux oder Windows auf x86_64 (64 bit) und da ist size_t äquivalent zu einer 64bit-Ganzzahl (unter Linux/x86_64 wäre das also äquivalent zu long, unter Windows/x86_64 äquivalent zu long long) während int auf diesen Systemen weiterhin eine 32bit-Ganzzahl ist.

              Dann wären 64bit-Windows oder 64bit-Linux Mogelpackungen - denn die "Bittigkeit" leitet sich ja von der Datenbusbreite ab, und int soll die "natürliche" Datenbreite eines Systems wiedergeben.

              Nein. Woher hast Du das? Der C-Standard definiert lediglich, dass int mindestens soundsogroß ist (ich glaube 16 bit, aber ich kann mich auch täuschen), mehr nicht.

              Es gibt ja folgende Integer-Datentypen in C: char, short, int, long, long long. Wenn Du nun ein 32bit-System hast, dann wird das typischerwiese so gemappt:

              8bit: char
                16bit: short
                32bit: int, long
                64bit: long long (wenn verfügbar)

              Folgende Mappings hast Du nun unter x86_64 unter Linux und Windows:

              Linux                      Windows
                 8bit: char                       char
                16bit: short                      short
                32bit: int                        int, long
                64bit: long, long long            long long

              Wenn Du nun vorschlägst, int zu einem 64bit-Datentyp zu machen, was ist dann Dein 32bit-Datentyp?

              Wenn ein Compiler für diese Prozessoren int mit nur 32bit definiert, würde man sie de facto wieder auf 32bit-Systeme degradieren

              Nein, würde man nicht, es gibt ja auch noch long bzw. long long. Der C-Standard sagt wie gesagt nur etwas über die Mindestgrößen eines Datentyps aus und nicht über die Relation zur Architektur aus.

              Im aktuellen C-Standard gibt's zusätzlich den Typen intmax_t (bzw. uintmax_t für unsigned), der garantiert der größte int ist. Auf einem aktuellen x86_64-System wäre das also ein 64bit-Integer, ebenso aber auch auf einem aktuellen x86-System, weil's dort 64bit-Befehle im Prozessor gibt, die einfach jeweils 2 32bit-Register auf einmal nehmen. Wenn man dagegen einen Microprozessor nimmt, dann kann intmax_t aber auch gerne mal 16bit groß sein.

              Der Typ size_t ist laut Standard so festgelegt, dass das *immer* für Puffergrößen u.ä. verwendet werden kann, während z.B. ptrdiff_t *immer* für Differenzen zwischen zwei Pointern verwendet werden kann. Genauso gibt's off_t für Positionen in Dateien (was z.B. auch auf einem 32bit-OS eine 64bit-Größe sein kann weil Dateien ja > 4 Gig sein können) und so weiter. Wenn man diese Typen an den korrekten Stellen verwendet, stellt man sicher, dass man den Code überall dort verwenden kann, wo es eine standardkonforme C-Implementierung gibt.

              Das heißt aber auch, dass derselbe Code, wenn er auf unterschiedlichen Plattformen compiliert wird, auch unterschiedliche Datenstrukturen erzeugt. Das ist nicht das, was ich unter Portabilität verstehe.

              Portabilität heißt im Idealfall (!), dass Du das Programm nur ein einziges Mal schreibst und dann überall ohne Änderungen kompilieren kannst. Wenn nun zwei Plattformen unterschiedliche Datentypen haben (auf 32bit-Systemen sind Pointer 32bit, auf 64bit-Systemen 64bit), dann musst Du das irgendwie ein Einklang bringen. Und da ist so eine Abstraktion eben sinnvoll, weil man dann einfach den korrekten Typen verwenden kann ohne sich darüber Sorgen zu müssen.

              Und andere Datenstrukturen hast Du doch eh: Wenn Du auf einem Big-Endian-System kompilierst, dreht sich die Byte-Reihenfolge um im Vergleich zu Intel. Wenn die Größen von int/short/etc. sich ändern, dann hast Du auch andere Datenstrukturen. Ich verstehe da das Problem nicht.

              Mal ein Beispiel: Ich hatte vor Jahren, als es noch den einen, alten SELFHTML-Server gab, der in einer Tiefgarage in München stand, ein kleines C-Tool geschrieben, das von den ganzen Artikeln in SELFHTML aktuell die Metatags validiert. Das hatte ich damals auf einem x86 unter Linux entwickelt, mich aber explizit auf ANSI-C beschränkt. Der Server lief damals unter FreeBSD auf einem x86. Als wir dann die neuen Server bekommen haben, habe ich das Ding einfach unter Linux/x86_64 neu kompiliert ohne die geringste Änderung (!) und es lief sofort. Das ist, was ich unter Portabilität verstehe.

              Die einzige Stelle, wo es interessant wird, ist der Austausch von Daten. Wenn also ein Programm eine Datei schreibt und die Datei auf einer anderen Architektur wieder eingelesen werden will. Dann werden die tatsächlichen Größen von Datenstrukturen relevant. Aber auch da hat der aktuelle C-Standard vorgesorgt, es gibt int8_t, int16_t, int32_t, int64_t sowie die ganzen unsigned-Varianten (uint8_t, ...), mit dem man eine bestimmte Bit-Größe sicherstellen kann. Letztendlich bietet der aktuelle C-Standard also die nötigen Mittel, um problemlos portable Programme zu schreiben:

              * Spezifische Datentypen für bestimmte Zwecke wie Puffergrößen, Datei-
                 offsets, etc.
               * Spezifische Datentypen mit festen Bitwerten für den wohldefinierten
                 Datenaustausch

              Und wie bereits gesagt: Portabilität ist nicht der einzige Vorteil. Wenn man bestimmte Typen wie size_t verwendet, dann weiß man sofort, welche Annahmen in bestimmten Stellen von Code stecken.

              Viele Grüße,
              Christian

              PS: Kleiner Hinweis: intmax_t, int8_t, etc. sind in <stdint.h> definiert im aktuellen C-Standard und keine intrinsischen Typen.

              1. Hi,

                int soll die "natürliche" Datenbreite eines Systems wiedergeben.
                Nein. Woher hast Du das?

                aus meinem Studium. Ich hab hier noch das alte Script von Prof.Dr. Gerald Kampe aus seiner Vorlesung "Programmiersprachen" von 1991 als traditionellen Papierstapel, eben aus dem Schrank gekramt. Da heißt es:
                "Der Datentyp int ist implementierungsspezifisch und entspricht in der Regel der natürlichen Wortbreite des System. Auf einem 16bit-System ist int also 16bit breit, auf einem 32bit-System 32bit."

                Die Information, dass int auch auf 8bit-System üblicherweise 16bit breit ist, hat er geflissentlich weggelassen, so wie die Existenz von 8bit-CPUs für ihn sowieso auch damals schon irrelevant schien. Es gibt aber auch Compiler für kleine µCs, die int als 8bit-Typ definieren und long als 16bit.
                Bis jetzt hatte ich also keinen Anlass, Kampes Aussage aus der Vorlesung anzuzweifeln.

                Das heißt aber auch, dass derselbe Code, wenn er auf unterschiedlichen Plattformen compiliert wird, auch unterschiedliche Datenstrukturen erzeugt. Das ist nicht das, was ich unter Portabilität verstehe.
                Portabilität heißt im Idealfall (!), dass Du das Programm nur ein einziges Mal schreibst und dann überall ohne Änderungen kompilieren kannst.

                Das ist IMHO nur *ein* Aspekt von Portabilität.

                Und andere Datenstrukturen hast Du doch eh: Wenn Du auf einem Big-Endian-System kompilierst, dreht sich die Byte-Reihenfolge um im Vergleich zu Intel.

                Eben. Also nicht wirklich portabel. Zwar auf Quellcodeebene, aber nicht auf der Ebene des Datenaustauschs.

                Ciao,
                 Martin

                --
                Wer im Glashaus sitzt, sollte Spaß am Fensterputzen haben.
                Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
                1. Hallo Martin,

                  aus meinem Studium. Ich hab hier noch das alte Script von Prof.Dr. Gerald Kampe aus seiner Vorlesung "Programmiersprachen" von 1991 als traditionellen Papierstapel, eben aus dem Schrank gekramt. Da heißt es:
                  "Der Datentyp int ist implementierungsspezifisch und entspricht in der Regel der natürlichen Wortbreite des System. Auf einem 16bit-System ist int also 16bit breit, auf einem 32bit-System 32bit."

                  Ja, unrecht hatte er ja nicht, da steht ja "in der Regel" und nicht "zwingend". ;-) Und damals war es wohl wirklich noch die Regel.

                  Portabilität heißt im Idealfall (!), dass Du das Programm nur ein einziges Mal schreibst und dann überall ohne Änderungen kompilieren kannst.

                  Das ist IMHO nur *ein* Aspekt von Portabilität.

                  Ja, ok, ich habe das etwas zu einfach gezeichnet. Aber oft ist das bereits der wichtigste Schritt.

                  Und andere Datenstrukturen hast Du doch eh: Wenn Du auf einem Big-Endian-System kompilierst, dreht sich die Byte-Reihenfolge um im Vergleich zu Intel.

                  Eben. Also nicht wirklich portabel. Zwar auf Quellcodeebene, aber nicht auf der Ebene des Datenaustauschs.

                  Und für Datenaustausch gibt's bereitgestellte Funktionen / Standards. (Stichwort "Network Byte Order", htonl/ntohs, ...) Nur, weil es für verschiedene Teilaspekte, die man benötigt, weitere Abstraktionen gibt, heißt es nicht, dass andere Abstraktionen sinnlos/unnötig sind.

                  Generell halte ich es beim Programmieren so, dass ich versuche, so wenig wie möglich implizite Annahmen in meinen Quellcode einfließen zu lassen. Und das heißt eben auch, in C die dafür bereitgestellten Datentypen zu nehmen, weil mir da der Compiler bestimmte Dinge garantieren kann, die dann nicht irgendwann mal wieder zusammenbrechen. Natürlich gibt es Fälle, wo es viel zu aufwändig wäre, bestimmte Annahmen zu verlassen, an diesen Stellen versuche ich die Annahmen aber mit Kommentaren zu kennzeichnen. Aber die Verwendung von Typen wie size_t o.ä. hat in meinen Augen keine Nachteile, aber jenseits der Portabilität eben auch Vorteile, wie die ein meinen Augen bessere Lesbarkeit des Codes.

                  Viele Grüße,
                  Christian

          2. Tach.

            Außerdem haben diese speziellen Typen in meinen Augen einen weiteren wichtigen Vorteil: Sie geben klar zu erkennen, was man eigentlich für eine Art Variable an der Stelle wollte. Wenn ich irgendwo eine Variable des Typs "int" sehe, dann kann das alles mögliche sein. Bei size_t weiß ich aber, dass es sich um eine Puffergröße handeln soll, etc. Damit erkenne ich leichter an Hand des Typs, welche Annahmen ein bestimmtes Stück Code macht.

            Nur mal so aus Interesse: Ist size_t für Dich ebenfalls ein Kandidat, wenn nicht gerade Größenangaben in Byte sondern auch beliebigen anderen Einheiten gemacht werden? Also beispielsweise jeweils die Anzahl der Zeilen und Spalten in einer Matrix (könnte DOUBLEs enthalten, INTs oder irgendwelche andere Strukturen z. B. für komplexe Zahlen) ... In solchen Fällen wäre dann auch die Verwendung von size_t für die Indizes in diesen Strukturen konsequent und eigentlich für so ziemlich alles andere auch, was als Größe/Dimension aufgefaßt werden kann.

            --
            Always remember that you are unique. Just like everybody else.
            1. Hallo,

              Außerdem haben diese speziellen Typen in meinen Augen einen weiteren wichtigen Vorteil: Sie geben klar zu erkennen, was man eigentlich für eine Art Variable an der Stelle wollte. Wenn ich irgendwo eine Variable des Typs "int" sehe, dann kann das alles mögliche sein. Bei size_t weiß ich aber, dass es sich um eine Puffergröße handeln soll, etc. Damit erkenne ich leichter an Hand des Typs, welche Annahmen ein bestimmtes Stück Code macht.

              Nur mal so aus Interesse: Ist size_t für Dich ebenfalls ein Kandidat, wenn nicht gerade Größenangaben in Byte sondern auch beliebigen anderen Einheiten gemacht werden? Also beispielsweise jeweils die Anzahl der Zeilen und Spalten in einer Matrix (könnte DOUBLEs enthalten, INTs oder irgendwelche andere Strukturen z. B. für komplexe Zahlen) ... In solchen Fällen wäre dann auch die Verwendung von size_t für die Indizes in diesen Strukturen konsequent und eigentlich für so ziemlich alles andere auch, was als Größe/Dimension aufgefaßt werden kann.

              Grundsätzlich ja, size_t bzw. ssize_t kann auch prima für die Numerierung von Dingen verwenden, die nicht bloß 1 Byte groß sind. Es hängt aber vom konkreten Einzelfall ab. Wenn man zum Beispiel mit Sicherheit weiß, dass die Zahl garantiert nie größer als 10000 wird, kann man wohl auch int_least16_t o.ä. nehmen.

              Viele Grüße,
              Christian

  2. [...] Hat allerdings den Nachteil, daß ich sowas hier nicht machen kann

    printf("x1 = %s, x2 = %s", myfunc(x1), myfunc(x2));

    
    > weil der Speicher durch den zweiten Funktionsaufruf direkt überschrieben wird.  
      
    Eine Quick&Dirty-Lösung wäre, ein endliches Array von char\* - Pointern zu verwenden, etwa  
      
    ~~~c
      
    /* ... */  
    char *table[N];  
    static int table_counter=0;  
    /* ... */  
    
    

    N muss dabei mindestens so groß sein wie die maximale Anzahl von Formatfunktionsaufrufen in einem printf. Deine Funktion würde in etwa so aussehen:

      
    char *myfunc(double x) {  
      table_counter %= N;  
      
      /* formatiere x irgendwie in table[table_counter] hinein,  
         der Speicherplatz dafür sollte natürlich vorher irgendwo  
         reserviert worden sein... */  
      
      return table[table_counter++];  
    }  
    
    

    Das ganze ist natürlich eher gefrickelt und schon gar nicht thread-safe...

    MfG

    Andreas