Zeiger und mehrdimensionale Arrays

Schnelle objektorientierte, kompilierende Programmiersprache.
MoonGuy
Beiträge: 231
Registriert: Fr Okt 08, 2010 2:49 pm

Re: Zeiger und mehrdimensionale Arrays

Beitrag von MoonGuy » Di Nov 09, 2010 10:51 pm

Xin hat geschrieben:
AnGaiNoR hat geschrieben:Was meinst du denn mit "lahm"?
Naja, es findet eine Multiplikation statt, die gerade bei alten CPUs sehr teuer war.
Bei alten CPUs dachte niemand daran, dass jemand mit C/++ riesige, viele oder oft MDA's erstellt(allein, weil damals der RAM überaus begrenzt war) und diese dann auch noch mit Pointern durchzugehen versucht.
(keinen historischen Beleg dafür; wieso mailt man nicht den Erfindern? :D)


Natürlich kann dies auch einfach eine weitere Schwäche, aber gleichzeitig "Einfachheit", der Sprache aufdecken.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Zeiger und mehrdimensionale Arrays

Beitrag von Xin » Di Nov 09, 2010 11:00 pm

MoonGuy hat geschrieben:Bei alten CPUs dachte niemand daran, dass jemand mit C/++ riesige, viele oder oft MDA's erstellt(allein, weil damals der RAM überaus begrenzt war) und diese dann auch noch mit Pointern durchzugehen versucht.
(keinen historischen Beleg dafür; wieso mailt man nicht den Erfindern? :D)
Vorsicht... C wurde für Unix programmiert und so eine Unix-Maschine hatte auch schnell mal ein MB RAM.
Da passt schon einiges rein.
MoonGuy hat geschrieben:Natürlich kann dies auch einfach eine weitere Schwäche, aber gleichzeitig "Einfachheit", der Sprache aufdecken.
Es wundert mich derzeit etwas, weil es eine semantische Merkwürdigkeit beschreibt.

Aber das ist bei Arrays sowieso alles etwas schwammig formuliert.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: Zeiger und mehrdimensionale Arrays

Beitrag von Kerli » Mi Nov 10, 2010 12:44 am

Damit ich jetzt keinen Blödsinn erzähle habe ich zuerst einmal einen Blick in den Standard geworfen (Ist übrigens immer gut eine Version davon griffbereit zu haben :)) Zuerst einmal ein paar Dinge zur Klarstellung:
  • Arrays werden immer Zeilenweise im Speicher abgelegt. Heißt also der bei einem linearen Durchlauf des vom Array verwendeten Speichers, ändert sich der letzte Index am schnellsten.
  • Der 'sizeof' Operator liefert bei Arrays immer die Größe des Arrays. Ein char*[10] ist demnach kleiner als ein char[10][10], da im ersten Fall ja nur Platz für 10 Charzeiger (für 32 Bit 10*4=40 Byte) benötigt wird, während im zweiten Fall Platz für 100 Chars (100*1=100Byte) benötigt werden.
    Bei mir ergibt zb der Ausdruck "sizeof(char[4][8]) == sizeof(char*[4])" wahr, da ein Zeiger auf einer 64-Bit Architektur mit 64 Bit genauso groß wie 8 Chars zu je ein Byte ist.
  • Der Indexoperator [] angewendet auf ein Array 'N' vom Grad 'n' soll sich bei Verwendung von einem Index 'i' gleich verhalten wie *(N + i). Aus diesem Grund wird bei der Evaluierung eines oder mehreren Indexoperatoren von Links nach Rechts wie folgt vorgegangen:
    Zuerst wird der N implizit zu einem Array mit dem Grad n-1 konvertiert (Also einen Zeiger auf ein Array mit dem Grad n-1) und dann der Offset entsprechend des neuen Arraytyps berechnet und dazu addiert. Bei einem Array 'int N[4][8]' führt also der Zugriff N dazu das zuerst aus N in ein eindimensionales Array mit 4 Elementen der Größe je 8*sizeof(int) konvertiert wird und der Ausdruck zu *(N + i * sizeof(int[8])) evaluiert.
    Sollte anschließend noch ein weiterer Zugriff mit dem Indexoperator folgen wird wieder gleich wie gerade beschrieben vorgegangen.


