Das verwirrende char in C/C++
-
@rr_rr!-wie auch immer
Im Grunde genommen machst du ja auch mit Adressen rum, wenn du auf ganz normale Variablen zugreifst. Bloß halt nicht explizit, der Compiler macht das automatisch fü dich. Arrays liegen wie normale Variablen auf dem Stack und nicht wie bei dynamischen Arrays der Pointer aufs erste Element. Manchmal brauchst du aber diesen den Pointer, und den kannst du dir über den Stack-Pointer ausrechnen. Auch das macht der Compiler in bestimmten Fällen implizit automatisch, ansonsten rechnet er ihn dir immer noch bei explizitem Casting oder Addressenholen aus.
-
!rr!rr_. schrieb:
Ich glaube nicht, daß man C wirklich verstehen kann, ohne sich zu vergegenwärtigen, daß a[..] nunmal "syntaktischer Zucker" für Zeiger und Adreßarithmetik ist.
Genau andersrum. Man wird C nicht verstehen, wenn man das glaubt. Weil es schlicht und einfach nicht der Fall ist.
Da ist doch nichts dabei - Zeiger und Adreßarithmetik laufen auf der CPU nunmal schneller, als ein "echter" Datentyp "array" laufen würde, der z.B. bei jedem Lese- und Schreib-Zugriff auf ein array-Element den Test auf Index-Grenzen durchführen würde.
Das ist das, was mit "Array-to-Pointer-Decay" gemeint ist.
Aber zuerst ist das Array da, und erst nach diesem "Zerfall" kann es als Pointer behandelt werden. Wenn man von vornherein annimmt, dass ein Array ein Pointer ist, wird man nicht in der Lage sein zu verstehen, was es mit Arrays vor dem Zerfall auf sich hat.
-
Bashar schrieb:
!rr!rr_. schrieb:
Ich glaube nicht, daß man C wirklich verstehen kann, ohne sich zu vergegenwärtigen, daß a[..] nunmal "syntaktischer Zucker" für Zeiger und Adreßarithmetik ist.
Genau andersrum. Man wird C nicht verstehen, wenn man das glaubt. Weil es schlicht und einfach nicht der Fall ist.
dann nenne doch mal eine Situation, in welcher mit einem Array hantiert wird, wobei nicht nur Zeiger + Adreßarithmetik im Spiel sind ?
-
@ !rr!rr_
Nur weil man in vielen Situationen die implizite Umwandlung von Arrays zu Zeigern benutzt, heisst das noch lange nicht, dass es sich um dasselbe Konzept handelt. Arrays sind keine Zeiger. Darum sollte man sie sich auch nicht als solche vorstellen. Denn Situationen, wo sich beide Konzepte komplett verschieden verhalten, gibt es genug.C++ geht da sogar noch einen Schritt weiter und bietet Klassen an, die dem Grundkonzept Array (Speicherung einer Folge von Werten, ganz ohne Zeiger) näher kommen, aber intern genau gleich wie gewöhnliche C-Arrays arbeiten.
-
!rr!rr_. schrieb:
dann nenne doch mal eine Situation, in welcher mit einem Array hantiert wird, wobei nicht nur Zeiger + Adreßarithmetik im Spiel sind ?
Moment mal, du versuchst deine ursprüngliche Aussage abzuschwächen. Du hattest behauptet, ein Array wäre nichts weiter als "syntaktischer Zucker" für einen Pointer. Dass da auf Maschinenebene immer nur mit Adressen rumgerechnet wird hat niemand bestritten.
Ich werde dir ein Beispiel geben, das zeigt, dass Arrays keine Pointer sind. Man sieht relativ häufig, dass Anfänger sowas versuchen:
void f(int **p); int A[10][10]; f(A);
Hat ja Logik, wenn ein Array ein Pointer ist, dann muss ein 2D-Array ein Doppelpointer sein. Leider erzählt ihnen der Compiler was von nicht passenden Typen und sie fragen hier, wie man das casten muss. Was erzählst du ihnen?
-
Nexus schrieb:
Arrays sind keine Zeiger.
ja, wer behauptet denn das Gegenteil?
Ich sagte, Arrays seien "syntaktischer Zucker" für Zeiger und Adreßarithmetik. Intern (Maschinenebene) sind es dann letztendlich doch Zeiger, mit denen hantiert wird.
-
Arrays sind ein Speicherbereich, keine Zeiger.
-
!rr!rr_. schrieb:
Nexus schrieb:
Arrays sind keine Zeiger.
ja, wer behauptet denn das Gegenteil?
Jemand, der unter dem gleichen Nick postet wie du.
Weder s,t,x noch y sind Zeiger
intern schon.
-
Edit: Bashar war schneller.
-
genug gelabert, werden wir mal konkret. Das da:
#include<stdio.h> int main(){ int i = 4; char a[10][10]; a[0][4] = 'A'; a[1][4] = 'B'; a[2][4] = 'C'; a[3][4] = 'D'; printf("%c%c%c%c", a[0][i], a[1][i], a[2][i], a[3][i] ); return(0); }
kompiliert bei mir zu dem da:
[...] subq $120, %rsp movl $4, -20(%rbp) movb $65, -124(%rbp) movb $66, -114(%rbp) movb $67, -104(%rbp) movb $68, -94(%rbp) movl -20(%rbp), %eax cltq movzbl -98(%rbp,%rax), %eax movsbl %al,%esi movl -20(%rbp), %eax cltq movzbl -108(%rbp,%rax), %eax movsbl %al,%ecx movl -20(%rbp), %eax cltq movzbl -118(%rbp,%rax), %eax movsbl %al,%edx movl -20(%rbp), %eax cltq movzbl -128(%rbp,%rax), %eax [...]
Auffällig die beiden Folgen -94 -104 -114 -124 und -98 -108 -118 -128
Quizfrage für die Ratefüchse: was fällt bei diesen zwei Folgen auf?
... ich weiß, ist etwas knifflig ...
Machen wir's kurz:
die Adress-Offsets zu a[j][4] haben die
Form -(10*n+4) und folgen schön im Abstand 10 aufeinander. Der Zugriff erfolgt
also über Z e i g e r und A d r e ß a r i t h m e t i k.
-
trotzdem ist ein Array kein Zeiger. Wenn der Zeiger zerstört wird, macht das dem Speicher nichts. Wenn das Array zerstört wird, ist der Speicherbereich freiwild.
-
Das ist nicht strittig. Der Arrayzugriff a[b] ist im Standard über Zeigerarithmetik definiert. Das ist tatsächlich nur syntaktischer Zucker.
Es geht einzig um deine Behauptung, ein Array wäre ein Zeiger. Dass das nicht so ist, sieht man übrigens auch in deinem Assembler-Code, oder wo siehst du bei; a[0][4] = 'A'; movb $65, -124(%rbp)
eine Zeigerdereferenzierung? Da steht nur eine Adressberechnung relativ zu rbp, also relativ zu einer lokalen Variablen. Wäre a ein Zeiger, müsste der erst geladen und dereferenziert werden, bevor darauf 4 addiert werden.
-
!rr!rr_. schrieb:
Nexus schrieb:
Arrays sind keine Zeiger.
ja, wer behauptet denn das Gegenteil?
Ich sagte, Arrays seien "syntaktischer Zucker" für Zeiger und Adreßarithmetik. [...]
Und die Bedeutung dieser Aussage ist wahrscheinlich vielen nicht klar. Ich kann damit auch echt nichts anfangen. Höchstens, dass Du glaubst, dass man Arrays auch ohne Arrays mit Zeigern hinbekommt. Das wäre aber falsch. Irgendo muss doch der Speicher herkommen. Wenn ich Speicher für 5 ints reservieren will, schreibe ich
int arr[5]
. Wie soll das bitte mit Zeigern funktionieren? Vielleicht hast Du auch eine andere Definition von "Syntaxzucker". Den Begriff "Syntaxzucker" würde ich zB bei C++0x Lambdas benutzen. Man kann all das, was man mit C++0x Lambdas machen kann, auch ohne hinbekommen. Ein Lambda-Ausdrück ist eine syntaktische Abkürzung für die Erzeugung einer Klasse und gleichzeitig einer Instanz dieser Klasse.
-
Man kann das auch ganz philosophisch sehen: C++ ist doch auch nur Syntaxzucker für Assembler
.
Aber im Ernst, das sind doch alles nur Vorstellungsweisen. Ein Array verhält sich nicht genauso wie ein Zeiger und auch nicht genauso wie eine Variable. Ein Array verhält sich wie ein Array, und wer genau wissen will, wie das ist, soll sich halt die Regeln von krümelkracker einprägen.
-
Habt ihr sonst noch Probleme? Ein char ist ein einzelnes Zeichen vom Typ Byte oder Integer. Ein array wie char string[10] macht damit einen String für mehrere Zeichen. Ein Pointer kann benutzt werden, um indizierte Zugriffe wie sting[i] zu vermeiden. Wenn man sich mit Pointern (noch) nicht sicher auskennt, benutzt man einfach Indizes.
Zu beachten ist nur, dass ein String ein abschliessenden Nullzeichen zwingend braucht. Das machen aber die String-Funktionen von C strcpy, strcat, ... von allein!
Was bitte ist beim Programmieren Zucker, kann es nicht auch Pfeffer oder Paprika sein?
-
berniebutt schrieb:
[blabla]
Was bitte ist beim Programmieren Zucker, kann es nicht auch Pfeffer oder Paprika sein?
-
Bashar schrieb:
berniebutt schrieb:
[blabla]
Was bitte ist beim Programmieren Zucker, kann es nicht auch Pfeffer oder Paprika sein?Wobei beim mir der erste Treffer die deutsche Wikiseite ist und auch diesen Quatsch enthält:
...Ein Beispiel für syntaktischen Zucker ist die Behandlung von Feldern in der Programmiersprache C. C unterscheidet streng genommen nicht zwischen Zeigern auf Objekte und Felder von Objekten...
Das kann man da so eigentlich nicht stehen lassen. Die englische Version macht da schon einen viel besseren Eindruck:
In the C programming language arrays are constructed as blocks of memory, accessed via an offset from the array's starting point in memory. Since some pointer operations are hard to write and understand when expressed in terms of pointer arithmetic, C also provides the A *syntax for what would otherwise be written as (A + i). Similarly A[i][j] is more readable than (A + Li + j), L being A's first dimension.
Mit diesem Text verstehe ich auch, worauf !r!r wahrscheinlich hinaus wollte. Aber mit Arrays hat das weniger zu tun. Passender kann man sagen, dass der [i]Index-Operator* syntaktischer Zucker für Zeigerarithmetik ist. Seit Operatorüberladung in C++ müsste man diesen Satz aber noch etwas einschränken.
-
Bashar und Sugar Baby kennen sich da pefekt aus. Ist kein [blabla] mehr zu ergänzen. daddeldu! :p
-
krümelkacker schrieb:
Und die Bedeutung dieser Aussage ist wahrscheinlich vielen nicht klar. Ich kann damit auch echt nichts anfangen. Höchstens, dass Du glaubst, dass man Arrays auch ohne Arrays mit Zeigern hinbekommt.
Man braucht keine Arrays um Speicher statisch oder dynamisch auf dem Stack zu reservieren. Das Verhalten von Arrays auf dem Stack bekommt man auf einem UNIX auch mit alloca() und Zeigern hin. Der Vorteil von arrays besteht darin, daß man mehr dimensionale Arrays anlegen kann, sich also das manuelle Berechnen der korrekten Speicheradresse erspart, und man sich nicht explizit um die Speicherbereitstellung kümmern muß.
Folgendes Programm zeigt wie man mit einem struct Speicher entsprechender Größe anfordert. Es könnte in grauer Theorie Probleme mit dem Padding geben, aber das soll kein richtiges Programm sein, sondern nur verdeutlichen was beim Array eigentlich passiert. Daher erspare ich mir jetzt nachzusehen, ob bei Typen gleicher Größe in einem struct Padding auftreten könnte. Wichtig ist nur, daß der struct mindestens 5*int an Speicher bereitstellt, und ich das so nie in einem richtigem Programm verwenden würde.
#include <stdlib.h> #include <stdio.h> int main () { struct array { int a0,a1,a2,a3,a4,a5; }; struct array a = {0,1,2,3,4}; int* p = &a.a0; for (size_t i = 0; i != 5; ++i) { printf("%i\n",p[i]); } }
krümelkacker schrieb:
Irgendo muss doch der Speicher herkommen. Wenn ich Speicher für 5 ints reservieren will, schreibe ich
int arr[5]
. Wie soll das bitte mit Zeigern funktionieren?
Nachfolgendes Programm ist UNIX only, es soll aber nur das Prinzip erläutern.
Variante 1int my_function (size_t s) { int* p = alloca(sizeof(int)*s); for (size_t i = 0; i != s; i++) { p[i] = i; } } // hier wird der Speicher auf den p verweist freigegeben
Variante 2
int my_function (size_t s) { int p[s]; for (size_t i = 0; i != s; i++) { p[i] = i; } } // hier wird der Speicher von p freigegeben
Das sieht nun wirklich nicht mehr sehr unterschiedlich aus.
-
dem te ist mit der diskussion sicher nicht geholfen