C Pointer-Arithmetik bei dynamischen zweidimensionalen Arrays

zaph42

Cadet 1st Year
Registriert
Mai 2012
Beiträge
8
Hallo,
ich habe leider ein Verständnisproblem bei der Speicheraddressierung dynamischer Arrays in C: Und zwar versuche ich mit einem einzigen Pointer Elemente eines Arrays anzusprechen. Mir ist dabei aufgefallen, dass am Ende jeder "Zeile" ein zusätzlicher Speicherplatz zu existieren scheint.

An sich sollte doch durch die im folgenden Beispiel verwendete Methode, zur Erstellung dynamischer Arrays, zunächst ein einzelner Pointer erstellt werden, der wiederum auf ein Array von Pointern zeigt und jene wiederum auf die eigentlichen Zahlen. Die Zahlen sollten doch auch zusammenhängend im Speicher liegen und mit einem einzelnen Pointer ansprechbar sein, oder liegt hier schon mein Fehler?

Mein Problem erkennt man am besten an folgendem minimal Beispiel:

Code:
#include <stdio.h>
#include <stdlib.h>

#define RD_ZEILEN  4
#define RD_SPALTEN 3

int main (int argc, char *argv[])
{   
    int i,j,n;    
    double** rd = (double**)malloc(  RD_ZEILEN * sizeof(double*));
	for (i=0; i<RD_ZEILEN; i++)	rd[i] = (double*)malloc( RD_SPALTEN * sizeof(double) );

    n=1;
    double* p= &(rd[0][0]);
    for (i=0; i < RD_ZEILEN*RD_SPALTEN; ++i){
            *p++ = n++;
    }
    
    for (i=0; i < RD_ZEILEN; i++){
        for (j=0; j < RD_SPALTEN; j++)
            printf("%.0f\t", rd[i][j] );
        printf("\n");
    }
   
    return 0;
}
Wenn ich das Program ausführe bekomm ich folgenden output:
1 2 3
5 6 7
9 10 11
13 14 15
Verändere ich die Ausgabe auf:
Code:
 for (i=0; i < RD_ZEILEN; i++){
        for (j=0; j < RD_SPALTEN+1; j++){
            printf("%.0f\t", rd[i][j] );
        }
        printf("\n");
    }
erhalte ich:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
An sich würde ich denken, dass die einzelnen "Zeilen" nun "random" im Speicher angelegt werden und man eben nicht so einfach mit einem einzelnen Pointer durch das Array gehn kann. Aber warum ist ausgerechet nur ein Speicherplatz jeweils zwischen den "Zeilen" und warum gibt es keine Speicherzugriffsfehler?
Des ganze verwirrt mich irgendwie :)
Vielen Dank für die Antworten schonmal im Vorraus!
zur Info ich verwende ubuntu 13.10 und gcc-4.8.1
 
Hm, ich wüsste auch gerne, warum das klappt. Vermutlich stellt der Compiler dort Optimierungen an oder erkennt deine Absicht und schreibt den Code entsprechend um. Wenn ich das unter Windows mit VCPP kompiliere kommt genau das raus, was man erwartet: Die ersten 3 Werte sind sinnvoll, danach kommt ne Weile nur Junk und später taucht auf einmal wieder ein sinnvoller Wert auf (man hat so viel Junk gelesen, dass man wieder in den belegten Speicherbereichen ist).

Eventuell kannst du ja versuchen, einen Debugger anzuschmeißen (falls gcc sowas hat) und dir mal die Adressen der Zeiger ausgeben zu lassen. Bzw. schreibst du in den Code rein, dass dir die Adressen angezeigt werden sollen. So kommst du dem ganzen auf die Schliche.
 
Speicher wird in Blöcken vom OS besorgt, d.h. wenn man sich 10bytes besorgt wird ein grösserer Block vom OS angefordert, mit dem man auch "arbeiten" kann.

Im der Programm wird der Speicher einmal als 2D Array behandelt, und ein anderes Mal als ein 1D Arrary von Pointern auf 1D Arrays. Das sind komplett unterschiedliche Sachen.
 
Mich wundert, dass du überhaupt so eine zusammenhängende Ausgabe erhälst. Ich hätte eher auf ein Verhalten wie das von SoDaTierchen getippt.
Schau dir nochmal an wie das mit der Speicherallokation funktioniert.