Und jetzt noch ein paar Kommentare zu abgegebenen Statements :)

Xin hat geschrieben:Aber das ist bei Arrays sowieso alles etwas schwammig formuliert.

Eigentlich nicht. Im Standard (ich beziehe mich hier auf den C++ Working Draft aus dem Jahr 2005) ist eigentlich alles sehr genau beschrieben. Sofern es Implementationsabhängige Sachen gibt steht es auch ausdrücklich dabei, was bei Arrays aber eigentlich eher weniger der Fall ist.

Xin hat geschrieben:Ich bin mir hingegen sicher, dass feld[3][5] was anderes ist als feld[3*5].

Ja, aber nur was den Zugriff betrifft, im Speicher schauen Beide gleich aus. Beim zweiten Feld wäre zb. feld[1][1] nicht erlaubt...

Xin hat geschrieben:Ein char feld[3*5] ist ein char feld[15] und damit ein char * auf 15 Bytes.

Nein. Ein char feld[15] ist kein char* auf 15 Bytes sondern ein char feld[15] kann implizit nach char* konvertiert werden um damit auf die reservierten 15 Bytes ohne Speicherverletzungen zugreifen zu können.

Xin hat geschrieben:Ein char feld[3][5] ist aber ein char * feld[3] oder ein char ** feld auf 12 Bytes (3 Pointer)

Achtung. Hier gilt natürlich wieder das gleiche wie oben.

Dirty Oerti hat geschrieben:Hm, dann erklär mir mit dieser Logik bitte folgendes Verhalten:

Code: Alles auswählen

  printf("Feldinhalt, Ausgabe durch 1 (!) Zeiger:\n{ ");
  for(i=0; i < (3*4); i++)
  {
    zeiger = (char*)feld;
Hier musst du aufpassen. 'feld' wird implizit zu char** konvertiert was du dir durch den Cast noch weiter zu char* konvertierst und somit einen Zeiger für den Byte-weisen Zugriff erhältst.

So, ich hoffe damit sind jetzt einmal alle Unklarheiten beseitigt :P
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Benutzeravatar
Dirty Oerti
Beiträge: 2229
Registriert: Di Jul 08, 2008 5:05 pm
Wohnort: Thurndorf / Würzburg

Re: Zeiger und mehrdimensionale Arrays

Beitrag von Dirty Oerti » Mi Nov 10, 2010 1:41 am

Hm, ich weiß jetzt auch, wo sich die Angelegenheit so verhalten würde (müsste) wie Xin es beschrieben hat: Bei JAVA

Dort ist TYPE name[a][c] gleich bedeutend mit (TYPE[a]) [c] gleich bedeutend mit ((TYPE[a]))[c]
Sprich hier enthält die "oberste" Stufe (also in diesem Fall die mit Index c) Zeiger auf Arrays vom TYPE[a]
Diese enthalten Zeiger auf Arrays vom TYPE[a]
Und das ist schließlich ein Array mit Elementen von TYPE

So zumindest, wie ich es gelernt habe ...
Bei Fragen einfach an daniel[ät]proggen[Punkt]org
Ich helfe gerne! :)
----------
Wenn du ein Licht am Ende des Tunnels siehst, freu dich nicht zu früh! Es könnte ein Zug sein, der auf dich zukommt!
----
It said: "Install Win95 or better ..." So I installed Linux.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Zeiger und mehrdimensionale Arrays

Beitrag von Xin » Mi Nov 10, 2010 2:09 pm

Kerli hat geschrieben:
Xin hat geschrieben:Aber das ist bei Arrays sowieso alles etwas schwammig formuliert.
Eigentlich nicht. Im Standard (ich beziehe mich hier auf den C++ Working Draft aus dem Jahr 2005) ist eigentlich alles sehr genau beschrieben. Sofern es Implementationsabhängige Sachen gibt steht es auch ausdrücklich dabei, was bei Arrays aber eigentlich eher weniger der Fall ist.
Ein char bla[2]; ist ein Primitiv aus zwei chars. Das kann man so aber nicht übergeben. Man muss die Adresse übergeben. Die Adresse dieses Primitivs mit den zwei Chars ist?
&bla oder bla?

