Forum: Compiler & IDEs Adresse von char-array Zeiger?


von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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
  char string[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?

von STK500-Besitzer (Gast)


Lesenswert?

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.

von Stefan E. (sternst)


Lesenswert?

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.

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
static string[10];
2
static char * ptr;
3
4
extern void bar (char*);
5
6
void foo (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).

von Stefan E. (sternst)


Lesenswert?

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

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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

von Stefan E. (sternst)


Lesenswert?

> Oder habe ich den Begriff "Stack-Frame" nicht verstanden?

Ja.

Im Source steht folgendes:
1
void SomeFunc ( void ) {
2
3
    int a;
4
    char b[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).

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
void foo (size_t size)
2
{
3
   char string[size];
4
   ...
5
}

Für lokale Variablen wie in
1
void foo ()
2
{
3
   char string[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
void foo (char string[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

von Stefan E. (sternst)


Lesenswert?

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)

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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

von Sven P. (Gast)


Lesenswert?

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
char s[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).

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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
> char s[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

von Sven P. (Gast)


Lesenswert?

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 :-/
1
void testfunktion(char *p) {
2
  char **pp = &p;
3
}
4
5
char arr[10];
6
testfunktion(arr);

von Stefan E. (sternst)


Lesenswert?

> 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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
1
x = *(&2); // x = 2

von Sven P. (Gast)


Lesenswert?

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

von daniel (Gast)


Lesenswert?

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

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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
   char array1[10];
2
   char array2[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

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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
   char x[10]="test1";
2
   char y[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
   char x[10]="test1";
2
   char y[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

von daniel (Gast)


Lesenswert?

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.
1
#include <iostream>
2
3
using namespace std;
4
5
#define SIZE(arr) sizeof(arr)/sizeof(arr[0])
6
#define SIZE1(arr) sizeof(arr)/sizeof(arr[0])
7
#define SIZE2(arr) sizeof(arr[0])/sizeof(arr[0][0])
8
#define SIZE3(arr) sizeof(arr[0][0])/sizeof(arr[0][0][0])
9
10
int main()
11
{
12
    int feld[10] = {1,2,3,4,5,6,7,8,9,10}, (*p)[10] = &feld;
13
    for(int i=0; i<SIZE(feld); i++)
14
        cout << (*p)[i] << endl;
15
16
    int x[2][3][4], *a = &x[0][0][0], (*b)[4] = &x[0][0], (*c)[3][4] = &x[0];
17
    int cnt=0;
18
    for(int i=0; i<SIZE1(x); i++)
19
        for(int j=0; j<SIZE2(x); j++)
20
            for(int k=0; k<SIZE3(x); k++) {
21
                //x[i][j][k] = cnt++; // 1
22
                *(a + i*3*4 + j*4 + k) = cnt++;  // 2
23
                // 1 dasselbe wie 2
24
            }
25
    for(int i=0; i<SIZE1(x); i++)
26
        for(int j=0; j<SIZE2(x); j++)
27
            for(int k=0; k<SIZE3(x); k++)
28
                cout << (*(b+i*3+j))[k] << endl;
29
30
    for(int i=0; i<SIZE1(x); i++)
31
        for(int j=0; j<SIZE2(x); j++)
32
            for(int k=0; k<SIZE3(x); k++)
33
                cout << (*(c+i))[j][k] << endl;
34
    return 0;
35
}

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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
char x[10];
2
char y[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

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

>
1
> char x[10];
2
> char y[10];
3
> char (*p)[10];
4
> p = &x;
5
> p[0] = ..
6
> p = &y;
7
> p[0] = ..
8
>

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?

von daniel (Gast)


Lesenswert?

>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
typedef int line[10];
2
3
int main()
4
{
5
    line matrix[5];
6
    line * p;
7
    p = &matrix[0];
8
    return 0;
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

von daniel (Gast)


Lesenswert?

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.

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

> 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
   char x[10]="test1"; // char Array
2
   char y[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?

von yalu (Gast)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.