zwei dimensionales Array von einer Struktur?



  • Hallo Leute, ich denke es liegt hier eher an einem Verständnisproblem, doch bin ich mir nicht ganz sicher. Ich habe eine Struktur RGBTRIPLE, und jetzt will ich ein Array vom Typ dieser Struktur anlegen.

    Das typische struct RGBTRIPLE pixels[][]; will so nicht gehen. Geht in dem Fall dann nur der Weg über ein Pointer Array, weil das habe ich SO weiter verwirklicht. Doch komme ich dann zu einem Fehler danach hier mein Code:

    RGBTRIPLE **pixels = NULL; // Anlegen des zweiDim Arrays
    	pixels = (RGBTRIPLE**)malloc((info_h.biWidth)*sizeof(RGBTRIPLE*));
    
    	for(i=0;i<info_h.biHeight;i++) {
    		pixels[i] = (RGBTRIPLE*)malloc((info_h.biHeight)*sizeof(RGBTRIPLE));
    	}
    
    	RGBTRIPLE **pixels_neu = NULL; // Anlegen des zweiten zweiDim Arrays für das beschreiben später
    	pixels_neu = (RGBTRIPLE**)malloc((info_h.biWidth)*sizeof(RGBTRIPLE*));
    
    	for(i=0;i<info_h.biHeight;i++) {
    		pixels_neu[i] = (RGBTRIPLE*)malloc((info_h.biHeight)*sizeof(RGBTRIPLE));
    	}
    ´
    fread(pixels, sizeof(RGBTRIPLE), ((info_h.biHeight) * (info_h.biWidth)), fp);
    
    	for(i = 1; i < info_h.biHeight-1; i++) {
    		for (j = 1; j < info_h.biWidth-1; j++) {
    			pixels_neu[i][j] = ((pixels[i-1][j-1] + pixels[i-1][j] + pixels[i-1][j+1] +pixels[i][j-1] + pixels[i][j] + pixels[i][j+1] + pixels[i+1][j-1] + pixels[i+1][j] + pixels[i+1][j+1]) / 9);
    		}
    	}
    	fclose(fp);
    

    Das heißt ich lese jetzt die pixels Matrix ein, dann nehme ich einen Punkt und nehme seine Paare, so dass ich 3x3 Feld habe. Danach addiere ich alle und teile sie durch die Anzahl und mache so den Mittelwert. Aber ich darf gar nicht addieren (steht zumindest bei Visual Studio 😃 ) wo liegt mein Fehler? 🙂

    Gruß
    Sanj3k



  • struct s darfste nicht miteinander addieren. Operatorenüberladung kennen wir noch nicht, zumindest nicht in dem C, das Visual Studio versteht. Aber ich programmiere auch noch mit C89, C90 und C99, und diese ganzen neuen Technologien sind zu viel für mich armen alten Mann. 🙂 Die Elemente willste addieren. Nicht die Objekte.

    Und Leute, gewöhnt euch mal an, sowas leserlicher zu implementieren:

    RGBTRIPLE**pixels,**pixels_neu;
        pixels    =malloc((info_h.biWidth)*sizeof(RGBTRIPLE*)); 
        pixels_neu=malloc((info_h.biWidth)*sizeof(RGBTRIPLE*));
    
        for(i=0;i<info_h.biHeight;i++)
        {
            pixels[i]    =malloc((info_h.biHeight)*sizeof(RGBTRIPLE));
            pixels_neu[i]=malloc((info_h.biHeight)*sizeof(RGBTRIPLE));
        }
    

    Viel ordentlicher, und trotzdem hat sich nichts geändert.
    Und bei Gelegenheit mal prüfen, ob malloc 0 zurückgibt. Dann ist dir der Speicher ausgegangen. Wenn du dann trotzdem in die Zeiger schreibst, kann dir das Programm abschmieren.

    #include <stdlib.h>
    
    struct xxx
    {
            size_t _1;
            size_t _2;
            size_t _3;
    };
    
    int main(void)
    {
            struct xxx a=((struct xxx){._1=1,._2=2,._3=3}),
                       b=((struct xxx){._1=1,._2=2,._3=3});
            /*struct xxx c=a+b;*/
    
            return 0;
    }
    

    Kompiliert der gcc recht fein ohne die auskommentierte Zeile. Mit ihr ist er so stoisch wie VS.


  • Mod

    Lemma: Ein Array ist kein Pointer, ein Pointer ist kein Array.

    Korollar 1: Ein 2D-Array ist kein Array von Pointern, ein Array von Pointern ist kein 2D-Array.
    Korollar 2: Ein Zeiger auf einen Zeiger ist erst recht kein 2D-Array und ein 2D-Array ist erst recht kein Zeiger auf Zeiger.

    Lösung: Kommt drauf an, was du genau willst. Was ist eigentlich dein Fehler? Vermutlich ein Laufzeitfehler, weil du lustig im Speicher rumschreibst, ohne zu wissen, was du da tust (weil du die obigen Prinzipien verletzt).

    Da ich nicht immer RGBTRIPLE schreiben will, nehme ich im folgenden an, dein Datentyp hieße Foo .

    • Methode 1: Willst du ein 2D-Array, bei dem alle Dimensionen bereits zur Compilezeit feststehen? Dann:
    Foo bar[aeussere_dimension][innere_dimension];
    

    Das ist ein Array mit aeussere_dimension Einträgen, wobei jeder Eintrag vom Typ Foo[innere_dimension] ist.

    • Methode 2: Willst du ein 2D-Array, bei dem die innere Dimension zur Compilezeit feststeht, aber du weißt nicht, wie viele von diesen Arrays du brauchst? Dann:
    Foo (*bar)[innere_dimension];
    

    Das ist ein Zeiger auf ein (oder mehrere) Arrays vom Typ Foo[innere_dimension] . Das ist, wie gesagt noch kein 2D-Array, denn ein Pointer ist kein Array. Wir können ihn aber auf ein Array* zeigen lassen:

    bar = malloc(sizeof(*bar) * wieviele_eintraege_wir_brauchen);
    

    (Nebenbemerkung: In C ist es nicht nötig, die Rückgabe von malloc zu casten und die allgemeine Meinung ist, dass es eher schadet als nützt.) Nun zeigt bar auf Speicher, der für ein 2D-Foo-Array der Dimensionen wieviele_eintraege_wir_brauchen X innere_dimension ausreicht. Der Zugriff erfolgt nach wie vor in der Art bar[x][y] .

    • Methode 3: Willst du ein 2D-Array, bei dem alle Dimensionen erst zur Laufzeit feststehen? Dann:
    Foo *bar;
    

    Das ist ein Zeiger auf ein (oder mehrere) Foo. Das hättest du nicht erwartet, oder? Wie weist man diesem bar einen Speicherbereich zu, der für innere_dimension X aeussere_dimension Einträge ausreicht? Ganz einfach:

    bar = malloc(sizeof(*bar) * innere_dimension * aeussere_dimension);
    

    Nun zeigt bar auf einen passend großen, zusammenhängenden(!) Block. Leider muss man für den Zugriff selber rechnen, denn es ist ja im Prinzip nur ein 1D-Array (bzw. ein Zeiger auf eines) und die Interpretation als 2D-Array erfolgt gänzlich im Kopf des Programmierers. Der Eintrag an der Stelle (x,y) ist also bar[x * innere_dimension + y] .

    Oder irgendeine Variante? Das kannst du dir selber ausdenken oder nachfragen, indem du genau beschreibst, was du willst.

    Was hast du in deinem Programm gemacht? Du hast kein 2D-Array erstellt, sondern du hast einen Zeiger auf einen Zeiger auf RGBTRIPLE . Diesen lässt du auf ein Array von Zeigern auf RGBTRIPLE zeigen (deine Zeilen 2 und 9). Jeden einzelnen dieser Zeiger in dem Array lässt du auf weitere Arrays mit Einträgen vom Typ RGBTRIPLE zeigen (deine Zeilen 5 und 12). Angenommen, du wolltest ein Array mit 2x3 Einträgen. Die oben beschriebenen Methoden hätten dir folgendes Speicherlayout beschert:

    Methode 1:
      Adresse von bar (bar ist ein Array[2] von Arrays[3] von Foo)
         |
         v
    --------------------------------------------------------------------------------
         | bar[0][0] | bar[0][1] | bar[0][2] | bar[1][0] | bar[1][1] | bar[1][2] |
    --------------------------------------------------------------------------------
    
    Methode 2:
      Adresse von bar (bar ist ein Zeiger auf Array[3] von Foo)
         |
         v
    -------------------
         | bar    |
    -------------------
             |
             | worauf bar zeigt (bar zeigt auf ein Array[2] von Array[3] von Foo)
          ----
         v
    --------------------------------------------------------------------------------
         | bar[0][0] | bar[0][1] | bar[0][2] | bar[1][0] | bar[1][1] | bar[1][2] |
    --------------------------------------------------------------------------------
    
    Methode 3:
      Adresse von bar (bar ist ein Zeiger auf Foo)
         |
         v
    -------------------
         | bar    |
    -------------------
             |
             | worauf bar zeigt (bar zeigt auf ein Array[6] von Foo)
          ----
         v
    ----------------------------------------------------------------
         | bar[0] | bar[1] | bar[2] | bar[3] | bar[4] | bar[5]  |
    ----------------------------------------------------------------
    
    Mittels bar[x * 3 + y] kann man auf das Element (x,y) kommen. Beispielsweise 
    ist das Element (1,1) an der Stelle bar[4]. Vergleiche mit den Methoden oben, 
    wo bar[1][1] an der Stelle steht, wo hier bar[4] steht.
    

    Alles schön kompakt, so wie du dir das wahrscheinlich vorgestellt hast+. Du hast aber gemacht:

    Adresse von bar (bar ist ein Zeiger auf Zeiger auf Foo)
         |
         v
    -------------------
         | bar    |
    -------------------
             |
             | worauf bar zeigt (bar zeigt auf ein Array[2] von Zeigern auf Foo)
          ----
         v
    ---------------------------
         | bar[0] | bar[1] |
    ---------------------------
             |          |
             |          ------------------------------------
             |                                             | worauf bar[1] zeigt (ein Array[3] von Foo)
             | worauf bar[0] zeigt (ein Array[3] von Foo)  v  
          ----                                          -------------------------------------------
         v                                                 | bar[1][0] | bar[1][1] | bar[1][2] |
    -------------------------------------------         -------------------------------------------
         | bar[0][0] | bar[0][1] | bar[0][2] |
    -------------------------------------------
    

    Alles kreuz und quer verteilt! Prinzipiell hätte ja auch jedes innere Array eine andere Größe haben können. Aber das wolltest du schließlich nicht, also ist es ziemlich sinnlos, das so zu machen. Später dann hast du

    fread(pixels, sizeof(RGBTRIPLE), ((info_h.biHeight) * (info_h.biWidth)), fp);
    

    gemacht. In das obige Bild übersetzt hast du die Stelle, auf die bar zeigt genommen, und an diese Stelle (info_h.biHeight) * (info_h.biWidth) Elemente vom Typ RGBTRIPLE geschrieben (oder im Beispiel 6 Elemente vom Typ Foo). Aber an der Stelle stehen bloß (info_h.biWidth) Pointer (oder im Beispiel 2 Pointer)! Du hast also sowohl die Pointer mit völlig falschen Daten überschrieben, also auch über dein Array von Pointern hinaus geschrieben. Als du dann später versuchtest, über diese Pointer irgendwelche Zugriffe zu machen, stand da nur Müll drin und dein Programm stürzte ab (wenn es nicht schon vorher abgestürzt ist, weil du über das Array hinaus geschrieben hast).

    *: Genau genommen, ist das kein Array, auf das wir hier (und im Folgenden) bar zeigen lassen. Es ist einfach ein Block Speicher. Arrays haben keine physikalische Realität im Computer, sie sind einzig im Kopf des Programmierers, um ihm das Denken zu erleichtern. Ich werde im folgenden aber trotzdem so schreiben, als zeigten diese Zeiger auf Arrays, da wir diese Speicherblöcke genau so ansprechen, als wären sie Arrays und - wie gerade erklärt - ist die Programmlogik, wie man ein Array anspricht, alles was ein Array überhaupt ausmacht.
    +: Es ist noch bemerkenswert, dass die Methoden 2 und 3 beide exakt das gleiche Speicherlayout erzeugen. Der Unterschied liegt nur da drin, dass bei Methode 2 der Compiler selber den Code erzeugen kann, wie man das Array anspricht (weil er die Größe des inneren Arrays kennt), während man dies bei der dritten Methode selber schreiben muss. Effektiv erhält man aber in beiden Fällen den gleichen Code. Der Compiler kann schließlich nicht zaubern, er kann einem nur die Arbeit abnehmen, die Indexberechnung selber zu programmieren.



  • @SeppJ:
    Muss ich mal in meinen Hinterkopf bekommen, dass Indexberechnung schneller ist als immer malloc aufzurufen. Natürlich hast du recht, den Code hätte man auch durch:

    RGBTRIPLE*pixels,*pixels_neu;
        size_t my_size=info_h.biWidth*info_h.biHeight*sizeof(RGBTRIPLE);
    
        pixels    =malloc(my_size);
        pixels_neu=malloc(my_size);
    

    komplett ersetzen können. Ich bin dumm. 😞



  • SeppJ schrieb:

    Das ist ein Zeiger auf ein (oder mehrere) Arrays vom Typ

    Das ist ein Zeiger auf ein (oder mehrere) Foo.

    Nein. Ein Zeiger zeigt immer genau auf ein Element/Objekt und nicht auf mehrere.
    Und wenn das Objekt ein Array ist, zeigt der Zeiger auch auf ein Element, nämlich auf das ganze Array.

    bar = malloc(sizeof(*bar) * innere_dimension * aeussere_dimension);
    Nun zeigt bar auf einen passend großen, zusammenhängenden(!) Block. Leider muss man für den Zugriff selber rechnen, denn es ist ja im Prinzip nur ein 1D-Array (bzw. ein Zeiger auf eines) und die Interpretation als 2D-Array erfolgt gänzlich im Kopf des Programmierers. Der Eintrag an der Stelle (x,y) ist also bar[x * innere_dimension + y].

    Mit C99 kann man Zeiger auf VLA benutzen, und somit die innere Dimension dynamisch kapseln, also

    Foo (*bar)[innere_dimension]; /* innere_dimension muss NICHT zur Compilezeit feststehen */
    also statt
    bar[x * innere_dimension + y]
    kürzer und weniger fehleranfällig
    bar[x][y]
    

    (Wohlgemerkt: Zeiger auf VLA sind die einzig sinnvolle Verwendung von VLA und nicht etwa VLA selbst)

    http://ideone.com/9s6Jvf
    Dabei ist dann zwar ein (einmaliger) Zeigercast fällig, aber im Tausch gegen das ständige Indexgefrickel vorzuziehen.


Anmelden zum Antworten