Wenn man dann bla an eine Methode übergibt, die ein char parameter[] als Array bekommt. Bekommt man dann die beiden chars oder die Adresse?

Und wenn ein char bla[2] ein Primitiv bestehend aus zwei chars ist, dann ist bla[0] ein char. Aber wenn bla ein char * ist, dann ist bla[0] ein char. Was, wenn bla ein char [] ist? Passiert bei char bla[2] und char * bla das gleiche wenn man bla[0] schreibt? Wenn ja - sind sie gleich? Wenn nein, warum kann man sie aufeinander zuweisen? Und kann man sie überhaupt aufeinander zuweisen?

Ich hatte Arrays in Genesys bereits nach C-Manier implementiert. Und später habe ich die Kompatiblität zur C-Syntax rausgeworfen - inkl. der Arrays.
Die Fragen sollen zum Nachdenken anregen, nicht zwangsläufig zum Nachprüfen. Wenn Dir alle Antworten spontan und logisch in den Kopf kommen - nicht weil Du sie auswendig gelernt hast, sondern weil Du sie Dir erklären kannst, dann passt es wohl.
Wenn Du nachdenken musst, dann ist es schwammig umgesetzt.
Ich musste nachdenken, als ich es implementieren wollte.
Kerli hat geschrieben:
Xin hat geschrieben:Ich bin mir hingegen sicher, dass feld[3][5] was anderes ist als feld[3*5].
Ja, aber nur was den Zugriff betrifft, im Speicher schauen Beide gleich aus. Beim zweiten Feld wäre zb. feld[1][1] nicht erlaubt...
Und ist im ersten Fall feld[1] erlaubt und wenn ja, was kommt als Datentyp raus? Ein Primitiv oder ein Pointer?
Kerli hat geschrieben:So, ich hoffe damit sind jetzt einmal alle Unklarheiten beseitigt :P
Noch nicht ganz, aber Du machst das bisher gut :-)
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: Zeiger und mehrdimensionale Arrays

Beitrag von Kerli » Do Nov 11, 2010 12:29 am

Xin hat geschrieben:Ein char bla[2]; ist ein Primitiv aus zwei chars. Das kann man so aber nicht übergeben. Man muss die Adresse übergeben. Die Adresse dieses Primitivs mit den zwei Chars ist?
&bla oder bla?
&bla[0] ;) Nein, hier hast du wohl recht. Da ist das ganze zwar genau festgelegt, aber wohl teilweise mit sehr vielen alternativen Schreibweisen bzw. teilweise wohl etwas unintuitivem Verhalten wie zb bei der Übergabe von bla[n], wo es eigentlich logischer wäre das per Value und nicht nur per Reference zu übergeben. Vermutlich hat hier wohl die Effizienz ihren Einfluss auf diese Entscheidung gehabt...
Xin hat geschrieben:Wenn man dann bla an eine Methode übergibt, die ein char parameter[] als Array bekommt. Bekommt man dann die beiden chars oder die Adresse?
Die Adresse.