Du forderst 5mal unabhängig voneinander Speicher an. Jedesmal für sich gesehen wird dir natürlich ein zusammenhängender Block reserviert, aber dass alle 5 Blöcke hintereinander im Speicher liegen ist so nicht vorgesehen.
 
zaph42 schrieb:
Code:
#include <stdio.h>
#include <stdlib.h>

#define RD_ZEILEN  4
#define RD_SPALTEN 3

int main (int argc, char *argv[])
{   
    int i,j,n;    
    double** rd = (double**)malloc(  RD_ZEILEN * sizeof(double*));
	for (i=0; i<RD_ZEILEN; i++)	rd[i] = (double*)malloc( RD_SPALTEN * sizeof(double) );

    n=1;
    double* p= &(rd[0][0]);
    for (i=0; i < RD_ZEILEN*RD_SPALTEN; ++i){
            *p++ = n++;
    }
    
    for (i=0; i < RD_ZEILEN; i++){
        for (j=0; j < RD_SPALTEN; j++)
            printf("%.0f\t", rd[i][j] );
        printf("\n");
    }
   
    return 0;
}

Da sind ein paar Fehler und Zufälle drin. Du reservierst zunächst Platz für 4 Zeiger. Jedem dieser Speicherplätze/Zeiger wird dann je ein Zeiger auf 3 double-Werte (d.h. 3*8 Byte = 24) zugewiesen. Abhängig von der malloc-Implementierung können diese 4 Zeiger irgendwohin in den Speicher zeigen. Sie müssen also nicht auf direkt hintereinander liegende Speicherbereiche zeigen, wenn malloc direkt in Folge aufgerufen wird. Prinzipiell sind die Adressen aber immer passend ausgerichtet. Auf einem 64-Bit System gilt also Adresse % 8 = 0 (reservierst du also Platz für 3 Byte, wird auf einer 64-Bit Kiste der nächste malloc-Aufruf eine Adresse liefern, die mindestens 8 Byte davon entfernt ist).
Für das Beispiel ist es allerdings so, dass die Zeiger wahrscheinlich oft fast direkt hintereinander liegen. Auf meiner Kiste liegen z.B. 8 Byte zwischen den 4 Zeigern. Diese 8 Byte könnten z.b. intern von malloc verwendet werden. Sie könnten aber auch ein Indikator sein, um Überläufe zu detekieren (z.B. mit valgrind). Dazu müsste man sich die malloc-Implementierung mal anschauen oder sich eben selbst eine schreiben :)

Die Schleife auf Zeile 15. behandelt dann allerdings den gesamten Speicherplatz (beginnend bei &rd[0][0]) so, als wäre es ein zusammenhängendes (ZEILEN*SPALTEN-) Feld. Das ist aber eben nicht der Fall und so werden die 8 "unbekannten" und nicht dir gehörenden Bytes beschrieben.
Der Array-Operator auf Zeile 25 behandelt den Speicherplatz dann wie ein mehrdimensionales C-Array. Diese sind per Definition zusammenhängend, unabhängig davon, wieviele Dimensionen verwendet werden. Dieser Fall ist also ähnlich zur Schleife an Zeile 15. Auch damit greifst du also falsch auf den Speicherplatz zu.
 
Zuletzt bearbeitet:
@aphex.matze:
Nöö, ein [] Operator dereferenziert den Pointer (a ist also äquvivalent zu *(a+i)).
Code:
double**p=malloc(...);
double *pp=p[i];
double ppp1=pp[j];
double ppp2=pp[i][j];//=(pp[i])[j];
Zeile 14 ff. ist natürlich trotzdem falsch, da sollte die normale geschachtelte for Schleife zum Einsatz kommen.
Code:
for(int j=0;j<ZEILEN;++j)
for(int i=0;i<SPALTEN;++i)
p[j][i]=...;

Dass die Speicherplätze hintereinanderliegen, kommt häufig vor (vor allem, wenn ein Programm "frisch" läuft und wenig Lücken auf dem Heap vorhanden sind). Dass du keine Speicherzugriffsverletzungen bekommst, liegt einfach da dran, dass du halt was anderes gültiges erwischst. Was das ist, ist natürlich "undefined".
 
Zuletzt bearbeitet:
Zurück
Oben