Was ist zweidimensionales Array C-intern?

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Patibonn
Beiträge: 6
Registriert: 12.12.2005 08:25:34
Wohnort: Hattingen

Was ist zweidimensionales Array C-intern?

Beitrag von Patibonn » 12.12.2005 09:21:15

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

Benutzeravatar
peschmae
Beiträge: 4844
Registriert: 07.01.2003 12:50:33
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: nirgendwo im irgendwo

Beitrag von peschmae » 12.12.2005 11:34:34

Code: Alles auswählen

char feld[10][2];
*feld = 0;
Gcc mag das nicht wegen dem Typ. *feld ist hat den Typ "Array-von-zwei-Chars" also char[2] und nicht einfach char.

Entsprechend versuchst du hier etwas zu machen was äquivalent ist zu

Code: Alles auswählen

char feld[2];
feld = 0;
Das geht genau so wenig weil auch hier feld kein char ist sondern ein char[2].

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

gms
Beiträge: 7798
Registriert: 26.11.2004 20:08:38
Lizenz eigener Beiträge: MIT Lizenz

Beitrag von gms » 12.12.2005 11:36:14

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;
liefert auch unter visual c++ bei beiden Zuweisungen den Fehler:
error C2106: '=' : left operand must be l-value
Patibonn hat geschrieben: 3) Kann man sicher sein, dass alle Elemente des Feldes lückenlos im Speicher angeordnet sind
sollte so sein

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;
}

Ausgabe: der erste Block zeigt, daß alle Felder hintereinander liegen, der zweite Block zeigt, daß die Initialisierung auch wirklich funktioniert.

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
Gruß
gms

Patibonn
Beiträge: 6
Registriert: 12.12.2005 08:25:34
Wohnort: Hattingen

Beitrag von Patibonn » 12.12.2005 12:24:11

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...

Code: Alles auswählen

int feld[10][2];
printf("\d", feld[0]);
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:

Code: Alles auswählen

**feld == feld[0][0];
Geht das und führt es zu dem erwarteten Ergebnis?

gms
Beiträge: 7798
Registriert: 26.11.2004 20:08:38
Lizenz eigener Beiträge: MIT Lizenz

Beitrag von gms » 12.12.2005 12:35:48

Patibonn hat geschrieben:Interesse halber aber noch eine Frage:

Code: Alles auswählen

**feld == feld[0][0];
Geht das und führt es zu dem erwarteten Ergebnis?
ist zwar so sinnvoll wie eine Abfrage "0==0", aber es funktioniert und liefert immer true.
Oder hast du gemeint "*feld = feld[0][0]" ?

gms
Beiträge: 7798
Registriert: 26.11.2004 20:08:38
Lizenz eigener Beiträge: MIT Lizenz

Beitrag von gms » 12.12.2005 13:12:16

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[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.
$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.

Patibonn
Beiträge: 6
Registriert: 12.12.2005 08:25:34
Wohnort: Hattingen

Beitrag von Patibonn » 12.12.2005 13:13:54

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?

gms
Beiträge: 7798
Registriert: 26.11.2004 20:08:38
Lizenz eigener Beiträge: MIT Lizenz

Beitrag von gms » 12.12.2005 13:24:42

Patibonn hat geschrieben:*feld ist vom Typ Array-von-zwei-Ints
"*feld", "feld[0]" oder "feld" entspricht immer der Anfangsadresse, das einige was sich ändert:
*feld und feld[0] sind vom Typ *int, bzw int[]
und feld ist vom Typ **int bzw int[][]

Gruß
gms

Patibonn
Beiträge: 6
Registriert: 12.12.2005 08:25:34
Wohnort: Hattingen

Beitrag von Patibonn » 12.12.2005 13:49:45

Aha, es kommt Licht ins Dunkel. Danke nochmals für die vielen Infos. Das wird mir sicher weiterhelfen.


Gruß
Patrick

gms
Beiträge: 7798
Registriert: 26.11.2004 20:08:38
Lizenz eigener Beiträge: MIT Lizenz

Beitrag von gms » 12.12.2005 17:28:59

Patibonn hat geschrieben:Aha, es kommt Licht ins Dunkel.
Das freut mich, obwohl ich mich leider berichtigen muß
gms hat geschrieben:
Patibonn hat geschrieben: 3) Kann man sicher sein, dass alle Elemente des Feldes lückenlos im Speicher angeordnet sind
sollte so sein
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:
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).
Das hatte ich leider falsch in Erinnerung.

Gruß
gms

Patibonn
Beiträge: 6
Registriert: 12.12.2005 08:25:34
Wohnort: Hattingen

Beitrag von Patibonn » 13.12.2005 12:02:30

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:

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 
	}
}
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

gms
Beiträge: 7798
Registriert: 26.11.2004 20:08:38
Lizenz eigener Beiträge: MIT Lizenz

Beitrag von gms » 13.12.2005 12:10:39

f ist eine lokale variable (liegt am stack).
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

Patibonn
Beiträge: 6
Registriert: 12.12.2005 08:25:34
Wohnort: Hattingen

Beitrag von Patibonn » 13.12.2005 13:40:07

Das wirft nun leider wieder neue Fragen bei mir auf.
f ist eine lokale variable (liegt am stack)
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:

Code: Alles auswählen

int g[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
f = g;
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
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.
Es funktioniert, falls Spalten im Speicher in gleichen Abständen abgelegt sind.

Freundlichen Gruß

Patrick

gms
Beiträge: 7798
Registriert: 26.11.2004 20:08:38
Lizenz eigener Beiträge: MIT Lizenz

Beitrag von gms » 13.12.2005 14:50:20

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.
Patibonn 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.
Dieser These stimme ich zu
Patibonn hat geschrieben:
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.
Es funktioniert, falls Spalten im Speicher in gleichen Abständen abgelegt sind.
das muß der Compiler auch gewährleisten. In deinem Beispiel wird mit f++ der zeiger immer um diesen Abstand erhöht

Gruß
gms

Benutzeravatar
peschmae
Beiträge: 4844
Registriert: 07.01.2003 12:50:33
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: nirgendwo im irgendwo

Beitrag von peschmae » 13.12.2005 19:02:07

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.
Also das f in der Funktion reset(char f[])? Das *ist* sogar ein Pointer. Also ein Pointer-auf-vier-Ints.
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

Antworten