Man könnte Array ja eigentlich auch anstatt ein Primitiv als eine Schreibweise zur Verwaltung von Speicher auf dem Stack betrachten und somit mit char bla[2]; eine Zeiger auf einen Speicherbereich für zwei chars zu erhalten. Bei dieser Ansicht passt dann aber das Verhalten von sizeof nicht mehr so ganz hinein. Wie gesagt, hier hat man wohl eher auf Effizienz und die häufigsten Anwendung gedacht und dabei wohl die eine oder andere Unstimmigkeit in Kauf genommen.
Xin hat geschrieben:Und wenn ein char bla[2] ein Primitiv bestehend aus zwei chars ist, dann ist bla[0] ein char. Aber wenn bla ein char * ist, dann ist bla[0] ein char. Was, wenn bla ein char [] ist? Passiert bei char bla[2] und char * bla das gleiche wenn man bla[0] schreibt? Wenn ja - sind sie gleich? Wenn nein, warum kann man sie aufeinander zuweisen? Und kann man sie überhaupt aufeinander zuweisen?
Sie sind nicht gleich, da bei char* bla im Gegensatz zu char bla[2] kein Speicherplatz für chars reserviert wird. Die Zuweisung kann man zb auch in Anlehnung an die Objektorientierte Programmierung erklären. Du kannst ja auch einem Zeiger der Basisklasse einen Zeiger eines abgeleiteten Objekts zuweisen. Genauso kannst du bei einem Array die Anfangsadresse einem Zeiger auf den Typ dieses Arrays zuweisen.
Xin hat geschrieben:
Kerli hat geschrieben:
Xin hat geschrieben:Ich bin mir hingegen sicher, dass feld[3][5] was anderes ist als feld[3*5].
Ja, aber nur was den Zugriff betrifft, im Speicher schauen Beide gleich aus. Beim zweiten Feld wäre zb. feld[1][1] nicht erlaubt...
Und ist im ersten Fall feld[1] erlaubt und wenn ja, was kommt als Datentyp raus? Ein Primitiv oder ein Pointer?
feld ist ein Array mit drei Elementen, die wieder selbst Arrays sind mit je 5 Elementen. Mit feld[1] nimmst erhältst du also ein Array bestehend aus 5 Elementen (Das zweite) und dieses kann wieder implizit in einen Zeiger des selben Typs konvertiert werden, der dann auf das erste Feld des Arrays zeigt.
Mit feld[1] erhältst du also einen Zeiger auf den Anfang des zweiten Elements im Array mit drei Elementen. Im Gegensatz dazu würdest du mit ((char*)feld)[1] das zweite Element des ersten Arrays erhalten.
Xin hat geschrieben:Noch nicht ganz, aber Du machst das bisher gut :-)
Dann hoffe ich, dass es mir auch weiterhin gut gelingt :P
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Zeiger und mehrdimensionale Arrays

Beitrag von Xin » Do Nov 11, 2010 11:21 am

Kerli hat geschrieben:
Xin hat geschrieben:Ein char bla[2]; ist ein Primitiv aus zwei chars. Das kann man so aber nicht übergeben. Man muss die Adresse übergeben. Die Adresse dieses Primitivs mit den zwei Chars ist?
&bla oder bla?
&bla[0] ;) Nein, hier hast du wohl recht. Da ist das ganze zwar genau festgelegt, aber wohl teilweise mit sehr vielen alternativen Schreibweisen bzw. teilweise wohl etwas unintuitivem Verhalten wie zb bei der Übergabe von bla[n], wo es eigentlich logischer wäre das per Value und nicht nur per Reference zu übergeben. Vermutlich hat hier wohl die Effizienz ihren Einfluss auf diese Entscheidung gehabt...
Definitiv, aber die Semantik ist falsch. Performance hin oder her, das ganze ist etwas schwammig. Möchte ich eine Koordinate übergeben, dann kann das ein double[2] sein. Wenn ich nun einen Zeiger übergebe und in der Funktion viel damit arbeite, dann wäre es performanter die zwei Doubles - wie es die Semantik aussagt - auf den Stack zu packen und gut, statt einen Zeiger zu generieren, diesen auf den Stack zu legen und bei jedem Zugriff erst zu dereferenzieren.

Du musst entweder ein typedef nachreichen oder das Array, dass die Koordinate bezeichnet in zwei doubles aufteilen. Alternativ kopierst Du vor der Verwendung die beiden Elemente in neue Variablen auf den Stack, um die Dereferenzierung zu vermeiden.

