Was ist zweidimensionales Array C-intern?
Was ist zweidimensionales Array C-intern?
Hallo,
vergangene Woche habe in an einem Fortbildungskurs "Programmieren in C" teilgenommen. Bei weiterführenden Übungen am Wochenende stieß ich dann auf folgendes Problem:
Wenn ich einen Vektor definiere, so kann ich das erste Element auch per Pointerzugriff wie folgt benutzen:
char vektor[20];
*vektor = 0;
Versuche ich ähnliches mit einem zweidimensionalen Feld
char feld[10][2];
*feld = 0;
so erhalte ich eine Fehlermeldung. Auch der Versuch, das erste Element mit
feld[0] = 0;
zu belegen ist nicht möglich.
Zu diesem Thema habe ich nun ein paar Fragen:
1) Ich meine mich zu erinnern, dass zumindest der letzte aufgeführte Versuch, auf das erste Element von feld zuzugreifen im Kurs vergangene Woche ging. Während wir dort jedoch mit Visual C++ arbeiteten, machte die oben beschriebenen Erfahrungen mit GCC. Ist es möglich, dass die unterschiedlichen Compiler mehrdimensionale Felder unterschiedlich behandeln?
2) Wie realisiert der GCC ein zweidimensionales Feld? Ist das ein Zeiger auf einen Vektor von Zeigern, die wiederum auf die einzelnen Spalten verweisen?
3) Kann man sicher sein, dass alle Elemente des Feldes lückenlos im Speicher angeordnet sind, so dass z.B. die Initialisierung folgendermaßen gemacht werden kann:
laufzeiger = feld; // bzw. irgendwie sinnvoll auf erste Speicherstelle gesetzt
for (int i = 0; i < anzahl_zeilen * anzahl_spalten; i++) {
*laufzeiger = 0;
laufzeiger++;
}
oder kann der Compiler ein zweidimensionales Feld auch in mehrere Speicherbereiche verteilen?
Patrick
vergangene Woche habe in an einem Fortbildungskurs "Programmieren in C" teilgenommen. Bei weiterführenden Übungen am Wochenende stieß ich dann auf folgendes Problem:
Wenn ich einen Vektor definiere, so kann ich das erste Element auch per Pointerzugriff wie folgt benutzen:
char vektor[20];
*vektor = 0;
Versuche ich ähnliches mit einem zweidimensionalen Feld
char feld[10][2];
*feld = 0;
so erhalte ich eine Fehlermeldung. Auch der Versuch, das erste Element mit
feld[0] = 0;
zu belegen ist nicht möglich.
Zu diesem Thema habe ich nun ein paar Fragen:
1) Ich meine mich zu erinnern, dass zumindest der letzte aufgeführte Versuch, auf das erste Element von feld zuzugreifen im Kurs vergangene Woche ging. Während wir dort jedoch mit Visual C++ arbeiteten, machte die oben beschriebenen Erfahrungen mit GCC. Ist es möglich, dass die unterschiedlichen Compiler mehrdimensionale Felder unterschiedlich behandeln?
2) Wie realisiert der GCC ein zweidimensionales Feld? Ist das ein Zeiger auf einen Vektor von Zeigern, die wiederum auf die einzelnen Spalten verweisen?
3) Kann man sicher sein, dass alle Elemente des Feldes lückenlos im Speicher angeordnet sind, so dass z.B. die Initialisierung folgendermaßen gemacht werden kann:
laufzeiger = feld; // bzw. irgendwie sinnvoll auf erste Speicherstelle gesetzt
for (int i = 0; i < anzahl_zeilen * anzahl_spalten; i++) {
*laufzeiger = 0;
laufzeiger++;
}
oder kann der Compiler ein zweidimensionales Feld auch in mehrere Speicherbereiche verteilen?
Patrick
- peschmae
- Beiträge: 4844
- Registriert: 07.01.2003 12:50:33
- Lizenz eigener Beiträge: MIT Lizenz
- Wohnort: nirgendwo im irgendwo
Code: Alles auswählen
char feld[10][2];
*feld = 0;
Entsprechend versuchst du hier etwas zu machen was äquivalent ist zu
Code: Alles auswählen
char feld[2];
feld = 0;
Dass VC++ das nicht anmeckert ist halt Sache von VC++ - ich denke er sollte meckern. Welche Version war das? Neuere sind glaub ich einiges Standardkonformer als ältere (inbesondere als VC++ 6)
Zu 2) realisiert ist das als ein Block Speicher. Im Fall von char[10][2] sind das jeweils 10 mal ein char[2] nacheinander.
Es ist *nicht* ein Zeiger auf ein Vektor von Zeigern im Sinne von es hat die Pointer nirgendwo im Speicher.
Zu 3) Das kannst du.
Ich hoff ich hab nicht all zu viel durcheinander gebracht. Ist ein haariges Thema.
Zum nachlesen: http://www.eskimo.com/~scs/C-faq/s6.html - da steht einiges an Zeugs dazu. Ist aber schon von 1995 und eventuell teilweise mit C99 nicht mehr korrekt (??)
MfG Peschmä
Zuletzt geändert von peschmae am 12.12.2005 11:41:00, insgesamt 2-mal geändert.
"er hätte nicht in die usa ziehen dürfen - die versauen alles" -- Snoopy
Patibonn hat geschrieben: 1) Ich meine mich zu erinnern, dass zumindest der letzte aufgeführte Versuch, auf das erste Element von feld zuzugreifen im Kurs vergangene Woche ging.
Code: Alles auswählen
char feld[10][2];
feld[0]=0;
*field=0;
error C2106: '=' : left operand must be l-value
sollte so seinPatibonn hat geschrieben: 3) Kann man sicher sein, dass alle Elemente des Feldes lückenlos im Speicher angeordnet sind
Code: Alles auswählen
#define XMAX 5
#define YMAX 7
int main() {
short d2array[XMAX][YMAX];
int i,j;
short *p;
for (i=0; i<XMAX; i++) {
for (j=0; j<YMAX; j++) {
printf(" %02d",&(d2array[i][j])-&(d2array[0][0]));
}
printf("\n");
}
printf("\n");
for (p=&(d2array[0][0]),i=0; i<XMAX*YMAX; i++,p++) {
*p=XMAX*YMAX-1-i;
}
for (i=0; i<XMAX; i++) {
for (j=0; j<YMAX; j++) {
printf(" %02d",d2array[i][j]);
}
printf("\n");
}
return 0;
}
Code: Alles auswählen
00 01 02 03 04 05 06
07 08 09 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 32 33 34
34 33 32 31 30 29 28
27 26 25 24 23 22 21
20 19 18 17 16 15 14
13 12 11 10 09 08 07
06 05 04 03 02 01 00
gms
In dem Kurs vergangene Woche arbeiteten wir, wenn ich richtig hingeschaut habe mit VC++6. Die Zeile, von der ich behauptete, dass der Complier nicht gemeckert hat, habe ich aus der Erinnerung aufgeschrieben. Was genau ich in dem Kurs vergangene Woche getippt hatte kann ich leider nicht mehr rekonstruieren. Einige wesentliche Details waren aber tatsächlich anders als wie zuvor geschildert...
Natürlich, ich gebe zu, daß das schon ganz anders ausschaut. Ob ich auch eine Zuweisung an feld[0] versucht habe weis ich nicht mehr, aber auch das wäre mit type int ja schon eher möglich. Aber keine Angst. Jetzt wo ihr mich aufgeklärt habt, wie das mit den 2D-Arrays so geht, werde ich das nicht wieder probieren. Danke!
Interesse halber aber noch eine Frage:
Geht das und führt es zu dem erwarteten Ergebnis?
Code: Alles auswählen
int feld[10][2];
printf("\d", feld[0]);
Interesse halber aber noch eine Frage:
Code: Alles auswählen
**feld == feld[0][0];
ist zwar so sinnvoll wie eine Abfrage "0==0", aber es funktioniert und liefert immer true.Patibonn hat geschrieben:Interesse halber aber noch eine Frage:Geht das und führt es zu dem erwarteten Ergebnis?Code: Alles auswählen
**feld == feld[0][0];
Oder hast du gemeint "*feld = feld[0][0]" ?
feld[n] (mit einem konstanten Wert für n) wird vom Compiler in "$feld + k" Assemblercode umgewandelt, wobei für k ein konstanter Wert n*sizeof(feld[0][0])*sizeof(feld[0]) eingesetzt wird.Patibonn hat geschrieben:Ob ich auch eine Zuweisung an feld[0] versucht habe weis ich nicht mehr, aber auch das wäre mit type int ja schon eher möglich.
$feld ist die Speicheradresse(d.h konstant) und daher keine l-Value, k ist eine Konstante und auch keine l-Value und ($feld+k) kann daher auch nie eine l-Value sein.
Ob das Feld jetzt vom Type "int" ist oder nicht ist nicht relevant. Das einzige was sich dadurch ändert ist der Wert von k.
Gruß
gms
Zuletzt geändert von gms am 12.12.2005 15:13:54, insgesamt 1-mal geändert.
Nein. Es war einfach als Selbsttest gedacht, ob ich es (wenigstens ansatzweise) verstanden habe. Dabei ging es mir einfach darum, ob ** geht und am Ende die Typen zueinander passen.
Bei dem Versuch *feld=feld[0][0] irgendwo programmtechnisch unterzubringen hätte ich doch von euch oder meinem Compiler wieder Kritik geerntet. *feld ist vom Typ Array-von-zwei-Ints und feld[0][0] vom Typ int. Passt doch nicht, oder habe ich das immer noch nicht verstanden?
Bei dem Versuch *feld=feld[0][0] irgendwo programmtechnisch unterzubringen hätte ich doch von euch oder meinem Compiler wieder Kritik geerntet. *feld ist vom Typ Array-von-zwei-Ints und feld[0][0] vom Typ int. Passt doch nicht, oder habe ich das immer noch nicht verstanden?
Das freut mich, obwohl ich mich leider berichtigen mußPatibonn hat geschrieben:Aha, es kommt Licht ins Dunkel.
Mein vorsichtiges "sollte so sein" war hier berechtigt, das funktioniert zwar beim gcc, vc und ucbcc, dürfte aber nicht im Standard festgehalten worden sein:gms hat geschrieben:sollte so seinPatibonn hat geschrieben: 3) Kann man sicher sein, dass alle Elemente des Feldes lückenlos im Speicher angeordnet sind
Das hatte ich leider falsch in Erinnerung.http://www.open-std.org/jtc1/sc22/wg14/www/docs/n843.htm hat geschrieben: [#3] Successive subscript operators designate an element of
a multidimensional array object. If E is an n-dimensional
array (n>=2) with dimensions i×j× ... ×k, then E (used as
other than an lvalue) is converted to a pointer to an
(n-1)-dimensional array with dimensions j× ... ×k. If the
unary * operator is applied to this pointer explicitly, or
implicitly as a result of subscripting, the result is the
pointed-to (n-1)-dimensional array, which itself is
converted into a pointer if used as other than an lvalue.
It follows from this that arrays are stored in row-major
order (last subscript varies fastest).
Gruß
gms
Verstehe ich richtig: Die Tatsache, dass nicht explizit da steht, dass die Werte lückenlos im Speicher stehen, kann bedeuten, dass es evtl. auch mal anders sein kann. Da merke ich mir aber lieber "solte so sein" und hoffe, dass ich merke, wenn's mal anders ist.
Um das Gelernte weiter zu vertiefen habe ich noch etwas geübt und dabei ein Codebeispiel erstellt, das mir beim zweiten Nachdenken selbst etwas Bauchschmerzen bereitet:
Zunächst stelle ich fest, dass die Funktion offensichtlich macht, was sie soll. Was genau ich mit *f++ jedoch bewirke ist mir nicht klar. Bei der Übergabe des Funktionsparameters f wird ein Zeiger auf f übergeben. Es stellt sich mir also die Frage, ob ich *f überhaupt ändern darf, oder ob ich damit die innere Struktur des Feldes manipuliere. Schließlich ist *f ja nicht eine Kopie des übergebenen Feldes sondern möglicherweise selbst ein Teil des Feldes? Oder legt der Kompiler für *f eine lokale Variable an, die er entsprechend der Felddefinition behandelt? Tests, bei denen ich auf die oben gezeigte Weise verschiedene Werte in das Feld geschrieben und anschließend angeschaut habe, verliefen problemlos.
Gruß
Patrick
Um das Gelernte weiter zu vertiefen habe ich noch etwas geübt und dabei ein Codebeispiel erstellt, das mir beim zweiten Nachdenken selbst etwas Bauchschmerzen bereitet:
Code: Alles auswählen
void null_setzen(int f[3][4])
{
int i, j;
int *p;
for (i=0; i<3; i++) // jede Spalte
{
p = *f; // *f, vom Typ array-von-vier-Ints, wird p zugewiesen
// p zeigt also auf die aktuelle Spalte bzw. ihr erstes Element
for (j=0; j<4; j++)
{
*p = 0; // aktuelles Element wird initialisiert
p++;
}
*f++; // fortsetzen mit nächster Spalte
}
}
Gruß
Patrick
Das wirft nun leider wieder neue Fragen bei mir auf.
Dann ändert sich das Feld in der aufrufenden Funktion nicht.
Aber was ist *f? Stimmt mir jemand zu folgender These zu?
*f ist eine vom Compiler erzeugte lokale Variable vom Typ Array-von-vier-Ints, die als Zeiger betrachtet auf das erste Element in f zeigt.
Wenn das so ist, brauche ich ja keine Bauchschmerzen mehr wegen meiner Funktion null_setzen() haben.
Abschließend noch einen Kommentar zu
Freundlichen Gruß
Patrick
So habe ich das gelernt, Funktionsparameter werden als Kopie übergeben. Also f ist eine lokale Kopie. Wenn ich also in der Funktion null_setzen() folgendes befehle:f ist eine lokale variable (liegt am stack)
Code: Alles auswählen
int g[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
f = g;
Aber was ist *f? Stimmt mir jemand zu folgender These zu?
*f ist eine vom Compiler erzeugte lokale Variable vom Typ Array-von-vier-Ints, die als Zeiger betrachtet auf das erste Element in f zeigt.
Wenn das so ist, brauche ich ja keine Bauchschmerzen mehr wegen meiner Funktion null_setzen() haben.
Abschließend noch einen Kommentar zu
Es funktioniert, falls Spalten im Speicher in gleichen Abständen abgelegt sind.Das ist übrigens ein gutes Beispiel, wie dein Code trotzdem funktioniert, auch wenn die Annahme, daß alle Felder lückenlos im Speicher sind, nicht erfüllt ist.
Freundlichen Gruß
Patrick
Diese Zuweisung von "f = g" in der Funktion "null_setzen" habe ich nicht ganz verstanden. Schaut einerseits so aus, als ob du dein Feld auf das die lokale Variable f zeigt mit g initialisieren möchtest, das geht so sicher nicht.
Gruß
gms
Dieser These stimme ich zuPatibonn hat geschrieben:Stimmt mir jemand zu folgender These zu?
*f ist eine vom Compiler erzeugte lokale Variable vom Typ Array-von-vier-Ints, die als Zeiger betrachtet auf das erste Element in f zeigt.
das muß der Compiler auch gewährleisten. In deinem Beispiel wird mit f++ der zeiger immer um diesen Abstand erhöhtPatibonn hat geschrieben:Es funktioniert, falls Spalten im Speicher in gleichen Abständen abgelegt sind.Das ist übrigens ein gutes Beispiel, wie dein Code trotzdem funktioniert, auch wenn die Annahme, daß alle Felder lückenlos im Speicher sind, nicht erfüllt ist.
Gruß
gms
- peschmae
- Beiträge: 4844
- Registriert: 07.01.2003 12:50:33
- Lizenz eigener Beiträge: MIT Lizenz
- Wohnort: nirgendwo im irgendwo
Also das f in der Funktion reset(char f[])? Das *ist* sogar ein Pointer. Also ein Pointer-auf-vier-Ints.Patibonn hat geschrieben:Aber was ist *f? Stimmt mir jemand zu folgender These zu?
*f ist eine vom Compiler erzeugte lokale Variable vom Typ Array-von-vier-Ints, die als Zeiger betrachtet auf das erste Element in f zeigt.
D.h. da musst du nicht "wenn man das als Pointer betrachtet" denken
Es heisst ja auch immer: reset(char f[]) und reset(char *f) sind gleichwertig. Deshalb kannst du z.B. auch f inkrementieren. (Was nicht ginge wenn f ein Arrayname wäre und du den "als const Pointer betrachtest" )
MfG Peschmä
"er hätte nicht in die usa ziehen dürfen - die versauen alles" -- Snoopy