Mehrdimensionales Array an Funktion übergeben



  • Hallo,
    in meinem Buch komme ich an folgender Stelle vom Verständnis nicht ganz weiter. Die Übergabe von einfachen Arrays ist mir noch klar. Da wird einfach ein Zeiger, der auf das erste Element zeigt übergeben. Bei mehrdimensionalen sehe ich allerdings nicht mehr so gut durch.

    void callmarray( int marray[][DIM2], int dim1 );
    

    wieso wird hier die 2. Dimension mit übergeben? Die erste wird ja wie bei eindimensionalen über einen extra Parameter übergeben. Und was wird jetzt genau an die Funktion übergeben? Ein mehrdimensionales Array ist ja quasi ein Zeigerarray, wobei jedes Array auf ein weiteres Array zeigt.

    Viele Grüße Max


  • Mod

    Das funktioniert nur, wenn DIM2 eine Compilezeitkonstante ist. Das Prinzip bei Arrays ist ja, dass die Größe der Objekte in den Arrays zur Compilezeit bekannt sein muss. Hat man mehrdimensionale Arrays, so hat man ein Array von einem Array. Das letztere Array nenne ich mal inneres Array. Da man, wie schon gesagt, nur Arrays von Objekten bekannter Größe machen kann, so muss man die Größe des inneren Arrays bei der Typangabe mit angeben. Der Compiler kann sonst nicht wissen, wie er die Zeigerarithmetik richtig umsetzen soll.

    Dieses Prinzip wird entsprechend bei höherdimensionalen Arrays fortgesetzt: Die inneren Arraygrößen müssen alle zur Compilezeit bekannt sein, das äußerste Array wird wie von 1-dimensionalen Arrays bekannt behandelt.

    Wie du siehst, ist das recht kompliziert. Deshalb der Tipp: Benutze keine mehrdimensionalen Arrays! Wenn es unbedingt Arrays sein müssen, so nimm anstatt eines zweidimensionalen Arrays[L][M} ein eindimensionales Array der Größe L * M und übergib L und M mit an die Funktion, die dann die nötige Zeigerarithmetik selber umsetzt. So bist du viel flexibler, weil du nicht für jede Größe des inneren Arrays eine eigene Funktion brauchst. Noch flexibler wirst du natürlich mit den dynamischen Containern aus der STL und man kann leichter merken, wenn man sich bei der Zeigerarithmetik doch mal verechnet hat und auf ungültige Indizes zugreifen will.



  • ok aber verstehen würde ich es trotzdem. Was wird denn da nun in dem Beispiel an die Funktion übergeben? Ein Zeiger zum Anfangs Element des äußeren Arrays?


  • Mod

    Es wird ein Array unbestimmter Größe mit Elementen vom Typ int[DIM2] übergeben. Dies entspricht (jetzt lynchen mich die anderen gleich) praktisch einem Zeiger auf das [0][0] Element. Über den Unterschied zwischen Zeigern und Arrays gibt es hier viele Threads in denen heiß diskutiert wird.



  • Das [] auf erster Ebene spielt innerhalb der Parameterdeklarationen von Funktionen eine besondere Rolle. Wenn Du schreibst

    void foo(int dings[]);
    

    ist das Äquivalent zu

    void foo(int *dings);
    

    Weil Arrays recht flott zu "Zeigern zerfallen" (auf Englisch: array to pointer decay) ist das auch nicht weiter schlimm. Du kannst die Funktion immer noch so aufrufen:

    int bums[99];
    foo(bums);
    

    Hier wird aus "bums" ein Zeiger, der auf das erste Element zeigt, welcher dann der Funktion übergeben wird. Die Syntax für einen Elementzugriff ist bei Arrays und Zeigern die gleiche, weswegen die Verwechelungsgefahr hoch ist und die sichtbaren Grenzen zwischen Zeiger und Arrays verwischen.

    Wenn Du jetzt

    void foo(int dings[][5]);
    

    deklarierst, dann ist dings vom Typ int()[5] -- also ein Zeiger auf ein Array von 5 ints. dings+1 zeigt dann auf das zweite 5-elimentige Array, dings+2 zeigt dann auf das dritte 5-elementige Array. Um allerdings dings+1 und dings+2 ausrechnen zu können, muss natürlich die Größe des inneren Arrays bekannt sein. Die stehen alle hintereinander im Speicher (ist ja ein Array von Arrays!) und die Adresse von dings+1 kann nur berechnet werden, wenn die Größe des "inneren Typs" (hier: int[5]) bekannt ist. Das ist auch der Grund, warum bei allen inneren Arrays die Größe bekannt sein muss; denn die Größe der Arrays wird nicht irgendwo sonst noch gespeichert. Die Typen int()[5] und int(*)[6] sind einfach unterschiedlich.

    Du kannst Deine Funktion natürlich auch anders deklarieren, zB so

    void foo(int *dings[]);
    

    Das ist dann allerdings ein Array aus Zeigern und kein linear im Speicher liegendes 2D-Array mehr -- obwohl der Elementzugriff syntaktisch der gleiche ist.

    Gruß,
    SP



  • ok nochmal ne andere Frage dazu ist eine String Tabelle nicht auch ein mehrdimensionales Array? Wieso werden die Parameter der main Funktion dann so festgelegt:

    int main( int argc, char *argv[] ) {
    


  • Ich denke, es ist sehr hilfreich, wenn man das mal "aufmalt".

    void foo(int *x[]);
    
               Array aus
    Zeiger      Zeigern
               +------+
      x -----> | x[0] |----> ? ? ? ... (ein int oder mehrere 
               |------|                 hintereinander im Speicher)
               | x[1] |----> ? ? ? ...
               |------|
               | x[2] |----> ? ? ? ...
               +------+
               :      :
    
    wobei zu
    void foo(int x[][3]);
    folgendes Bildchen gehört
    
                   Array aus
    Zeiger     Arrays von 3 ints
               +---------------+
      x -----> | x[0]: [? ? ?] |
               |---------------|
               | x[1]: [? ? ?] |
               |---------------|
               | x[1]: [? ? ?] |
               +---------------+
               :               :
    

    wobei ? jeweils für einen int steht, welche im letzten Fall alle hintereinander im Speicher angeordnet sind, also x[0][1] steht hinter x[0][0], x[1][0] steht hinter x[0][2], u.s.w.

    edit: Tippfehler 5 in 3 geändert



  • ok dazu habe ich nochmal ein paar Fragen. Erstmal wieso "5 ints "? SInd es nicht nur 3?

    Dann das man für Stringtabellen

    void foo(int *x[]);
    

    verwndet erscheint mir logisch, mann weis ja nicht wie lang die einzelnen Strings sind.

    Aber warum muss man bei der Übergabe mehrdimensionaler Arrays

    void foo(int x[][3]);
    

    verwenden. Liegt das nur am Zerfallsverhalten bei der Übergabe?

    Aber wieso ist beim ersten die Übergabe der Länge nicht erforderlich? Wenn man sie doch beim 2. unbedingt zum berechnen braucht.


  • Mod

    Max123 schrieb:

    ok dazu habe ich nochmal ein paar Fragen. Erstmal wieso "5 ints "? SInd es nicht nur 3?

    Sebastian Pizer war in seinem Schaublid anscheinend zu faul, alle 5 hin zu malen und hat sich mit 3 begnügt um das Prinzip zu zeigen. Aber int[5] sind nun mal 5 ints.

    Dann das man für Stringtabellen

    void foo(int *x[]);
    

    verwndet erscheint mir logisch, mann weis ja nicht wie lang die einzelnen Strings sind.

    Aber warum muss man bei der Übergabe mehrdimensionaler Arrays

    void foo(int x[][3]);
    

    verwenden. Liegt das nur am Zerfallsverhalten bei der Übergabe?

    Mach dir nochmal den Unterschied klar: Das erste ist ein Array von Zeigern, das zweite ist ein Array von Array. Das sind ganz verschiedene Dinge! Sebastian Pizers Bild zeigt das schon genau wie es ist, mehr kann man da eigentlich nicht erklären. Guck dir das noch mal an!

    Aber wieso ist beim ersten die Übergabe der Länge nicht erforderlich? Wenn man sie doch beim 2. unbedingt zum berechnen braucht.

    Nochmal: Wenn man ein Array hat, muss man irgendwie berechnen können, wo array[0], array[1], array[2], usw. tatsächlich im Speicher liegen. Dazu muss man wissen, wie lang jedes einzelne Element ist, es ist aber egal, wie viele Elemente das Array selber hat.
    Wenn man nun ein Array von Arrays hat, dann muss man wissen, wie viele Elemente die inneren Arrays haben, damit man weiß, wie lang die Elemente des äußeren Arrays sind.



  • Max123 schrieb:

    ok dazu habe ich nochmal ein paar Fragen. Erstmal wieso "5 ints "? SInd es nicht nur 3?

    Tippfehler von mir.

    Max123 schrieb:

    Aber wieso ist beim ersten die Übergabe der Länge nicht erforderlich? Wenn man sie doch beim 2. unbedingt zum berechnen braucht.

    Du hast anscheinend den Unterschied zwischen Zeiger und Array nicht verstanden. Wie gesagt, der Elementzugriff ist syntaktisch derselbe. Trotzdem ist ein Zeiger etwas anderes als ein Array.

    Gruß,
    SP



  • Ok ich glaube ich habs jetzt verstanden.

    void foo(int *x[]);
    

    bei dem Array aus Zigern ist die Größe ja durch die Zeigerart festgelegt, also bekannt.

    Beim Array aus Arrays hängt die Größe der Elemente des äußeren Arrays jedoch von der Element Anzahl der inneren ab- Deshalb muss diese bekannt sein.

    Nur noch eine Frage wieso kann ich mehrdimensionale Arrays nicht an eine Funktion übergeben, indem ich mit

    void foo(int *x[]);
    

    einen Zeiger auf ein Zeigerarray übergebe?



  • Max123 schrieb:

    bei dem Array aus Zigern ist die Größe ja durch die Zeigerart festgelegt, also bekannt.

    Beim Array aus Arrays hängt die Größe der Elemente des äußeren Arrays jedoch von der Element Anzahl der inneren ab- Deshalb muss diese bekannt sein.

    Das ist ein Bingo! 😉

    Max123 schrieb:

    Nur noch eine Frage wieso kann ich mehrdimensionale Arrays nicht an eine Funktion übergeben, indem ich mit

    void foo(int *x[]);
    

    einen Zeiger auf ein Zeigerarray übergebe?

    Hmm... Das sitzt noch nicht 100%. Also nochmal: Array != Zeiger

    Deine Frage ist: Warum kann die Funktion foo

    void foo(int *x[]);
    

    nicht mit einem 2D-Array aufgerufen werden:

    int dings[5][5];
    foo(dings);
    

    Die Antwort ist: Der Typ von dings ist int[5][5] (Array von Arrays von 5 ints). Der/Die/Das "array-to-pointer decay" würde in einem Objekt vom Typ int(*)[5] resultieren (Zeiger auf (das erste) Array von 5 ints). Der Typ von x ist aber int** (Zeiger auf Zeiger auf int). Es passt also nicht zusammen.

    Was man auch nicht zu oft sagen kann: std::vector benutzen. Das Ding ist so schlau, dass es sogar weiß, wie groß es ist, kann die Größe dynamisch anpassen -- und das obwohl sizeof(std::vector<int>) konstant ist. 😉

    Gruß,
    SP



  • Das ist ein Bingo! 😉

    inglorious bastards geschaut?

    ok danke also bei dem vector bin ich in meinem Buch noch nicht angekommen und ich würde das ganze schon ein wenig geordnet angehen wollen.

    Mit dem Zerfallen kann man sich das so vorstellen, das der Typ immer um eins hin zur Adresse "degradiert wird" also aus:

    int[5][5] wird int* [5], aus int [5] wird int*

    nur wieso zerfällt die Stingtabelle, also die Parameter, die ich an main Übergebe zu char**?


  • Mod

    Max123 schrieb:

    int[5][5] wird int* [5], aus int [5] wird int*

    fast, Klammern nicht vergessen. Aus
    int[5][5] wird int(*)[5], eine Zeiger auf ein Array, nicht ein Array aus Zeigern - sonst könnte der Arrayzerfall ja weitergehen.

    nur wieso zerfällt die Stingtabelle, also die Parameter, die ich an main Übergebe zu char**?

    Das ist von vornherein ein Array aus Zeigern. Arrays zerfallen beim kleinsten Anzeichen von Gefahr zu Zeigern.



  • ok dann ist ja alles klar mich hat nur das in meinem Buch verwirrt:

    C-String-Tabellen sind [...] mehrdimensionale
    char-Arrays.

    Das hat mich annehmen lassen, dass mehrdimensionale Arrays und String Tabellen das selbe sind.

    Also zusammenfassend kann man sagen, Stringtabellen sind Arrays aus Zeigern und ein mehrdimensionales, "normales" Array ist ein Array aus Arrays.


  • Mod

    Max123 schrieb:

    ok dann ist ja alles klar mich hat nur das in meinem Buch verwirrt:

    C-String-Tabellen sind [...] mehrdimensionale
    char-Arrays.

    Das hat mich annehmen lassen, dass mehrdimensionale Arrays und String Tabellen das selbe sind.

    Dann ist die Aussage entweder ungenau oder falsch. Ein mehrdimensionales char Array ist sowas wie char[5][20], das wären 5 Zeichenkette mit Länge 19 (weil ein Zeichen bei cstrings für das Ende draufgeht). Es werden aber immer 20 chars pro Zeichenkette belegt, egal wie lang die Zeichenketten wirklich sind.

    Bei der Übergabe zur main hat man *char[], ein eindimensionales array von Zeigern auf andere chars. Dort wo diese Zeiger hinzeigen, liegen dann char Arrays mit genau der passenden Größe für die an main übergebenen Zeichenfolgen.



  • Man kann sich sogar darüber streiten, ob es eigentlich wirklich "mehrdimensionale Arrays" gibt, oder ob es nur Arrays von Arrays sind. :p

    In Pascal gibt es -- wenn ich mich richtig erinnere -- "echte" mehrdimensionale Arrays, bei denen die Indizierung nicht über [x][y], sondern [x,y] läuft.

    C++ erlaubt es einem aber, einen eigenen Typen so zu definieren, so dass er wie ein dynamisches mehrdimensionales Array benutzbar wird. ZB Boost.MultiArray.


Anmelden zum Antworten