structs



  • Ich habe mich ein wenig mit den Möglichkeiten von "structs" in C ausgetobt. Hier mal der Code:

    #include <stdio.h>
    #include <stdlib.h>
    // static 
    struct a1 {
       int i[2];
    }b1;
    typedef struct a2 {
       int i[2];
    }b2;
    struct a1 f1(struct a1 z)   // Ist das wirklich sauber in C
    {
       struct a1 r = z; // eine ganze struct zu kopieren?
       r.i[1] = 14;
       b1.i[0] = 1; // Zählt sowas als "globale Variable"?
       printf("b1 struct (function): %d\n", b1.i[0]);
       return r; // Ist das sauberes C eine ganze struct zurückzugeben?
    }
    b2 f2(b2 z) // klar, die selben Fragen bei "typedef struct"
    {
       b2 r = z;
       r.i[1] = 15;
       return r; // Ist das sauberes C eine ganze struct zurückzugeben?
    }
    int main(void)
    {
       b1.i[0] = 2; // Zählt sowas als "globale Variable"?
       printf("b1 struct (main): %d\n", b1.i[0]);
       struct a1 x1 = {{12,12}}; // struct
       struct a1 y1 = f1(x1);
       printf("a1 (struct): %d %d\n", y1.i[0],y1.i[1]);
       b2 x2 = {{13,13}}; // typedef struct
       b2 y2 = f2(x2);
       printf("b2 (typedef struct): %d %d\n", y2.i[0],y2.i[1]);
       return 0;
    }
    

    Die Fragen stehen ja schon im Code. Ganz wichtig wäre für mich: ist der Umgang so mit b1 in C "erlaubt", ohne von der restlichen C-Welt "gesteinigt" zu werden? Wenn "nein", wäre es mit "static" (auskommentiert) besser?

    Vielen Dank für euere Tipps!


  • Mod

    Für das globale b1 wirst du gesteinigt, egal ob static oder nicht. Der Rest ist ok.



  • Etwas mehr Leerzeilen wären nicht schlecht.



  • SeppJ schrieb:

    Für das globale b1 wirst du gesteinigt, egal ob static oder nicht. Der Rest ist ok.

    Danke mal für diese Information. Ich denke mal, das eröffnet mir eine völlig neue Möglichkeit mit diesem C umzugehen (Ich verdiene mein Geld mit einer anderen Programmierwelt). Wenn ich jetzt noch ein "Union" drüberlege, ist das auch suaberes C?

    typedef union _data {
        char data1[50];
        struct {
            int  knr;
            char vname[10];
            char nname[10];
            char wort[15];
        } stdat;
    }data;
    int main(void) 
    {
    	data a,b;
    	int length_a = sizeof(a.data1);
    .
    .
    .
    

    data1 ist 50, um beim Wegschreiben dieses Datensatzes in ein File noch Reserve zu haben.

    Ich komme dann wohl ohne die lästigen "mallocs" resp. "reallocs" und "strcpy" aus, um mit Strings und Datensätzen zu arbeiten - oder übersehe ich da was??

    Vielleicht als Hintergrund: Ich habe eine Aufgabe bekommen, etwas von euerer Windows/Linux/Unix Welt auf einen IBM-Host zu übertragen.....


  • Mod

    Das ist nicht mehr sauber, wenn ich deine Absicht korrekt verstehe. Auf einen anderen union-Member lesend zuzugreifen, nachdem in einen anderen Member geschrieben wurde ist undefiniertes Verhalten.

    (Es wird in der Praxis funktionieren, da genügend schlechte Programmierer das so machen, dass die Compilerbauer darauf Rücksicht nehmen. Aber verlassen darfst du dich darauf nicht.)



  • SeppJ schrieb:

    Das ist nicht mehr sauber, wenn ich deine Absicht korrekt verstehe. Auf einen anderen union-Member lesend zuzugreifen, nachdem in einen anderen Member geschrieben wurde ist undefiniertes Verhalten.

    (Es wird in der Praxis funktionieren, da genügend schlechte Programmierer das so machen, dass die Compilerbauer darauf Rücksicht nehmen. Aber verlassen darfst du dich darauf nicht.)

    Hmmmm
    Dann wäre sowas nicht sauber:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    typedef union _data {
        char data1[50];
        struct {
            int  knr;
            char vname[10];
            char nname[10];
            char wort[15];
        } stdat;
    }data;
    int main(void) 
    {
    	data a,b;
    	int i, length = sizeof(a.data1);
    	FILE *datei;
    	for (i = 0 ; i <= length; i++) a.data1[i] = ' ';
    	a.stdat.knr = 100;
    	strcpy(a.stdat.vname, "Hugo");
    	strcpy(a.stdat.nname, "Baltasar");
    	strcpy(a.stdat.wort, "Gugustadt");
    	for (i = 0 ; i <= length; i++) putchar (a.data1[i]);
    	printf("\n");
    	datei = fopen ("testdat.dat", "w");
    	for (i = 0 ; i <= length; i++) fputc (a.data1[i], datei);
    	fclose (datei);
    	for (i = 0 ; i <= length; i++) a.data1[i] = ' ';
    	for (i = 0 ; i <= length; i++) putchar (a.data1[i]);
    	printf("\n");
    	datei = fopen ("testdat.dat", "r");
    	for (i = 0 ; i <= length; i++) a.data1[i] = fgetc (datei);
    	fclose (datei);
    	for (i = 0 ; i <= length; i++) putchar (a.data1[i]);
    	printf("\n");
    	b = a;
    	strcpy(b.stdat.vname, "Hauruck");
    	for (i = 0 ; i <= length; i++) putchar (b.data1[i]);
    	printf("\n");
    	for (i = 0 ; i <= length; i++) putchar (a.data1[i]);
    	printf("\n");
    	return 0;
    }
    

    Beachte bitte "b = a" (36) -> ist das nicht sauber?? Das ist eigentlich die "Kardinalfrage", da ich der Nachwelt kein Mistprogramm hinterlassen will......

    Nochmal der Hintergrund: Ich bekomme Linux/Unix/Windows Dateien aus der ganzen Welt - das unsortrtiert - und diese müssen auf den Host in ein völlig anderes Format (also ohne Delimiter Zeug) in EBCDIC umgewandelt werden (https://en.wikipedia.org/wiki/EBCDIC) - ich muss mir also jeden Record anschauen, ob der Delimiter oder sonstiges Zeug enthält, was nicht zu den Daten gehört und diese dann entfernen. Die Dateistruktur muss eine fixe Länge haben, die ich schliesslich wegschreibe und an das ASCII -> EBCDIC Transferprogramm übermittelt. (Es handelt sich um ein paar 100 Millionen Records täglich) Auf dem Host wird das Verarbeiten dann einfach (Sortieren, verarbeiten und final abspeichern).
    (Die Dateistruktur sieht natürlich anders aus, wie im Beispiel und es wird auch gelesen, statt initialisiert)



  • Du solltest dir mal fwrite und fread ansehen. Da brauchst du keinen Umweg über data1

    b = a ist sauber, aber nicht das Beschreiben von stdat und anschließende Lesen von data1

    struct_frager schrieb:

    (Ich verdiene mein Geld mit einer anderen Programmierwelt)

    Das merkt man.
    Du denkst nicht C 🙂


  • Mod

    Nein, das ist nicht sauber, aus dem erwähnten Grund.

    Da du Angst vor Zuweisungen zu haben scheinst: Die sind nicht das Problem. Die funktionieren so, wie man es intuitiv erwartet. Wenn in C etwas kopiert wird, dann wird auch genau dieses Objekt kopiert. Wovor du anscheinend Angst hast, ist das Kopieren von Zeigern, bei dem, entsprechend der beschriebenen Regel, wirklich nur der Zeiger kopiert wird, und nicht, wie manche Leute erwarten, das worauf die Zeiger zeigen. Hier hast du structs/unions und deren Inhalt, die Datenfelder, wird entsprechend komplett kopiert.

    Du solltest dir auch mal dringend die Standardbibliothek genauer angucken. Praktisch jede einzelne deiner Schleifen ist entweder komplett unnötig oder kann elegant durch einen einzelnen Standardfunktionsaufruf ersetzt werden. Ich habe auch den Eindruck, dass dir nicht ganz klar zu sein scheint, dass in C Zeichenketten mit einem Nullzeichen beendet werden. Das ist essentielles Wissen, wenn man in C irgendetwas mit Zeichenketten machen möchte.

    Nochmal der Hintergrund: Ich bekomme Linux/Unix/Windows Dateien aus der ganzen Welt - das unsortrtiert - und diese müssen auf den Host in ein völlig anderes Format (also ohne Delimiter Zeug) in EBCDIC umgewandelt werden (https://en.wikipedia.org/wiki/EBCDIC) - ich muss mir also jeden Record anschauen, ob der Delimiter oder sonstiges Zeug enthält, was nicht zu den Daten gehört und diese dann entfernen. Die Dateistruktur muss eine fixe Länge haben, die ich schliesslich wegschreibe und an das ASCII -> EBCDIC Transferprogramm übermittelt. (Es handelt sich um ein paar 100 Millionen Records täglich) Auf dem Host wird das Verarbeiten dann einfach (Sortieren, verarbeiten und final abspeichern).
    (Die Dateistruktur sieht natürlich anders aus, wie im Beispiel und es wird auch gelesen, statt initialisiert)

    Man könnte dir da sicher gerne helfen, aber die Problembeschreibung ist zu knapp und es besteht kein erkennbarer Zusammenhang zu dem gezeigten Code (der eher ein Ausprobieren von diversen Techniken am kleinen Beispiel zu sein scheint, was sehr löblich ist), so dass nicht wirklich klar ist, was zu tun ist oder wo die Schwierigkeiten liegen.



  • Eine andere Sache ist deine knr. Die wird bei dir binär in die Datei gechrieben.
    Das ist nur sinnvoll, wenn das lesende System dieselbe Codierung hat.

    Besser ist es als ASCII (oder EBCDIC). Mit einem sinnvollen Trennzeichen kannst du dann auch auf die festen Satzlängen für die Namen verzichten.

    Ein weiters Problem umgehst du dabei auch noch: Du öffnest die Dateien im Textmodus.
    Dabei wird das Zeilenende von C '\n' an das System angepasst bzw umgekehrt.
    https://de.wikipedia.org/wiki/Zeilenumbruch#ASCII

    In C nimmst du deine struct

    typedef struct {
            int  knr;
            char vname[10];
            char nname[10];
            char wort[15];
          } data;
    

    befüllst diese.
    Wenn du dann (warum auch immer) Byteweise darauf zugreifen willst, dann nimmst du dir einen Zeiger auf Bytes:

    data a,b;
        int i, length = sizeof(a); 
        char *p;
    
        ....
    
        p = (char *)&a; // p zeigt jetzt auf den Anfang von a.
        for (i = 0 ; i < length; i++) putchar (p[i]);  // wichtig ist hier noch das <
        ...
        fwrite(&a, sizeof(a), 1, datei);
    

    Du darfst auch nicht davon ausgehen, dass alle Elemente der struct nahtlos hintereinander stehen. Der Compiler darf da Füllbytes einfügen, wenn er das für sinnvoll hält.

    (Nimm jetzt aber bitte nicht mein Beispiel als Grundlage für deinen Code)



  • Herzlichen Dank für euere Antworten und die Mühe, die ihr euch gemacht habt, meinen Testcode zu analysieren.

    Ich habe mein Projektproblem schon umrissen: Wir bekommen grosse Datenmengen in eben diesem "Delimeterformat" - also mit "/0". Und genau diese sollten entfernt werden, da auf IBM-Hosts anders gearbeitet wird. Auf diesen Hosts habe ich wie beim Code eine zusammenhängende Datenstruktur - sprich ich kann eine Datenstruktur mit einem "Rutsch" als Record wegschreiben. Die Länge des Records ist im Filesystem als Metadaten hinterlegt (also etwa 250 Byte oder, oder...). Es braucht daher kein /0 Delimiter. Meine Idee war jetzt, diese Daten auf einem Windows, Unix, Linux Rechner schon in dieses Format zu bringen - diese dann in EBCDIC umzuwandeln und auf den Host zur weiteren Verarbeitung (Sort usw) hochzuschieben.

    Klar "int" war ein schlechtes Beispiel - Die "knr" müsste auch Character Format haben.

    Wir sind noch in der Analysephase, wie wir das technisch umsetzen können.

    Ich werde das mit dieser "Union-Technik" noch näher anschauen - selbst wenn es nach C Philosophie unschön ist.

    Ich bedanke mich nochmal für euer Engagement für mein Problem


  • Mod

    struct_frager schrieb:

    Ich werde das mit dieser "Union-Technik" noch näher anschauen - selbst wenn es nach C Philosophie unschön ist.

    Nicht unschön. Falsch. "Undefiniertes Verhalten" klingt zwar harmlos, ist aber unter den schlimmsten Dingen, die man machen kann. Geht (wenn man Pech hat und es dadurch nicht bemerkt) möglicherweise 1000x gut, aber wenn's dann plötzlich schief geht, dann kracht es richtig.

    Es wurde schon gesagt, dass eigentlich gar kein Problem vorläge, wenn du nur die Standardbibliothek besser nutzen würdest. DirkB hat beispielsweise schon fread/fwrite erwähnt. Und er hat auch auf andere Gefahren hingewiesen, die beachtet werden müssen. Und die übrigens auch beim "Trick" mit den unions gelten würden, selbst wenn man sich anderweitig auf dessen Funktionieren verlassen könnte.

    Was ich mich gerade frage ist, was ist, wenn die Zeichenkette kürzer als der Record ist? Habt ihr das bedacht? Deine Beschreibung klingt diesbezüglich komisch.



  • Wenn du einen Datensatz in einen zusammenhängenden String speichern willst, dann schau dir mal printf (sprintf, fprintf) an.
    Das ist eine mächtige Funktion, die deine Ausgabe schon fast auf einen Einzeiler reduziert.

    Mit den length und precision Modifieren kannst du die Feldlänge bei Strings begrenzen.
    Mit dem * statt einer festen Zahl bist du auch frei in der Länge.

    printf("%10d%-10.10s%-10.10s%-*.*s", a.knr, a.vname, a.nname, 15,15,a.wort);
    

    Du solltest sehr sorgfältig deine Eingaben auf mögliche Fehler (Kundenummer zu groß für das Feld, Namen zu lang, nicht konvertierbare Zeichen, ...) testen.

    printf arbeitet mit C-Strings. Deine Beispielstruct kann daher nur 9 bzw 14 Zeichen aufnehmen, da die '\0' auch noch Platz braucht.


Anmelden zum Antworten