Hallo!!
Eigentlich mehr ein C- als ein GCC-Problem, aber ich hoffe trotzdem
im richtigen Forum zu sein.
Bislang dachte ich immer dass sich die "Objekte" string und ptr nach
folgenden Definitionen praktisch identisch verhalten:
1
char*ptr;
2
charstring[10];
3
ptr=string;
char *ptr stellt dabei natürlich keinen Speicher bereit, aber durch
die Zuweisung zeigt ptr anschließend auf den gleichen Speicher wie
string. Und ich dachte eigentlich mit ptr die gleichen Dinge wie mit
string auch machen zu können.
Aber:
1
printf("string : %p\n",string);
2
printf("&string: %p\n",&string);
3
4
printf("ptr: %p \n",ptr);
5
printf("&ptr:%p \n",&ptr);
liefert nun dasda:
string : 0xbfbe79f1
&string: 0xbfbe79f1
ptr: 0xbfbe79f1
&ptr:0xbfbe79dc
Mit ptr funktioniert der Adressoperator erwartungsgemäß. ptr
selbst repräsentiert die Adresse auf das erste Element im char array
und &ptr die Adresse des Speichers an der vorhergehende Adresse
gespeichert ist. string selbst zeigt ebenfalls auf die Adresse
des ersten char-Elements, aber dieser Pointer muss ja ebenfalls
irgendwo im Speicher stehen und ich erwartete mit &string diese
Adresse ermitteln zu können. Stattdessen erhalte ich ebenfalls die
Adresse auf das erste char-Element.
Warum ist das so?
ptr ist doch eine Variable vom Type "Adresse".
In dieser Variable steht die Adresse des String oder irgendetwas anderem
(vom gleichen Typ).
String umgeht diesen "Umweg": Die Adresse ist dem Programm bekannt und
immer gleich, und kann somit auch gleich das erste Zeichen adressieren.
Klaus W. wrote:
> aber dieser Pointer muss ja ebenfalls irgendwo im Speicher stehen
Nein, muss er nicht (und tut er auch nicht), weil er konstant ist.
Während ptr ja auf alles mögliche zeigen kann, zeigt string immer
nur auf das immer gleiche Array. Daher wird im Code string direkt
durch die konstante Adresse des Arrays ersetzt, statt einen im Speicher
stehenden Pointer zu dereferenzieren.
Stefan Ernst wrote:
> Klaus W. wrote:>>> aber dieser Pointer muss ja ebenfalls irgendwo im Speicher stehen>> Nein, muss er nicht (und tut er auch nicht), weil er konstant ist.> Während ptr ja auf alles mögliche zeigen kann, zeigt string immer> nur auf das immer gleiche Array. Daher wird im Code string direkt> durch die konstante Adresse des Arrays ersetzt, statt einen im Speicher> stehenden Pointer zu dereferenzieren.
Hm, verstehe.
Ich war der festen Überzeugung string++ z.B. wäre möglich. Aber es geht
tatsächlich nur mit ptr wie ich inzwischen festgestellt habe.
Was ich trotzdem nicht verstehe:
Wie kann string im Code durch eine konstante Adresse ersetzt werden?
Ergibt sich die Adresse nicht erst zur Laufzeit? - Tatsächlich ist es ja
bei jedem Programmstart eine andere! Also muss sie doch auch irgendwo
gespeichert sein?
Viele Grüße,
Klaus
Klaus W. wrote:
> Wie kann string im Code durch eine konstante Adresse ersetzt werden?> Ergibt sich die Adresse nicht erst zur Laufzeit? - Tatsächlich ist es ja> bei jedem Programmstart eine andere! Also muss sie doch auch irgendwo> gespeichert sein?>> Viele Grüße,> Klaus
string ist zunächst ein Symbol, von dem der Compiler weiß, daß es eine
Konstante ist, deren Wert er aber nicht kennt. Jedenfalls funktioniert
GCC+binutils so.
Für diese unbekannte Konstante wird der Assembler im .o ein Symbol
anlegen und für Stellen im Code ein RELOC/FIXUP einfügen, also einen
Platzhalter. So muss es auch mit Funktionsaufrufen gemacht werden. Wenn
Du zB printf aufrufst, dann muss Code für den Aufruf erzeugt werden, und
zwar wird normal ein direkter Call, also Call zur Startadresse der
Funktion als asm-Code ausgegeben, obwohl die Adresse nicht bekannt ist.
Unterschied bei der Funktion ist nur, daß kein Platz für das Symbol
reserviert werden muss.
Die RELOCs werden erst vom Linker/Locator mit den endgültigen Werten
befüllt.
1
staticstring[10];
2
staticchar*ptr;
3
4
externvoidbar(char*);
5
6
voidfoo(void)
7
{
8
bar(string);
9
bar(ptr);
10
}
Diese C-Quelle wird (ich hab avr-gcc genommen) zu
1
foo:
2
ldi r24,lo8(string)
3
ldi r25,hi8(string)
4
call bar
5
lds r24,ptr
6
lds r25,(ptr)+1
7
call bar
8
ret
9
.lcomm string,100
10
.lcomm ptr,2
Hier sieht man schon, daß string als Immediate, also wie eine Konstante
geladen wird (LDI), während ptr aus dem RAM geladen wird (LDS).
Für ptr werden 2 Byte reserviert, für string 100.
Allerdings braucht der Ladebefehl LDS auch eine Konstante als zweites
Argument, und auch das Symbol ptr ist wie eine Konstante zu behandeln.
Gleiches gilt für bar, für das allerdings kein Platz reserviet wird.
Der Assembler assembliert das zu (hier ein Disassemble mit objdump)
1
0000004a <foo>:
2
4a: 80 e0 ldi r24, 0x00 ; 0
3
4c: 90 e0 ldi r25, 0x00 ; 0
4
4e: 0e 94 00 00 call 0x0 <dac_init>
5
52: 80 91 00 00 lds r24, 0x0000
6
56: 90 91 00 00 lds r25, 0x0000
7
5a: 0e 94 00 00 call 0x0 <dac_init>
8
5e: 08 95 ret
Hier stehen an den RELOCs noch überall Nullen, weil der Assembler die
Adressen nicht fixt bzw nicht fixen kann. Das führt auch zu dem
verwirrenden call <dac_init>. In dem Modul gibt es ab Offset 0x0 eine
dac_init(), daher die Fehlintergretation.
Erst nach Linkung/Lokatierung ist der Code ausgefüllt:
1
000020ce <bar>:
2
20ce: 08 95 ret
3
4
000020d0 <foo>:
5
20d0: 82 e3 ldi r24, 0x32 ; 50
6
20d2: 93 e0 ldi r25, 0x03 ; 3
7
20d4: 0e 94 67 10 call 0x20ce <bar>
8
20d8: 80 91 96 03 lds r24, 0x0396
9
20dc: 90 91 97 03 lds r25, 0x0397
10
20e0: 0e 94 67 10 call 0x20ce <bar>
11
20e4: 08 95 ret
Dabei hab ich eine triviale Implementierung für bar gemacht, damit der
Linker nicht meckert. string wurde ab Adresse 0x0332 angelegt, ptr
direkt dahinter ab Adresse 0x0396 (0x332+100=0x369).
> Tatsächlich ist es ja bei jedem Programmstart eine andere!
Wie kommst du darauf? Die Variable string[] liegt wie jede Variable an
einer festen Adresse, die beim Linken vergeben wird (wie von Johann
erklärt). Und wenn es eine lokale Variable ist, ist die absolute Adresse
zwar variabel, aber die relative Position im Stack-Frame ist konstant.
Hallo Johann!!
Entschuldige bitte meine späte Antwort und vielen Dank für Deine
ausführliche Erklärung!!
Ich denke den Mechanismus so wie Du ihn beschrieben hast, verstanden zu
haben. Dass das "irgendwie" so funktionieren muss, war mir schon
bekannt, aber so genau habe ich mir das noch nie überlegt.
100% klar ist es mir trotzdem noch nicht.
Hat man globale (oder statische lokale) Variablen (oder darf man hier
auch Symbole sagen?), dann denke ich den von Dir beschriebenen Vorgang
schon nachvollziehen zu können. Denn da kann der Inhalt der Variablen
(hier also ein Zeiger) ja immer an der gleichen physikalischen (ram)
Speicheradresse zu liegen kommen.
Aber ist es nicht so, dass lokale Variablen auf dem Stack abgelegt
werden? Je nach dem in welchem Zustand sich der Stack zu diesem Moment
befindet (und das kann ja möglicherweise erst zur Laufzeit entschieden
werden), kann doch die Speicheradresse für den Zeiger jedes mal eine
andere sein. Und eben diese Adresse muss doch auch irgendwo festgehalten
sein.
viele Grüße!!
Klaus
Stefan Ernst wrote:
> Wie kommst du darauf? Die Variable string[] liegt wie jede Variable an> einer festen Adresse, die beim Linken vergeben wird (wie von Johann> erklärt). Und wenn es eine lokale Variable ist, ist die absolute Adresse> zwar variabel, aber die relative Position im Stack-Frame ist konstant.
Ist sie das wirklich??
Welche Funtionen vorher aufgerufen wurden und wie "voll" der Stack
bereits ist, entscheidet sich doch erst zur Laufzeit!? Daraus resultiert
doch jedes mal eine andere Position auf dem Stack.
Oder habe ich den Begriff "Stack-Frame" nicht verstanden?
viele Grüße,
Klaus
> Oder habe ich den Begriff "Stack-Frame" nicht verstanden?
Ja.
Im Source steht folgendes:
1
voidSomeFunc(void){
2
3
inta;
4
charb[10];
Der vom Compiler generierte Code macht dann folgendes:
Er speichert den aktuellen Stack-Pointer in einem Pointer-Registerpaar
(ich glaube r28 und r29, also Y), das ist dann der "Frame-Pointer".
Danach wird auf den Stack-Pointer 12 addiert, um Platz für die lokalen
Variablen a und b zu schaffen, das ist dann der "Stack-Frame" der
Funktion. Zugegriffen wird dann auf die einzelnen Variablen relativ zum
Frame-Pointer, und diese relative Position ist wieder konstant. Es muss
also nirgendwo die genaue Adresse von b gespeichert sein. Gespeichert
ist nur der Anfang des ganzen lokalen Variablenblockes (und auch nur in
Registern, nicht im Speicher).
PS: Das ist nur das grundlegende Vorgehen. Nachdem der Optimierer drüber
gelaufen ist, kann es auch anders aussehen (z.B. Variablen liegen nur in
Registern und nicht im Frame).
Klaus W. wrote:
> Hallo Johann!!>> Hat man globale (oder statische lokale) Variablen (oder darf man hier> auch Symbole sagen?), dann denke ich den von Dir beschriebenen Vorgang> schon nachvollziehen zu können. Denn da kann der Inhalt der Variablen> (hier also ein Zeiger) ja immer an der gleichen physikalischen (ram)> Speicheradresse zu liegen kommen.
Der Programmierer wird "Variable" sagen, aus Compiler/Linker-Sicht wird
man eher von "Symbolen" und "Objekten" (nicht zu verwechseln mit
OO-Objekten) reden.
Was die Adressen angeht: Das schon, sofern man ein und dasselbe statisch
gelinkte Programm betrachtet.
> Aber ist es nicht so, dass lokale Variablen auf dem Stack abgelegt> werden? Je nach dem in welchem Zustand sich der Stack zu diesem Moment> befindet (und das kann ja möglicherweise erst zur Laufzeit entschieden> werden), kann doch die Speicheradresse für den Zeiger jedes mal eine> andere sein. Und eben diese Adresse muss doch auch irgendwo festgehalten> sein.
Lokale Variablen können
-- Ebenfalls statisch sein (static): siehe oben
-- Wegoptimiert worden sein, kein Platz wird gebraucht
-- In Registern leben, es wird ebenfalls kein RAM gebraucht
-- Im Frame der Funktion leben
Bei komplexere Funktionen legt der Compiler einen Frame an, in dem
lokale Variablen angelegt werden. Diese müssen dann vom Frame (RAM)
gelesen und wieder gespeichert werden. Der Frame wird auf dem Stack
angelegt, entweder auf dem Hardware-Stack, manchmal auch eine
Software-Implementierung eines Stacks. Das ist compilerabhängig.
Das Layout des Frames ist zur Compilezeit weitgehend bekannt, immerhin
so weit, daß der Compiler jeder lokalen Variablen, die im Frame lebt,
einen Offset zuordnen kann. Dieser Offset (relative Adresse) ist
konstant und unabhängig vom momentanen Zustand der Maschine/des
Programms.
Bei einem anderen Aufruf der Funktion werden diese Variablen idR an
einer anderen Stelle angelegt. Ein Grund dafür, warum nicht-lokale
Variablen ein Verlassen der Funktion nicht überleben (und wenn, kann man
sich nicht drauf verlassen).
Ein Fall, in dem der Frame-Aufbau zur Compilezeit unbekannt ist, sind
Konstrukte wie sie zB GCC erlaubt:
1
voidfoo(size_tsize)
2
{
3
charstring[size];
4
...
5
}
Für lokale Variablen wie in
1
voidfoo()
2
{
3
charstring[10];
4
...
5
}
muss natürlich ein anderer Code erzeugt werden wie für statische
Variablen; der Zugriff muss dann Stackpointer-relativ geschehen, bzw
genauer Framepoiner-relativ. Ein guter Compiler wird aber versuchen, den
Framepointer zu eliminieren, falls möglich.
Wie auch immer die relative Adresse ist immer die gleiche! ...auch
wenn das je nach Aufruf eine andere absolute Adresse gibt.
Ausdrücke wie &string sind aber nicht sehr sinvoll oder erhellend, auch
wenn sie in manchen Zusammenhängen erlaubt sind, etwa.
1
**(&string)='A';
Der Code ist der gleiche wie
1
*string='A';
2
// oder
3
string[0]='A';
Ein Fall verbleibt, nämlich wenn string Übergabe-Parameter ist: Mit
1
voidfoo(charstring[10][10])
wird nicht etwa das genze Array in die Funktion geliefert, sondern nur
dessen Startadresse. Der Zugriff auf das 2-dimensionale Feld wird von
Progammierer-Seite aber einfacher. Die Größe muss zur Compile-Zeit
feststehen. Ansonsten geht natürlich immer char ** string oder char
string[][]. Bei bekannter Größe liefert sizeof-Operator übeigens nicht
die Größe des Feldes wie bei einer statischen Variablen, sondern das
gleiche wie sizeof(char**).
Nochwas: Stack-Über- und Unterläufe sind hier kein Thema, das ist eine
andere Baustelle. Teilweise wird das über die Hardware abgehandelt wie
bei µC der C16-Familie: man kann bei Over- oder Underflow ne Trap
auslösen lassen. Oder man kann den Compiler instruieren, im
Funktions-Prolog entsprechende Tests einzubauen, was aber Code und Zeit
kostet und natürlich wieder compilerabhängig ist.
Johann
Ich selber:
> Pointer-Registerpaar (ich glaube r28 und r29, also Y)
Ähm, dieses Detail gilt natürlich nur für den AVR, um den es hier
anscheinend ja gar nicht geht. (peinlich)
Vielen Dank nochmal Johann und Stefan!!!
Nachdem Stefan die Sache mit dem Stackframe aufgeklärt hat, wird mir
auch einiges an dem Assembler Code klar, den der Compiler bei
Funktionsaufrufen so ausspuckt! Darauf habe ich zwar schon immer wieder
einen Blick geworfen, stand aber öfters vor Rätseln.
Die detailierten Ausführungen von Johann habe ich auch wieder mit großem
Interesse gelesen. Wenn man's mal begriffen hat eigentlich auch gar
nicht soooo kompliziert :-). Wenngleich durchaus mit weitreichenden
Konsequenzen...
@Stefan:
NICHT um den AVR geht es nur bedingt. Ich war gerade dabei ein
C-Programm vom AVR auf den MSP430 zu portieren. Zum Testen habe ich den
eigentlichen (hardwareunabhängigen) Algorithmus einfach mal direkt auf
i386 compiliert und bin dort auf diese Fragen gestoßen.
Vielen Dank und viele Grüße,
Klaus
STOP. Genau auf die Fangfrage bin ich auch reingefallen und habs ums
Verrecken nich geglaubt...
1
char*p=(char*)malloc(10);
2
char**pp=&p;
3
chars[10];
4
char**ss=&s;/* <-- Käse */
p ist ein Zeiger. Der zeigt hier in etwa auf das erste Element vom
Vektor. p[9] oder *(p+9) wäre dann das zehnte Zeichen dadrinne.
pp ist ein Zeiger auf nen Zeiger, der zeigt auf p. *p ist dann wieder
der Zeiger aufs erste Element (der "Vektor") und **p ists erste Element
selbst.
s ist ein Array und kein Zeiger. Das kann man sich vorstellen, wie ein
Label im Assembler-Quelltext oder so. Man kann auf s keinen Zeiger
setzen, weil da auch nix ist, auf was der zeigen sollte; man kann s ja
selbst nicht ändern (dann würde man ja das ganze Array im Speicher
verschieben).
Sven Pauli wrote:
> STOP. Genau auf die Fangfrage bin ich auch reingefallen und habs ums> Verrecken nich geglaubt...>>
1
>char*p=(char*)malloc(10);
2
>char**pp=&p;
3
>chars[10];
4
>char**ss=&s;/* <-- Käse */
5
>
>> p ist ein Zeiger. Der zeigt hier in etwa auf das erste Element vom> Vektor. p[9] oder *(p+9) wäre dann das zehnte Zeichen dadrinne.> pp ist ein Zeiger auf nen Zeiger, der zeigt auf p. *p ist dann wieder> der Zeiger aufs erste Element (der "Vektor") und **p ists erste Element> selbst.
So weit so klar!
> s ist ein Array und kein Zeiger. Das kann man sich vorstellen, wie ein> Label im Assembler-Quelltext oder so. Man kann auf s keinen Zeiger> setzen, weil da auch nix ist, auf was der zeigen sollte; man kann s ja> selbst nicht ändern (dann würde man ja das ganze Array im Speicher> verschieben).
Naja, das Array verschieben würde ich jetzt nicht sagen, aber wie Johann
und Stefan ausführlich beschrieben haben, muss s erstens nicht zwingend
im RAM liegen und ist ausserdem quasi als Konstante zur Compile- bzw.
Link-zeit festgelegt. Aber das steht ja oben alles ausführlich.
In meinem Versuch war es ja nun tatsächlich so, daß der Ausdruck &s
eigentlich wieder die Adresse des ersten Elements des Arrays geliefert
hat.
Also s == &s. Ein bischen komisch ist das ja trotzdem.
Ist das dann compilerspezifisch? Oder gänzlich undefiniert?
Viele Grüße,
Klaus
Klaus W. wrote:
> So weit so klar!
Oki.
> Naja, das Array verschieben würde ich jetzt nicht sagen,
Jo doch, im Prinzip schon. Wenn du nen Zeiger auf etwas machst, dann ist
"etwas" ja auch veränderbar (*zeiger = x). Nur hier würdeste ja dann den
"Label" oder wie auch immer, also prinzipiell das ganze Array,
verschieben.
> aber wie Johann> und Stefan ausführlich beschrieben haben, muss s erstens nicht zwingend> im RAM liegen und ist ausserdem quasi als Konstante zur Compile- bzw.> Link-zeit festgelegt. Aber das steht ja oben alles ausführlich.
Jubb.
> In meinem Versuch war es ja nun tatsächlich so, daß der Ausdruck &s> eigentlich wieder die Adresse des ersten Elements des Arrays geliefert> hat.> Also s == &s. Ein bischen komisch ist das ja trotzdem.> Ist das dann compilerspezifisch? Oder gänzlich undefiniert?
Komisch isses, aber völlig kontrolliert. Ich überleg grad selber, wie
das noch gleich hieß, weil selbst im Kernighan&Ritchie steht das net
drinne, wohl aber irgendwo im C-Standard...
Jedenfalls hängts vom Kontext ab, inwieweit ein "char x[10];" zum
Pointer zerfällt, weil sowas hier ist wieder legal, weils wiederum was
völlig andres ist :-/
> In meinem Versuch war es ja nun tatsächlich so, daß der Ausdruck &s> eigentlich wieder die Adresse des ersten Elements des Arrays geliefert> hat.> Also s == &s. Ein bischen komisch ist das ja trotzdem.
Es ist schon so wie Sven geschrieben hat, s ist ja nicht wirklich ein
Pointer, sondern das Array. Ein Array ist aber charakterisiert durch die
Adresse des ersten Elements, so dass s quasi wie ein Pointer auf das
erste Element wirkt. Und was ist nun ein Pointer auf das Array? Richtig,
wieder ein Pointer auf das erste Element, denn das steht ja an der
Stelle im Speicher wo sich das Array befindet. Also: s == &s
Klaus W. wrote:
>> In meinem Versuch war es ja nun tatsächlich so, daß der Ausdruck &s> eigentlich wieder die Adresse des ersten Elements des Arrays geliefert> hat.> Also s == &s. Ein bischen komisch ist das ja trotzdem.> Ist das dann compilerspezifisch? Oder gänzlich undefiniert?
Ja, ist seltsam das. Da hülft nur ein Blich in die C-Spec, die ich in
den Untiefen nicht im Kopp hab. Entweder es steht da drin, oder nicht.
In letzterem Fall ist das Resultat nicht definiert und ein Compiler kann
irgendwas machen, ähnlich wie bei foo(a++,a++).
Ich fänd's aber besser, wenn an der Stelle zumindest ne Warnung käme...
&string zu schreiben ist ungefär so, also würde man schreiben
>s ist ein Array und kein Zeiger. Das kann man sich vorstellen, wie ein>Label im Assembler-Quelltext oder so. Man kann auf s keinen Zeiger>setzen, weil da auch nix ist, auf was der zeigen sollte; man kann s ja>selbst nicht ändern (dann würde man ja das ganze Array im Speicher>verschieben).
man kann vom Array Zeiger nehmen
char x[10];
char y[10];
char (*p)[10];
p = &x;
p[0] = ..
p = &y;
p[0] = ..
das Prinzip ist erweiterbar
char xy[20][10];
p = &xy[0];
p[0] = ..
das ganze geht auch für mehrere Dimensionen
char a[5][10][20][30];
char (*i)[20][30];
char (*j)[10][20][30];
i = &a[4][9];
usw
So eine Zeiger auf Array Deklaration hat aber zur Aufgabe
mehrdimensionales auf eindimensionales abzubilden.
Deswegen ist bei der Übergabe eines Feldes mit n-Dimensionen
immer die Angabe der n-1 letzen Dimensionen obligatorisch.
Die Angabe der ersten ist wurscht!
Sven Pauli wrote:
> Johann L. wrote:>> Ich fänd's aber besser, wenn an der Stelle zumindest ne Warnung käme...> Wenn man die anstellt, kommt die bei gcc auch :-) -Wall
Hm, bei sowas wie
1
chararray1[10];
2
chararray2[10];
3
char*ptr;
4
5
ptr=&array1;
muss man das nicht mal. Da meckert der compiler auch so schon:
test.c:9: warning: assignment from incompatible pointer type
Bei sowas:
1
printf("%p\n",array1);
2
printf("%p\n",&array1);
Gibt es allerdings keine Warnung. Da hilft auch -Wall nicht. Vermutlich
liegt das daran, weil printf keine Typkontrolle durchführt, da man ja
x-beliebige Zeiger übergeben kann.
Viele Grüße,
Klaus
daniel wrote:
> man kann vom Array Zeiger nehmen
uff, jetzt wirds aber wild!
> char x[10];> char y[10];> char (*p)[10];> p = &x;> p[0] = ..> p = &y;> p[0] = ..
Also p ist ein Array aus char* Zeigern.
Nachdem was wir oben gelernt haben, verweist &x auf die gleiche Adresse
wie x. Also auf das erste Array Element. Wenngleich &x von merkwürdigem
Typ zu sein scheint.
Tatsächlich beschwert sich der Compiler hier nicht.
Wenn p nun aber auf das erste Array-Element zeigt, sollte ich mit p
wiederum so arbeiten können wie mit x.
Ja, das funktioniert auch so:
1
charx[10]="test1";
2
chary[10]="test2";
3
char(*p)[10];
4
p=&x;
5
// p[0] = ..
6
p=&y;
7
// p[0] = ..
8
9
printf("x:%p\n",x);
10
printf("y:%p\n",y);
11
printf("p:%p\n",p);
12
printf("p:%s\n",p);
liefert:
x:0xbfa851e6
y:0xbfa851dc
p:0xbfa851dc
p:test2
Also erwartungsgemäß.
Nicht verwunderlich ist auch die Compilerwarnung:
test.c:17: warning: format '%s' expects type 'char *', but argument 2
has type 'char (*)[10]'
> das Prinzip ist erweiterbar> char xy[20][10];> p = &xy[0];> p[0] = ..>> das ganze geht auch für mehrere Dimensionen>> char a[5][10][20][30];> char (*i)[20][30];> char (*j)[10][20][30];> i = &a[4][9];>> usw> So eine Zeiger auf Array Deklaration hat aber zur Aufgabe> mehrdimensionales auf eindimensionales abzubilden.
Hm, heisst das wenn ich nun statt
1
printf("p:%s\n",p);
mit
1
printf("p:%s\n",p[0]);
zugreife, dann wäre das typkonform und der Compiler sollte nicht
meckern?
Tatsächlich! Und:
1
charx[10]="test1";
2
chary[10]="test2";
3
char(*p)[10];
4
p=&x;
5
p=&y;
6
7
printf("x:%p\n",x);
8
printf("y:%p\n",y);
9
printf("p:%p\n",p);
10
printf("p:%s\n",p[0]);
11
printf("p:%s\n",p[1]);
Ergibt:
x:0xbf9f0946
y:0xbf9f093c
p:0xbf9f093c
p:test2
p:test1
Das ist ja wild!
Aber wird da nicht eher eindimensionales auf mehrdimensionales
abgebildet statt umgekehrt? p hat offenbar 2-dimensionale Eigenschaften.
Ich verstehe nicht, wieso die Zuweisungen:
1
p=&x;
2
p=&y;
dafür sorgen, dass das Array p mit Inhalt gefüllt wird, ohne dass irgend
etwas indiziert wird!
Also ich merk' schon, ich hab' echt keine Ahnung!
deprimierend ist das :-).
Trotzdem vielen Dank und viele Grüße,
Klaus
char (*p)[10];
>Also p ist ein Array aus char* Zeigern.
p ist ein Zeiger auf ein eindim. Array, das aus 10 char's besteht.
Die Dereferenzierung von p, also *p macht ein "Array" daraus.
Und der Zugriff dann geht normal mit (*p)[0].
Array aus char* Zeigern wäre
char * p[10];
Die Umklammerung mit dem Stern hat da eine grosse Bedeutung.
Ich habe ein ganz wildes Program, das aber komplet alles enthält.
daniel wrote:
> char (*p)[10];>>>Also p ist ein Array aus char* Zeigern.>> p ist ein Zeiger auf ein eindim. Array, das aus 10 char's besteht.> Die Dereferenzierung von p, also *p macht ein "Array" daraus.> Und der Zugriff dann geht normal mit (*p)[0].>> Array aus char* Zeigern wäre> char * p[10];> Die Umklammerung mit dem Stern hat da eine grosse Bedeutung.uff
Also dass die Klammern von fundamentaler Bedeutung sind, ist klar. Nur
von welcher?
Ich ging davon aus, dass () eine höhere Priorität hat als []. Wie ich
gerade sehe, ist dem aber nicht so. Da aber die Assoziativität von links
nach rechts erfolgt, kommt's im konkreten Fall wohl auf das gleiche
raus.
Das heisst aber doch, dass ZUERST dereferenziert und dann indiziert
wird.
Das heisst, äh... dass Du recht hast :-).
Also nochmal zu Deinem Beispiel:
1
charx[10];
2
chary[10];
3
char(*p)[10];
4
p=&x;
5
p[0]=..
6
p=&y;
7
p[0]=..
p ist also ein Zeiger auf ein Char array.
Grundsätzlich also sowas ähnliches wie char **p.
Sehe ich jetzt richtig, dass nun doch geht, wovon wir oben eigentlich
festgestellt haben, dass es nicht geht?
Vor allem würde der Ausdruck &x meiner Erfahrung nach einen Zeiger auf
das erste Array Element in x liefern, wenn p als char** definiert wäre.
Hier scheint allerdings datsächlich ein Zeiger auf x zu entstehen, der
ja eigentlich gar nicht existieren dürfte.
D.h. die Bedeutung des Ausdrucks &x soll sich je na typ der
zielvariablen ändern? Geht sowas nicht nur in C++?
Also langsam wird's mir echt zu hoch und ich muss wohl noch eine Nacht
darüber schlafen.
Viele Grüße,
Klaus
Moment, das heisst eigentlich, dass char (*p)[10]; im Gegensatz zu
char**p tatsächlich Speicher bereit stellt, oder?
Bedeutet das evtl., dass die Lage der Zeiger ebenso wie die von x und y
selbst bereits zur Compile bzw. Linkzeit festgelegt wird?
>Moment, das heisst eigentlich, dass char (*p)[10]; im Gegensatz zu>char**p tatsächlich Speicher bereit stellt, oder?
char ** p;
ist ein Zeiger auf den Zeiger. Er nimmt genauso viel Speicher ein
wie ein einfacher Zeiger
char * p;
Üblicherweise werden es 4 Byte sein.
beides kann man mit sizeof(char *) und sizeof(char **)
nachprüfen. Auch sizeof(char(*p)[10]) wird wahrscheinlich so gross
wie sizeof(char *) sein. Denn alloziert muss da nichts. Das einzige
was Compiler sich aber merken muss, dass p+1 nun in 10*sizeof(char)
Byteschritten sich im Speicher vorwärtsbewegen muss.
Das ist genau die Grösse des Arrays worauf p zeigt.
1
typedefintline[10];
2
3
intmain()
4
{
5
linematrix[5];
6
line*p;
7
p=&matrix[0];
8
return0;
9
}
hier ist dasselbe, nur mit Hilfe von typedef gelöst.
p zeigt auf Typ 'line', folglich p++ zeigt im Speicher
sizeof(line) Bytes weiter.
void * x = p, * y = ++p;
cout << y << endl;
cout << x << endl;
=> 40 Bytes Differenz
Grüsse
Achso noch zum
char ** p;
man weiss zunächst nie genau was alles über p erreichbar ist.
Das ist genauso wie zum Beispiel hier
int * pi;
Es kann ein einzelnes int dahinter sein oder viele die
direkt nacheinander liegen.
Genauso könnte dort wohin ppc zeigt ein charzeiger drin sein
oder mehrere hintereinander. Und natürlich besteht noch
wie beim int Beispiel die Unsicherheit wenn man über
das charzeiger geht. Da kann ein einzelnes char oder mehrere
Hintereinander sein.
char ** ppc ist damit flexibler.
> beides kann man mit sizeof(char *) und sizeof(char **)> nachprüfen. Auch sizeof(char(*p)[10]) wird wahrscheinlich so gross> wie sizeof(char *) sein. Denn alloziert muss da nichts. Das einzige> was Compiler sich aber merken muss, dass p+1 nun in 10*sizeof(char)> Byteschritten sich im Speicher vorwärtsbewegen muss.> Das ist genau die Grösse des Arrays worauf p zeigt.
Hmm... ich glaube so langsam fange ich an zu begreifen, warum das
Beispiel funktioniert.
Aber kann es sein, dass es nur deshalb funktioniert, weil x und y
zufällig direkt hintereinander liegen?
Wenn ich
1
charx[10]="test1";// char Array
2
chary[10]="test2";// char Array
3
char(*p)[10];// Pointer auf char-Array
4
p=&x;
5
p=&y;
schreibe, kann ich zwar mit
1
printf("p:%s\n",p[0]);
2
printf("p:%s\n",p[1]);
auf die beiden Arrays zugreifen....
gcc -O0 -o test test.c -Wall && ./test
p:test2
p:test1
, aber dass die im Speicher direkt hintereinander liegen ist doch
nirgends definiert, oder?
> Aber kann es sein, dass es nur deshalb funktioniert, weil x und y> zufällig direkt hintereinander liegen?
Ja.
> aber dass die im Speicher direkt hintereinander liegen ist doch> nirgends definiert, oder?
Richtig.