An der Stelle hat man teilweise - und je nach Kompiler - etwas merkwürdiges Verhalten.
Kerli hat geschrieben:Man könnte Array ja eigentlich auch anstatt ein Primitiv als eine Schreibweise zur Verwaltung von Speicher auf dem Stack betrachten und somit mit char bla[2]; eine Zeiger auf einen Speicherbereich für zwei chars zu erhalten. Bei dieser Ansicht passt dann aber das Verhalten von sizeof nicht mehr so ganz hinein.
Wenn das Verhalten von sizeof nicht passt, dann passt die Ansicht nicht. Oder das ganze ist an der Stelle schwammig? ^^
Kerli hat geschrieben:
Xin hat geschrieben:Und wenn ein char bla[2] ein Primitiv bestehend aus zwei chars ist, dann ist bla[0] ein char. Aber wenn bla ein char * ist, dann ist bla[0] ein char. Was, wenn bla ein char [] ist? Passiert bei char bla[2] und char * bla das gleiche wenn man bla[0] schreibt? Wenn ja - sind sie gleich? Wenn nein, warum kann man sie aufeinander zuweisen? Und kann man sie überhaupt aufeinander zuweisen?
Sie sind nicht gleich, da bei char* bla im Gegensatz zu char bla[2] kein Speicherplatz für chars reserviert wird. Die Zuweisung kann man zb auch in Anlehnung an die Objektorientierte Programmierung erklären. Du kannst ja auch einem Zeiger der Basisklasse einen Zeiger eines abgeleiteten Objekts zuweisen. Genauso kannst du bei einem Array die Anfangsadresse einem Zeiger auf den Typ dieses Arrays zuweisen.
Die Ansicht passt schon besser. Kombiniert mit der Möglichkeit, einen Zeiger implizit mit operator[] verwenden zu dürfen, fällt die semantische Schwachstelle gar nicht mehr richtig auf. Der Punkt ist nunmal, dass ein Zeiger kein Array ist, sondern genau eine bestimmte Position in einem Array (dem Speicher) beschreibt.
Mir fiel das auch erst auf, als ich das im Compiler umsetzen musste, dass ich etwas, was ich als eine Operation ansah, auf einmal an zwei vollkommen verschiedenen Baustellen einbauen musste. Die Sprache gibt diese Unterscheidung nicht offensichtlich her, also wird die Unterscheidung auch nicht für den Sprecher (Programmierer) offensichtlich. Der Compiler kommt aber in beiden Fällen mit einer Adresse an (wo liegt die Variable auf dem Stack?), aber mit unterschiedlichen Datentypen an und muss auch unterschiedliche Reaktionen zeigen: Beim char * muss erst dereferenziert werden, beim char[2] darf nicht dereferenziert werden, weil die Daten auf dem Stack liegen.
Xin hat geschrieben:
Kerli hat geschrieben:
Xin hat geschrieben:Ich bin mir hingegen sicher, dass feld[3][5] was anderes ist als feld[3*5].
Ja, aber nur was den Zugriff betrifft, im Speicher schauen Beide gleich aus. Beim zweiten Feld wäre zb. feld[1][1] nicht erlaubt...
Und ist im ersten Fall feld[1] erlaubt und wenn ja, was kommt als Datentyp raus? Ein Primitiv oder ein Pointer?
feld ist ein Array mit drei Elementen, die wieder selbst Arrays sind mit je 5 Elementen. Mit feld[1] nimmst erhältst du also ein Array bestehend aus 5 Elementen (Das zweite) und dieses kann wieder implizit in einen Zeiger des selben Typs konvertiert werden, der dann auf das erste Feld des Arrays zeigt.
Mit feld[1] erhältst du also einen Zeiger auf den Anfang des zweiten Elements im Array mit drei Elementen. Im Gegensatz dazu würdest du mit ((char*)feld)[1] das zweite Element des ersten Arrays erhalten.[/quote]
Man bekommt also als Datentyp einen char[5], was quasi in ein char * umgewandelt werden kann.

Wenn man ein char bla[1][2][3] hat und auf auf bla[1] zugreife, dann bekomme ich welchen Daten zurück, wenn ich auf bla[1] zurückgreife?
Wir haben ja festgestellt, dass sich ein char[1][2] wie ein char * verhält, und sich ein Array eines Typs auf ein Typ * automatisch casten lässt. Dann ist ein bla[1][2][3] doch in ein char **... oder?

(Wir drehen uns im Kreis und mir ist das bewusst. Das ist, was ich als schwammig bezeichne ^^)
Kerli hat geschrieben:
Xin hat geschrieben:Noch nicht ganz, aber Du machst das bisher gut :-)
Dann hoffe ich, dass es mir auch weiterhin gut gelingt :P
Yepp, nur hast Du mich noch nicht überzeugt, dass der Umgang in C mit Arrays semantisch sauber durchformuliert ist.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Antworten