Falscher Feldzugriff



  • Hallo liebe c/c++ Gemeinde,

    ich habe mal wieder ein kleines Problem mit C und hoffe daher auf eure Hilfe.

    Ich arbeite diesmal mit einem Programm, das ich nicht selber geschrieben habe. Hier erfolgt ein Zugriff auf ein Element eines zweidimensionalen arrays, der so nach meinem Verständnis eigentlich nicht funktionieren sollte und wenn, dann nur durch Zufall oder Glück. Trotzdem läuft das Programm stabil durch und spuckt auch noch sinnvolle Werte aus. Vielleicht könnt ihr mich ja aufklären.

    Ich habe das Kernproblem mal in einem kleinen separaten Programm aufgefasst und den Code, sowie die Ausgabedatei angehängt.
    Es wird in einer Struktur ein zweidimensionales array definiert, das in der zweiten Dimension aus zwei Elementen besteht. Nach meinem Verständnis wird auf diese Elemente mit den Indizes 0 und 1 zugegriffen.
    Innerhalb des Programm erfolgt der Zugriff auf die Elemente jedoch mit den Indizes 1 und 2. Gibt man deren Inhalt dann aus, stimmen die Einträge auch. Zufall? Lässt man sich noch das Element mit dem Index 0 ausgeben, stehen in diesem die Werte der zweiten Dimension der vorherigen Zeile (siehe angehägte Ausgabe). Es macht also den Anschein, als ob das zweidimensionale array in einer Art Schleife gefüllt werden würde. Vielleicht könnt ihr mich ja aufklären.

    Vielen Dank schon mal im Voraus und euch allen noch eine schönes Wochenende,

    Niki

    Hier mal ein kleines Programm mit dem Kernproblem:

    #include <stdio.h>
    #include <stdlib.h>
    
    struct test
    {
        double a[10][2];
    };
    
    int main()
    {
        int i;
        FILE *output;
        struct test artest;
    
        for(i=0;i<10;i++)
        {
            artest.a[i][1]=i;
            artest.a[i][2]=i+1;
        }
    
        output=fopen("artest.dat","w");
    
        fprintf(output,"#[0]\t[1]\t[2]\n\n");
    
        for(i=0;i<10;i++)
        {
            fprintf(output,"%f\t%f\t%f\n",artest.a[i][0],artest.a[i][1],artest.a[i][2]);
        }
    
        fclose(output);
    
        return 0;
    }
    

    Und die zugehörige Ausgabe:

    #[0] [1] [2]

    0.000000 0.000000 1.000000
    1.000000 1.000000 2.000000
    2.000000 2.000000 3.000000
    3.000000 3.000000 4.000000
    4.000000 4.000000 5.000000
    5.000000 5.000000 6.000000
    6.000000 6.000000 7.000000
    7.000000 7.000000 8.000000
    8.000000 8.000000 9.000000
    9.000000 9.000000 10.000000



  • Zufall.
    Bei mir stürzt da Programm ab.



  • Der Zugriff auf a[9][2] ist undefiniert, damit auch das Verhalten des Programms.

    Sonst aber ist die Sache etwas komplizierter. Die Adressierung a[i] bedeutet (a + i), hierbei ist a + i aufgrund der Zeigerarithmetik die Speicherstelle i * sizeof(*a) Byte hinter (dem Beginn von) a. a[i][j] ist entsprechend *((a + i) + j).

    In konkreten Fall ist der Typ von *a double[2], sizeof(*a) also gleich 2 * sizeof(double). Angenommen, sizeof(double) sei 8 (das ist heute allgemein üblich), dann liegt a + 1 genau 16 Byte hinter a, a + 2 32 Byte etc. Da der Typ von *(a + 1) double ist, liegt *(a + 1) + 1 dann 8 Byte hinter a + 1 bzw. 24 Byte hinter a - so kommt die verschachtelte Adressierung zustande.

    Jetzt vergleichen wir einmal die Fälle a[2][0] und a[1][2] - letzterer sieht ja zunächst illegal aus. Also:

    a[2][0] = ((a + 2) + 0) = der double 2 * sizeof(double[2]) + 0 * sizeof(double) Byte hinter a
    a[1][2] = ((a + 1) + 2) = der double 1 * sizeof(double[2]) + 2 * sizeof(double) Byte hinter a

    Da sizeof(double[2]) == 2 * sizeof(double) gilt, sind diese beiden Ausdrücke äquivalent!

    In deinem Fall gilt allgemein: &a[x][2] == &a[x + 1][0], und erst bei a[9][2] bewegt sich das Programm außerhalb der zulässigen Feldgrenzen.

    Allerdings muss man dazu sagen: Das ist vom Standard zwar so garantiert, aber man sollte schon gute Gründe haben, diese Eigenschaft zu verwenden. Diese Art von Schweinerei wird sehr schnell sehr unübersichtlich.



  • Bei double a[10][2]; ist ein Array und belegt 10 * 2 * sizeof(double) Bytes Speicher. Die Elemente liegen alle direkt hintereinander im Speicher und der ganz rechte Index ändert sich am schnellsten.
    Also a00, a01, a10, a11, ... a81, a90, a91

    seldon schrieb:

    a[i][j] ist entsprechend ((a + i) + j).

    Da müsste dann aber an der Stelle (a+1) ein Zeiger sein. Dort ist aber ein double.

    Statt a[i][j] kann man über Zeiger auch mit (a+i2+j) darauf zugreifen.

    Da ist nichts mit Doppelzeiger.



  • An a + 1 liegt ein double[2], kein double. Das ist in diesem Zusammenhang ein wichtiger Unterschied, weil dieses in *(a + 1) + 2 zu einem Zeiger zerfällt, womit der ganze Kram wieder funktioniert.

    Probier das mal aus. a + i * 2 + j bringt dich nicht zu a[i][j] (sofern i und j nicht sehr günstig gewählt sind).



  • Hallo Dirk und Seldon.

    Vielen Dank euch beiden für die schnellen Antworten.

    Die Erklärungen waren wirklich super und leuchten ein. Ich bin jetzt auf jeden Fall beruhigt und werde den Code des Programms mal dahingehend ändern.

    Ich wünsch euch beiden noch ein schönes Wochenende,

    Gruß Niki


Anmelden zum Antworten