Forum: Compiler & IDEs Pointer in Struct-Arrays Werd Wahnsinnig!


von stift (Gast)


Lesenswert?

Als ich micht jetzt nur mehr beim Try&Error erwischte musste ich 
aufgeben und muss mit dem Thema 'Pointer' nochmals bei 0 beginnen.
Und wenn Pointer in ein Struct-Array zeigen soll die auch Pointer zu 
anderen Struct-Array mit Funktionspointern enthalten dann bringt alles 
Probieren nichts mehr...
Mein Wissen über Pointer ist noch jung und anscheinend hab ich von Grund 
auf falsches gelernt.

Folgendes glaubte ich zu wissen:
1
// 'meinCharArray' ist ein Pointer und zeigt auf das erste 'char'("a") im Speicherfeld das 6+1 Bytes belegt.
2
char meinCharArray[] = "abcdef";
3
4
// 'meineFunktion' ist ein Pointer und zeigt auf die Einsprungadresse dieser Prozedur
5
void meineFunktion (void){
6
  //wichtiges Zeug
7
}
8
9
//'meinStruct' ist ein Pointer und zeigt auf das erste 'char' (101) im Speicherfeld.
10
typedef struct {
11
  char elementA;
12
  char elementB;
13
  char elementC;
14
} mStruct;
15
16
const mStruct meinStruct[] = {
17
  {101, 102, 103},
18
  {201, 202, 203},
19
  {301, 302, 303}
20
};
Das glaub ich alles nicht!
Sind folgende aussagen nun Richtig?
- 'meinCharArray' ist eine Variable die das erste 'char' repräsentiert. 
RICHTIG??
- 'meineFunktion' ist ebenfalls 'nur' eine Variable. RICHTIG??
- 'meinStruct' ist ebenso nur eine Variabel die den Wert '101' 
repräsentiert. RICHTIG??

Ich weiß das sind sehr grundlegende Fragen aber ich weiß zur Zeit 
einfach nicht mehr wo oben und unten ist.
Danke!

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

> Sind folgende aussagen nun Richtig?
> - 'meinCharArray' ist eine Variable die das erste 'char' repräsentiert.
> RICHTIG??

Nein.

> - 'meineFunktion' ist ebenfalls 'nur' eine Variable. RICHTIG??

Nein.

> - 'meinStruct' ist ebenso nur eine Variabel die den Wert '101'
> repräsentiert. RICHTIG??

Nein.


Besorg' Dir ein C-Buch. Nach wie vor empfehlenswert: Brian Kernighan & 
Dennis Ritchie, "Programmieren in C", zweite Ausgabe, Hanser-Verlag.

von Karl H. (kbuchegg)


Lesenswert?

stift schrieb:

> Folgendes glaubte ich zu wissen:
>
> // 'meinCharArray' ist ein Pointer und zeigt auf das erste 'char'("a")
> im Speicherfeld das 6+1 Bytes belegt.
> char meinCharArray[] = "abcdef";

Nein.
Du schreibst jetzt 100 mal: Ein Array ist kein Pointer!

> // 'meineFunktion' ist ein Pointer und zeigt auf die Einsprungadresse
> dieser Prozedur
> void meineFunktion (void){
>   //wichtiges Zeug
> }

auch nicht.

>
> //'meinStruct' ist ein Pointer und zeigt auf das erste 'char' (101) im
> Speicherfeld.
> typedef struct {
>   char elementA;
>   char elementB;
>   char elementC;
> } mStruct;
>
> const mStruct meinStruct[] = {

Da steht eindeutig:
meinStruct ist ein Array. Und wie du, nach 100 mal schreiben, weißt: Ein 
Array ist kein Pointer.

> Das glaub ich alles nicht!

Ach, jetzt kommt die Auflösung.
-> Völlig richtig. Das sollst du auch nicht glauben!

> Sind folgende aussagen nun Richtig?
> - 'meinCharArray' ist eine Variable die das erste 'char' repräsentiert.
> RICHTIG??

meinCharArray ist ein Array.
Punkt.

Aber: der Variablenname eines Arrays fungiert automatisch als die 
Startadresse des Arrays.

In dem Punkt unterscheiden sich "normale" Variablen und Arrays.

In
 int i, j;

   i = j;

fungiert j als 'Platzhalter' für den Inhalt von j.
Und das ist bei allen anderen Datentypen ebenfalls so. Mit Ausnahme von 
Arrays. Bei Arrays fungiert der Variablenname als 'Platzhalter' für die 
Startadresse des Arrays.

> - 'meineFunktion' ist ebenfalls 'nur' eine Variable. RICHTIG??

meineFunktion ist der Name einer Funktion. Hat mit Variablen erst mal 
nichts zu tun.

Aber ich denke man könnte dir insofern beipflichten, als die Erwähnung 
des Namens einer Funktion in einem Ausdruck ähnlich wie bei Arrays die 
Startadresse der Funktion bezeichnet. Und auf diese Startadresse kann 
man die Operation () anwenden, also die Funktion aufrufen, deren 
Startadresse gerade genannt wurde.

> - 'meinStruct' ist ebenso nur eine Variabel die den Wert '101'
> repräsentiert. RICHTIG??

meinStruct ist ein Array, das in diesem Fall dann eben nicht aus lauter 
ints besteht sondern eben aus Strukturobjekten. Aber es ist ein Array. 
Und damit gelten alle Regeln für Arrays.

von Karl H. (kbuchegg)


Lesenswert?

Übrigens:
Was sich bei mir in der Ausbildung ganz gut bewährt hat:
Die strikte Trennung zwischen den Begriffen 'Adresse' und 'Pointer'.

Alles in einem Computer hat eine Adresse. Eine Adresse ist also nichts 
anderes als ein Zahlenwert.
Ein Pointer hingegen ist eine Variable, die so eine Adresse speichern 
kann.

Ja, ich weiß. In der täglichen Praxis wird gerne für alles der Begriff 
Pointer benutzt. Aber ich habe die Erfahrung gemacht, dass es am Anfang 
einfacher wird, wenn man sich den Unterschied klar macht und auch 
sprachlich erst mal benutzt.

Da gibt es zb den Adress-Of Operator in C. Das vorangestellte &

Es liefert die Adresse (ein Zahlenwert) eines Objektes
1
  int j;
2
3
   &j;

&j ist einfach nur die Adresse im Speicher, an der j angelegt wurde. Und 
diese Adresse kann man in einer Pointer-Variablen speichern
1
  int  j;
2
  int *p;
3
4
  p = &j;

p enthält die Adresse, unter der j im Speicher zu finden ist.

So gesehen könnte man sagen, dass bei Arrays automatisch immer ein & 
vorangestellt wird. (Mit Ausnahme von sizeof natürlich. Aber sizeof ist 
sowieso ein anderes Kapitel)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> stift schrieb:
>
>> Folgendes glaubte ich zu wissen:
>>
>> // 'meinCharArray' ist ein Pointer und zeigt auf das erste 'char'("a")
>> im Speicherfeld das 6+1 Bytes belegt.
>> char meinCharArray[] = "abcdef";
>
> Nein.
> Du schreibst jetzt 100 mal: Ein Array ist kein Pointer!

Es kann aber in ganz vielen Situationen so verwendet werden!

>> // 'meineFunktion' ist ein Pointer und zeigt auf die Einsprungadresse
>> dieser Prozedur
>> void meineFunktion (void){
>>   //wichtiges Zeug
>> }
>
> auch nicht.

Auch meineFunktion kann wie eine Variable verwendet werden, nämlich 
wie eine vom Typ void (*)(void):
1
extern void meineFunktion (void);
2
3
// Funktionspointer func mit meineFunktion initialisieren
4
void (*func)(void) = meineFunktion;
5
6
// Gibt einen Funktionspointer zurück
7
void (*get_func(void))(void)
8
{
9
    return meineFunktion;
10
}

Übersetzt mit avr-gcc wird das z.B.:
1
.text
2
get_func:
3
  ldi r24,lo8(gs(meineFunktion))
4
  ldi r25,hi8(gs(meineFunktion))
5
  ret
6
7
.data
8
func:
9
  .word  gs(meineFunktion)

>> //'meinStruct' ist ein Pointer und zeigt auf das erste 'char' (101) im
>> Speicherfeld.
>> typedef struct {
>>   char elementA;
>>   char elementB;
>>   char elementC;
>> } mStruct;
>>
>> const mStruct meinStruct[] = {
>
> Da steht eindeutig:
> meinStruct ist ein Array. Und wie du, nach 100 mal schreiben, weißt:
> Ein Array ist kein Pointer.

Abermals: In vielen Situationen gilt die "Arrays decaly to a Pointers" 
Regel, d.h. meinStruct kann wie ein Zeiger verwendet werden.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Auch meineFunktion kann wie eine Variable verwendet werden, nämlich wie
> eine vom Typ void (*)(void):

Aber nicht doch. Das ist allenfalls eine Konstante.

Versuch mal, Deiner "Variablen" irgendwas zuzuweisen.

von stift (Gast)


Lesenswert?

Danke für die ausführlichen antworten!

Karl Heinz Buchegger schrieb:
> > So gesehen könnte man sagen, dass bei Arrays automatisch immer ein &
> > vorangestellt wird. (Mit Ausnahme von sizeof natürlich. Aber sizeof ist
> > sowieso ein anderes Kapitel)

und genau das verwirrt mich total, einerseits wird das array wie ein 
pointer behandelt andererseits muss man kein & oder * davor schreiben 
wie bei variablen

Was ein Pointer ist ist mir total klar und verständlich!

Für mich gibt es zwei Kategorien punkto & und *
1. Variablen
2. Pointer
und ich weiß nicht wo ich Arrays einordnen soll. und das macht mich 
schon ganz verrückt!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

3. Arrays

von stift (Gast)


Lesenswert?

Johann L. schrieb:
> 3. Arrays

Hmmm da hätt ich wohl selbst drauf kommen können das es vielleicht seine 
eigene Kategorie hat!

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:

>> Nein.
>> Du schreibst jetzt 100 mal: Ein Array ist kein Pointer!
>
> Es kann aber in ganz vielen Situationen so verwendet werden!

Kann er.
Aber es IST kein Pointer.

Das ist wichtig, dass man da gerade bei Anfängern nicht zu lasch ist. 
Nur weil etwas in der Verwendung wie ein Pointer behandelt werden kann, 
bedeutet das nicht das es einer ist. Denn sonst versteht ein Anfänger 
nie, worin das Problem in
1
char Test[10];
1
extern char *Test;

liegt. Denn: Ein Array ist nun mal kein Pointer.

von Karl H. (kbuchegg)


Lesenswert?

stift schrieb:

> und genau das verwirrt mich total, einerseits wird das array wie ein
> pointer behandelt

Diese Aussage ist gefährlich!

> andererseits muss man kein & oder * davor schreiben
> wie bei variablen

Weil bei anderen Variablen der Name der Variable für den Inhalt der 
Variablen steht, während bei Arrays der Name des Arrays für die 
Startadresse des Arrays steht (mit Ausnahme von sizeof). Alles andere 
folgt daraus.

Und das wars auch schon. Das ist im Grunde der ganze Unterschied in der 
Verwendung den es zwischen Arrays und anderen Datentypen gibt.

> Für mich gibt es zwei Kategorien punkto & und *
> 1. Variablen
> 2. Pointer

Wo soll da ein Unterschied sein? Pointer-Variablen sind auch nichts 
anderes als ganz normale Variablen. Sie haben eine Adresse, unter der 
sie im Speicher abgelegt werden und sie haben einen Inhalt, der 
seinerseits wieder eine Adresse ist (und als solche selbstverständlich 
mit einem * dereferenziert werden kann.)

von stift (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
>> Für mich gibt es zwei Kategorien punkto & und *
>> 1. Variablen
>> 2. Pointer
>
> Wo soll da ein Unterschied sein? Pointer-Variablen sind auch nichts
> anderes als ganz normale Variablen. Sie haben eine Adresse, unter der
> sie im Speicher abgelegt werden und sie haben einen Inhalt, der
> seinerseits wieder eine Adresse ist (und als solche selbstverständlich
> mit einem * dereferenziert werden kann.)

Ja genau so seh ich das auch!
Ich meinte damit:
will ich an die Adresse eine Variable pack ich das '&' davor,
will ich an die Adresse die im Pointer hinterlegt ist brauch ich gar 
nichts.

will ich an den Wert einer Variable brauch ich nichts weiter dazu.
will ich an den Wert der Adresse die im Pointer hinterlegt ist nehm ich 
mir das helferlein '*' mit dazu.
Das ist mir alles 1A klar!
Nur wann brauch ich welche operatorn bei Arrays?

von Karl H. (kbuchegg)


Lesenswert?

stift schrieb:


> will ich an den Wert einer Variable brauch ich nichts weiter dazu.

Exakt.

> will ich an den Wert der Adresse die im Pointer hinterlegt ist nehm ich
> mir das helferlein '*' mit dazu.

Das sind aber 2 Operationen
Die erste Operation ist: welcher Wert ist in der Variablen (dem Pointer) 
gespeichert.
Um an diesen Wert zu kommen, schreibst du einfach den Namen der 
Variablen hin.

Als Ergebnis erhältst du eine Adresse.

Und erst mit dieser Adresse machst du dann die 2.te Operation: Nämlich 
diese Adresse dereferenzieren.

Ich denke hier liegt dein geistiges Problem, dass du
1
   *Ptr
als eine Aktion ansiehst. Nope: es sind deren 2

Es ist in einem gewissen sinne gleichwertig wie wenn du schreiben 
würdest
1
   int i, j;
2
3
   i = -j;

das - modifiziert in keinster Weise, was durch die Erwähnung von 'j' 
ausgelöst wird. Die Erwähnung von j löst immer noch genau das gleiche 
aus wie sonst auch: Der Wert von j wird geholt.
Und dann wird als 2. Operation auf diesen Wert die Operation 'Negieren' 
angewendet. Nichts anderes passier hier
1
   int i;
2
   int *ptr;
3
4
   i = *ptr;

Die Verwendung der Variablen 'ptr' sorgt dafür, dass ihr Wert besorgt 
wird. Nur ist der Wert, der in einer Pointer Variablen gespeichert ist, 
selber wieder eine Speicheradresse, die durch den Zugriff auf 'ptr' 
geliefert wird. Und genau wie in der Negation wird dann auf diesen Wert 
(die gelieferte Adresse) eine weitere Operation, nämlich die Operation * 
angewendet. Was auch soweit klappt, denn der *-Operator will genau das: 
eine Adresse. Der *-Operator sieht dann an dieser Stelle im Speicher 
nach und besorgt dann seinerseits den Wert, der an dieser Adresse im 
Speicher gespeichert ist.

> Das ist mir alles 1A klar!
> Nur wann brauch ich welche operatorn bei Arrays?

Die Operationen sind nach wie vor dieselben. Der einzige Unterschied 
besteht einfach nur darin, dass die Verwendung des Variablennamens des 
Arrays eben nicht den Wert liefert, der in dieser Variablen gespeichert 
ist, sondern die Adresse an der dieses Array im Speicher liegt.

Ich weiß nicht, wie ich das anders oder besser ausdrücken könnte. 
Vielleicht hat sich ja jetzt dein Knopf gelöst, dadurch dass du *ptr 
nicht mehr als eine integrale Einheit ansiehst sondern als 2 
Operationen:
1
* Wert holen
2
*  auf diesen Wert eine Operation anwenden

Ok, eine Variante probier ich noch

gegeben
1
  int i;
2
  int * ptr;

und die Aufgabe lautet, die Adresse der Variablen i in ptr zu speichern.
Da die Verwendung des Variablennamens 'i' den Inhalt der Variablen (den 
Wert, also zb 5  oder 8 oder 42) benennen würde, musst du den Adress-Of 
Operator benutzen um die Bedeutung von 'i' zu verändern.
1
    ptr = &i;

dieser Adress-Of Operator modifiziert also den darauffolgenden Ausdruck 
dahingehend, dass nicht mehr der Wert des Ausdrucks geliefert wird, 
sondern die Adresse im Speicher, an der der Ausdruck selber (in diesem 
Fall eben das i) zu finden ist. Natürlich nur, sofern es für diesen 
Ausdruck überhaupt eine Speicheradresse gibt.

Der & Operator besorgt diese Adresse und da ptr als Pointer Variable 
eine Variable ist, die Adressen speichern kann, kann ich diese Adresse 
ptr zuweisen. Auch hier wieder: da ptr kein Array ist, ist mit der 
Benennung der Variablen automatisch der Wert gemeint, der in dieser 
Variablen gespeichert ist. D.h. in die Variable ptr wird als Wert die 
Adresse gespeichert, die sich aus dem Adress-Of Operator ergibt.

Jetzt im Gegenzug dazu
1
   int a[5];
2
   int * ptr;
nur gestatte mir das Pferd jetzt von hinten aufzuzäumen. Wie müsste man 
es schreiben, wenn man die Startadresse des Arrays in ptr speichern 
will.

Nun, links vom = muss auf jeden Fall stehen
1
    ptr =
soweit ist das klar, denn hier wird ja wieder der Wert angesprochen, der 
in der Variablen ptr gespeichert wird. Aber wie kriegt man diesen Wert? 
Eines ist klar: Es muss eine Adresse sein, denn nur Adressen können in 
Pointer Variablen abgelegt werden. Aber wie lautet das in diesem Fall?

In obigen Fall lautete es
1
   ptr = &i;
weil ja i alleine den Wert von i bezeichnen würde.

Hier haben wir es aber mit einem Array zu tun. Und bei einem Array steht 
der Name des Arrays in der Verwendung automatisch für die Startadresse 
des Arrays im Speicher. Das heißt
1
    a
alleine ist bereits eine Adresse! Denn a ist ja ein Array.
Das heisst aber auch, ich brauche an der rechten Seite gar nichts tun. 
Es ist kein & Operator notwendig um eine Adresse zu erhalten. Denn 'a' 
alleine ist ja bereits diese Adresse
1
   ptr = a;
ist daher völlig korrekt.


Jetzt machen wir was anderes. Wertzuweisung
Wieder
1
  int i, j;
Wie weist du den Wert von j an i zu? Du schreibst
1
   i = j;
Dies deshalb, weil ja j alleine bereits für den Wert steht, der in j 
gespeichert ist.

Nächster Fall. Das j ersetzt wir durch einen Pointer
1
  int i;
2
  int * ptr;
wie weist du den Wert, auf den ptr zeigt, an i zu?
1
  i = ptr;
kann nicht die Lösung sein. Denn die Verwendung von ptr alleine liefert 
ja erst die Adresse unter der der Wert zu finden ist. Diese Adresse 
wollen wir aber nicht, sondern wir wollen den Wert dahinter. Also muss 
eine Dereferenzierung her
1
  i = *ptr;
der * Operator macht genau das. Er wertet den Ausdruck rechts von sich 
aus, das muss dann eine Adresse sein und genau unter dieser Adresse 
sieht er im Speicher nach und holt den zugehörigen Wert. Und der wird 
dann an i zugewiesen.

Wie wäre das bei einem Array?
1
  int i;
2
  int a[5];
Da die Benutzung von a nicht den Wert in a liefert, sondern seine 
Startadresse, müsste es doch möglich sein, zu schreiben
1
  i = *a;
denn alles was der * Operator haben will ist eine Adresse, die er 
derefernziert. Der Ausdruck a liefert so eine Adresse.

Und in der Tat: Ja, genau das ist möglich! Und da a für die Startadresse 
des Arrays steht, liefert *a genau den allerersten Wert im Array (den 
der 'am Start' steht)

Wie kriegt man den nächsten Wert im Array?
Hier kommt Pointer Arithmetik ins Spiel, aber nur soviel
1
   i = *(a + 1);
(das '+ 1' erklärt sich aus der Pointer Arithmetik)

Und genau das ist dann auch die eigentliche Schreibweise, wie man auf 
das zweite Element im Array zugreift. Die ausführliche Schreibweise für 
das erste Array Element wäre
1
   i = *(a + 0);
Und hier sieht man auch, dass der Dereferenzier-* einfach nur eine 
Operation ist, die eine Adresse in den Wert an dieser Adresse umsetzt. 
Genauso wie zb bei einem Negier-Minus muss ich einen Ausdruck der zuvor 
ausgewertet werden soll (in diesem Fall eben die Adress-Arithmetik) in 
Klammern schreiben.

Da man allerdings derartige Array-Operationen häufig braucht, ist diese 
Schreibweise um an den Wert an einer bestimmten Array-Position zu 
kommen, äusserst mühsam. Daher haben die Macher von C dafür eine andere 
Schreibweise definiert. Sie haben definiert, dass die Schreibweise
1
   *(a + i)
gleichwertig sein soll zur Schreibweise
1
   a[i]

Um an das zweite Array-Element zu kommen mussten wir schreiben
1
   i = *(a + 1);

Aufgrund der zusätzlichen Definition ergibt sich, dass es eine 
alternative Schreibweise gibt. Nämlich
1
   i = a[1];
aber das ist im Grunde nur eine alternative Schreibweise. (Und warum die 
besser ist, brauchen wir hoffentlich nicht zu diskutieren :-) Auch hier 
gibt es den Dereferenzier-* nach wie vor, der notwendig ist um an einen 
Wert zu kommen da ja die Nennung eines Arraynamens die Startadresse des 
Arrays bezeichnet. Nur ist er in dieser Schreibweisendefinition 
versteckt.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Abermals: In vielen Situationen gilt die "Arrays decaly to a Pointers"
> Regel, d.h. meinStruct kann wie ein Zeiger verwendet werden.

Für alle, die wie ich sonst erst einmal googlen müssen:
  arrays decay into pointers.

von Peter D. (peda)


Lesenswert?

Ein Array ist eine Reihe von Elementen im RAM.

Der Name des Arrays wird vom Compiler als Adresse des ersten Elements 
referenziert. Daher können die Zugriffsoperatoren wie bei Pointern 
verwendet werden (*name = foo; foo = name[1];).

Der Name des Arrays ist aber kein Pointer!
Er ist nur eine konstante Zahl.


Ein Pointer ist eine Variable im RAM, die als Adresse auf irgendwas 
referenziert wird.
Vor jeglicher Verwendung muß also dem Pointer eine reale Adresse einer 
Variablen oder Funktion zugewiesen werden!

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:
> Der Name des Arrays ist aber kein Pointer!
> Er ist nur eine konstante Zahl.

Er ist was den Typ angeht ein Pointer. Der Wert kann während der 
Ausführung des Programms konstant sein, wenn statisch, oder auch bloss 
im Verlauf der Ausführung einer Funktion konstant sein.

> Ein Pointer ist eine Variable im RAM, die als Adresse auf irgendwas
> referenziert wird.

Der formale Begriff "Pointer" stammt aus dem Kontext der Datentypen. Ob 
er sich auf eine Variable bezieht ist offen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

A. K. schrieb:
> Der Wert kann während der Ausführung des Programms konstant sein,
> wenn statisch, oder auch bloss im Verlauf der Ausführung einer
> Funktion konstant sein.

Anders formuliert:

Der Wert ist während der Gültigkeits- oder Lebensdauer des Arrays 
konstant; eine Zuweisung/Änderung ist nicht möglich.

Denn "kann konstant sein", was man aus Deiner Formulierung herauslesen 
kann, impliziert, daß das nicht so sein müsste.

Das hast Du zwar nicht gemeint, das "kann" bezieht sich bei Dir nicht 
auf die Konstanz, sondern die Lebensdauer des Arrays (während der 
Ausführung des Programmes oder während der Ausführung einer Funktion) - 
aber das ist meiner Ansicht nach in Deiner Formulierung nicht so gut zu 
erkennen.

von (prx) A. K. (prx)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Der Wert ist während der Gültigkeits- oder Lebensdauer des Arrays
> konstant; eine Zuweisung/Änderung ist nicht möglich.

Ja, so ist das besser ausgedrückt.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Weil bei anderen Variablen der Name der Variable für den Inhalt der
> Variablen steht, während bei Arrays der Name des Arrays für die
> Startadresse des Arrays steht (mit Ausnahme von sizeof).

Gerade weil Arrays als Operand des sizeof-Operators anders behandelt 
werden als in gewöhnlichen Ausdrücken, kann man durch Experiemnte mit 
sizeof einiges über die Unterschiede zwischen Arrays, Strukturen und 
Pointer lernen.

In folgendem Beispiel gibt das PRINTSIZE-Makro die mit sizeof ermittelte 
Größe des Arguments aus:
1
#include <stdio.h>
2
#define PRINTSIZE(x) printf("sizeof %-16s =           %3zd\n", "("#x")", sizeof (x))
3
4
typedef struct {
5
  char c1, c2, c3;
6
} structure1;
7
8
typedef struct {
9
  int i1;
10
  char c1;
11
} structure2;
12
13
char a1[100];
14
char a2[6][7];
15
char *(*a3)[100];
16
structure1 s1;
17
structure2 s2;
18
structure1 as1[10];
19
20
void function( char *a1_arg_ptr,
21
              char a1_arg[],
22
              char a1_arg_100[100],
23
24
              char (*a2_arg_ptr)[7],
25
              char a2_arg_7[][7],
26
              char a2_arg_6_7[6][7],
27
28
              structure1 *s1_arg_ptr,
29
              structure1 s1_arg) {
30
31
  PRINTSIZE(a1);
32
  PRINTSIZE(a1[0]);
33
  PRINTSIZE(*a1);
34
  PRINTSIZE(a1+0);
35
  printf("\n");
36
37
  PRINTSIZE(a1_arg_ptr);
38
  PRINTSIZE(a1_arg);
39
  PRINTSIZE(a1_arg_100);
40
  PRINTSIZE(*a1_arg_ptr);
41
  printf("\n");
42
43
  PRINTSIZE(a2_arg_ptr);
44
  PRINTSIZE(a2_arg_7);
45
  PRINTSIZE(a2_arg_6_7);
46
  PRINTSIZE(*a2_arg_ptr);
47
  printf("\n");
48
49
  PRINTSIZE(s1_arg_ptr);
50
  PRINTSIZE(s1_arg);
51
  printf("\n");
52
53
  PRINTSIZE(a2);
54
  PRINTSIZE(a2[0]);
55
  PRINTSIZE(a2[0]+0);
56
  PRINTSIZE(*a2[0]);
57
  PRINTSIZE((*a2)[0]);
58
  PRINTSIZE(a2[0][0]);
59
  PRINTSIZE(*a2);
60
  PRINTSIZE(**a2);
61
  PRINTSIZE(a2+0);
62
  PRINTSIZE((a2+0)[0]);
63
  PRINTSIZE(*(a2+0));
64
  printf("\n");
65
66
  PRINTSIZE(a3);
67
  PRINTSIZE(a3[0]);
68
  PRINTSIZE(a3[0][0]);
69
  PRINTSIZE(a3[0][0][0]);
70
  printf("\n");
71
72
  PRINTSIZE(s1);
73
  PRINTSIZE(s1);
74
  printf("\n");
75
76
  PRINTSIZE(s2);
77
  PRINTSIZE(s2.i1);
78
  PRINTSIZE(s2.c1);
79
  printf("\n");
80
81
  PRINTSIZE(as1);
82
  PRINTSIZE(as1[0]);
83
  PRINTSIZE(*as1);
84
  printf("\n");
85
86
  PRINTSIZE(&function);
87
  PRINTSIZE(&*function);
88
  PRINTSIZE(&****function);
89
  printf("\n");
90
91
  // ggf. folgenden Abschnitt auskommentieren:
92
93
  printf("sizeof von und Zeigerarithmetik mit\n");
94
  printf("Funktionen ist nicht C99-konform,\n");
95
  printf("aber als GNU-Erweiterung möglich:\n\n");
96
97
  PRINTSIZE(function);
98
  PRINTSIZE(*function);
99
  PRINTSIZE(****function);
100
  PRINTSIZE(function+0);
101
}
102
103
int main(void) {
104
  function(a1, a1, a1, a2, a2, a2, &s1, s1);
105
  return 0;
106
}

@stift:

Decke in folgender Ausgabe die ganz rechte Spalte mit den Ergebnissen ab 
(real mit einem Stück Papier oder virtuell mit einem anderen Fenster auf 
dem Bildschirm. Dann versuche die Ergebnisse zu erraten. Wenn du bei 
allen Beispielen (bis auf die nicht C99-konformen ganz unten) richtig 
liegst, hast du die Unterschiede kapiert :)

Hinweis: die Größe eines Pointers auf einem PC ist bei einem 32-Bit-OS 4 
Bytes, bei einem 64-Bit-OS 8 Bytes. Die untige Ausgabe stammt von einem 
64-Bit-OS.
1
sizeof (a1)             =           100
2
sizeof (a1[0])          =             1
3
sizeof (*a1)            =             1
4
sizeof (a1+0)           =             8
5
6
sizeof (a1_arg_ptr)     =             8
7
sizeof (a1_arg)         =             8
8
sizeof (a1_arg_100)     =             8
9
sizeof (*a1_arg_ptr)    =             1
10
11
sizeof (a2_arg_ptr)     =             8
12
sizeof (a2_arg_7)       =             8
13
sizeof (a2_arg_6_7)     =             8
14
sizeof (*a2_arg_ptr)    =             7
15
16
sizeof (s1_arg_ptr)     =             8
17
sizeof (s1_arg)         =             3
18
19
sizeof (a2)             =            42
20
sizeof (a2[0])          =             7
21
sizeof (a2[0]+0)        =             8
22
sizeof (*a2[0])         =             1
23
sizeof ((*a2)[0])       =             1
24
sizeof (a2[0][0])       =             1
25
sizeof (*a2)            =             7
26
sizeof (**a2)           =             1
27
sizeof (a2+0)           =             8
28
sizeof ((a2+0)[0])      =             7
29
sizeof (*(a2+0))        =             7
30
31
sizeof (a3)             =             8
32
sizeof (a3[0])          =           800
33
sizeof (a3[0][0])       =             8
34
sizeof (a3[0][0][0])    =             1
35
36
sizeof (s1)             =             3
37
sizeof (s1)             =             3
38
39
sizeof (s2)             =             8
40
sizeof (s2.i1)          =             4
41
sizeof (s2.c1)          =             1
42
43
sizeof (as1)            =            30
44
sizeof (as1[0])         =             3
45
sizeof (*as1)           =             3
46
47
sizeof (&function)      =             8
48
sizeof (&*function)     =             8
49
sizeof (&****function)  =             8
50
51
sizeof von und Zeigerarithmetik mit
52
Funktionen ist nicht C99-konform,
53
aber als GNU-Erweiterung möglich:
54
55
sizeof (function)       =             1
56
sizeof (*function)      =             1
57
sizeof (****function)   =             1
58
sizeof (function+0)     =             8

Du kannst dir natürlich nach Belieben weitere verrückte 
Array-pointer-Ausdrücke ausdenken und ebenfalls mit dem Programm testen.

von stift (Gast)


Lesenswert?

Sehr genial wie ausführlich du das hier erklärst!
So ist es genau perfekt wenn man festgefahren ist.

zudem hab ich mir jetzt auch noch ein eBook von amazon besorgt "The C 
Programming Language" in englischer orginalfassung und hab das kapitel 
array, pointer und strukturen durchgearbeitet.
Jetzt darf ich behaupten das mein wissen auf einen anderen level ist als 
vor ein paar tagen noch war. Nun muss ich es nur mehr umsetzten lernen.
Ehrlich gesagt is es mir lieber viel zu lernen wie man diese zeichen "* 
& -> . ++()--" in der richtigen kompination einsetzt als wenn man 
hiermit eingeschränkt wäre. Hat man das erst kapiert kann man echt 
("krankes" || "geniales") zeug mit struct-arrays anstellen.
Das was mich vor tagen hauptsächlich verwirrt hat war das 'versteckte' 
*-chen hinter diesen [] klammern.

Herzlichsten Dank für eure tolle Hilfe und auch für den Buchtip.

ps: die nächste hürde wird werden die ganzen stract-arrays mit seine 
vielen funktions-,pointern im flash zu lagern. Aber da meld ich mich 
gesondert sollt ich hierbei in ne dunkle sackgasse geraten.

von Karl H. (kbuchegg)


Lesenswert?

stift schrieb:

> Das was mich vor tagen hauptsächlich verwirrt hat war das 'versteckte'
> *-chen hinter diesen [] klammern.


Ja, dieser Pointer - Array Zusammenhang ist am Anfang ein wenig 
gewöhnungsbedürftig und kommt einem seltsam vor. Aber eigentlich ist er 
ganz einfach.

Wenn man verinnerlicht hat, dass derartige Array-Index Zugriffe nichts 
anderes als versteckte Pointer-Arithmetik ist, dann erklärt sich so 
manches.

Übrigens ist das nicht nur reine Theorie. Ein C Compiler muss einen 
Ausdruck
1
  a[i]

tatsächlich zuallererst in die Pointer Form bringen
1
  *(a + i)

ehe er dann damit weitermachen kann.

Das trägt dann zu einer Stilblüte bei, die es so nur in C gibt. Denn a + 
i ist ja gleichwertig zu i + a

D.h. das zum Beispiel der Ausdruck
1
  *(a + 2)
identisch ist zum Ausdruck
1
  *(2 + a)

Du wirst jetzt sagen: no - na.

Aber die Sache geht weiter. Denn daraus und aus der Tatsache, dass [] 
Operationen sofort in Pointer Arithmetik umgewandelt werden muss, folgt 
auch, dass in C
1
   a[2]
und
1
  2[a]
in allen Belangen gleichwertig sind. Und letzters sieht dann schon ein 
wenig seltsam aus. Ist aber völlig legal! (Wenn auch schlechter Stil 
:-))

von Peter D. (peda)


Lesenswert?

Hier noch eine kuriose Sache mit Pointern:
1
#include <avr/io.h>
2
3
char x[] = "Hallo Peter";
4
char *y = x;
5
6
int main()
7
{
8
  char *p;
9
10
  p = x;
11
  PORTA = p[0]; // 1
12
13
  p = &x;
14
  PORTB = p[0]; // 2
15
16
  p = y;
17
  PORTC = p[0]; // 3
18
19
  p = &y;
20
  PORTD = p[0]; // 4
21
22
  for(;;);
23
}

Ausdruck 1, 2, 3 haben das gleiche Ergebnis, nur Ausdruck 4 nicht.
x ist ein Array, d.h. die Konstante: Adresse des 'H'.
Dann kann &x nicht berechnet werden, da diese Zahl nirgends gespeichert 
ist.
Und dann macht der Compiler: &x = x
Deshalb liefert Ausdruck 2 das gleiche Ergebnis wie Ausdruck 1.

y ist aber ein Pointer und daher hat er auch eine Adresse. Ausdruck 4 
gibt also nicht 'H' aus, sondern das low-Byte der Adresse von y.

Wer aber immer schön brav die Warnungen liest, kriegt auch ein:
test.c:13: warning: assignment from incompatible pointer type
test.c:19: warning: assignment from incompatible pointer type

von Yalu X. (yalu) (Moderator)


Lesenswert?

Peter Dannegger schrieb:
> Dann kann &x nicht berechnet werden, da diese Zahl nirgends gespeichert
> ist.

Vorsicht, jetzt hast du dich selber bei der Unterscheidung zwischen 
Array und Pointer verheddert.

Der Ausdruck &x ist mitnichten ein Pointer auf eine Zahl bzw. einen 
anderen Pointer, sondern ein Pointer auf ein Array, was einen großen 
Unterschied macht.

Die drei wichtigsten Regeln im Zusammenhang mit Arrays und Pointern 
lauten:
1
Regel 1:
2
3
Wenn dem Array-Namen ein sizeof oder der Adressoperator (&) vorangeht,
4
steht der Name für das gesamte Array, sonst wird er als Pointer auf das
5
erste Array-Element interpretiert.
6
7
Regel 2:
8
9
Der Ausdruck a[i] ist gleichbedeutend mit *(a+i). Folglich ist a[0] das
10
Gleiche wie *a.
11
12
Regel 3:
13
14
Das Ergebnis des Ausdrucks a+i ist ein Pointer, dessen Zahlenwert der
15
Zahlenwert von a plus das i-fache der Byte-Größe des Objekts, auf das a
16
zeigt, ist. Formal:
17
18
  #(a+i) = #a + i * sizeof (*a)
19
20
#p sei dabei der Zahlenwert des Pointers p.

In &x ist x also kein Pointer, sondern das Array selbst. Wird der 
Adressoperator auf ein Array angewendet werden, liefert er als Ergebnis 
logischerweise einen Pointer auf das Array. Wohlbemerkt auf das 
gesamte Array und nicht etwa nur auf dessen erstes Element! Beide 
haben zwar den gleichen Zahlenwert, aber nicht den gleichen Typ: Der 
Pointer auf das erste Element ist vom Typ (char *), während der Pointer 
auf das Array vom Typ ((char [12]) *) ist.

Das folgende, etwas modifizierte "Hallo Peter"-Programm macht den 
Unterschied deutlich:
1
#include <stdio.h>
2
3
char x[] = "Hallo Peter";
4
5
int main(void) {
6
7
  printf("%p\n", x);
8
9
  printf("%c\n", x[0]);
10
  printf("%c\n", x[1]);
11
12
  printf("%c\n", (&x)[0]);
13
  printf("%c\n", (&x)[1]);
14
15
  return 0;
16
}

Ausgabe (auf meinem PC, die Zeilen 1, 4 und 5 werden bei euch 
wahrscheinlich anders aussehen):
1
0x600940
2
H
3
a
4
@
5
L

0x600940 ist die Adresse von x, und 'H' und 'a' sind die ersten beiden 
Array-Elemente von x. Bis hierher ist das noch leicht nachvollziehbar.

Aber woher kommen das '@' und das 'L'?

Nach Regel 1 ist &x ein Pointer auf das Array. Durch Anhängen von [0] 
wird dieser Pointer dereferenziert, das Ergebnis ist also das Array 
selbst. Da vor dem Array-Ausdruck (&x)[0] weder  ein sizeof noch der 
Adressoperator steht, wird er nach Regel 1 als Pointer auf das erste 
Element interpretiert. Und dieser Pointer hat den Zahlenwert 0x600940. 
Die printf-Funktion mit dem "%c"-Format gibt davon die niederwertigsten 
8 Bits (0x40) als ASCII-Zeichen ('@') aus.

Der Ausdruck (&x)[1] in der nächsten Zeile ist nach Regel 2 
gleichbedeutend mit *(&x+1). &x ist dabei nach Regel 1 wieder ein 
Pointer auf das gesamte Array. Die Pointer-Addition +1 addiert nach 
Regel 3 zum Zahlenwert des Pointers die Byte-Größe des Arrays. Diese ist 
12, also wird 12 zur Adresse 0x600940 addiert, das Ergebnis ist 
0x60094C. Von diesem Wert zeigt printf wieder die niederwertigsten 8 
Bits als ASCII-Zeichen an, und das ist ein 'L'.

Wie man sieht, ist das mit den Arrays und Pointern ganz einfach, wenn 
man nur das Regelwerk für die Umwandlung zwischen den beiden im Kopf 
hat.

von Rosa-Kleidchen (Gast)


Lesenswert?

Karl Heinz Buchegger@
>Alles in einem Computer hat eine Adresse. Eine Adresse ist also nichts
>anderes als ein Zahlenwert.
Korrekt!

>Ein Pointer hingegen ist eine Variable, die so eine Adresse speichern
>kann.
Ist aber auch nur ein Zahlenwert als Adresse!
Ein Pointer ist (je nach native datatype) erreichbar durch eine Adresse, 
deren Inhalt eine Speicheradresse enthält!

>meinCharArray ist ein Array.
>Punkt.
meinCharArray repräsentiert den Anfang eines Arrays. Das Array selber 
steht fein sauber im Speicher!

>In dem Punkt unterscheiden sich "normale" Variablen und Arrays.
auch nicht. Beides sind Variablen. Die eine Speicherstelle reräsentiert 
den Wert der Variablen, die andere die Speicheradresse des Arrays.

ich höre hier auf
Rosa

von Karl H. (kbuchegg)


Lesenswert?

Rosa-Kleidchen schrieb:

>>In dem Punkt unterscheiden sich "normale" Variablen und Arrays.
> auch nicht. Beides sind Variablen. Die eine Speicherstelle reräsentiert
> den Wert der Variablen, die andere die Speicheradresse des Arrays.
>
> ich höre hier auf

Ist auch besser so. Du beginnst dich hier gerade in einen Wirbel 
reinzureden.

von Rosa-Kleidchen (Gast)


Lesenswert?

>Ist auch besser so. Du beginnst dich hier gerade in einen Wirbel
>reinzureden.
Ich beginne mich nicht in einen Wirbel hinein zureden. Meine 
Ausführungen sind kurz und knapp im Gegesatz zu deinen "Reden"!
Rosa

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rosa-Kleidchen schrieb:
>> Ist auch besser so. Du beginnst dich hier gerade in einen Wirbel
>>>reinzureden.
> Ich beginne mich nicht in einen Wirbel hinein zureden. Meine
> Ausführungen sind kurz und knapp im Gegesatz zu deinen "Reden"!

Dafür aber ungenau bis falsch, spätestens hier:

Rosa-Kleidchen schrieb:
> meinCharArray repräsentiert den Anfang eines Arrays.

Eben nicht immer. Du solltest dir vielleicht doch mal die "Reden" in 
diesem Thread durchlesen. Oder ein C-Buch.

von Rosa-Kleidchen (Gast)


Lesenswert?

>Eben nicht immer. Du solltest dir vielleicht doch mal die "Reden" in
>diesem Thread durchlesen. Oder ein C-Buch.
a.) Begründung für "eben nicht immer"???
b.) Guck dir das an, was im Speicher steht. Adressen und Werte. Mehr 
nicht!
Dann versteht man ziemlich schnell, wie sich das mit Pointer, Adressen, 
Variablen,.. verhält.
Rosa

Ps: K&R ist ein alter Hut...

von Oliver (Gast)


Lesenswert?

Rosa-Kleidchen schrieb:
> a.) Begründung für "eben nicht immer"???

Steht zwar schon oben, aber nochmal für dich: Der Unterschied wird dann 
sichbar, wenn du das Ergebnis von sizeof(meinCharArray) anschaust.

Rosa-Kleidchen schrieb:
> Ps: K&R ist ein alter Hut...

was nichts daran ändert, daß alles, was da drin steht, unverändert 
richtig ist.

Oliver

von Rosa-Kleidchen (Gast)


Lesenswert?

>Steht zwar schon oben, aber nochmal für dich: Der Unterschied wird dann
>sichbar, wenn du das Ergebnis von sizeof(meinCharArray) anschaust.
Nun gut!

nun ja, aber

char *myptr = null;
*myptr = &meinCharArray[0];

im Prinzip gibt es jetzt zwei Addressen im Speicher, die beide die 
Addresse des Arrays enthalten (so ungefähr):


myptr:         0x00001111: 0x00002222 ..
meinCharArray: 0x00001120: 0x00002222 ..

Array:         0x00002222: 0x00000000 0x00000001 0x00000002

oder?

von Karl H. (kbuchegg)


Lesenswert?

Rosa-Kleidchen schrieb:
>>Steht zwar schon oben, aber nochmal für dich: Der Unterschied wird dann
>>sichbar, wenn du das Ergebnis von sizeof(meinCharArray) anschaust.
> Nun gut!
>
> nun ja, aber
>
> char *myptr = null;
> *myptr = &meinCharArray[0];
>
> im Prinzip gibt es jetzt zwei Addressen im Speicher, die beide die
> Addresse des Arrays enthalten

Äh, nein.

Nimm den * bei
   * myptr = ....
weg und dann stimmts.

> myptr:         0x00001111: 0x00002222 ..
> meinCharArray: 0x00001120: 0x00002222 ..

Auch nicht.
meinCharArray ist keine Pointer Variable.

Mir scheint du solltest dir wirklich noch mal durchlesen was ich da 
weiter oben geschrieben habe anstatt da mit Halbwissen zu brillieren. 
Ich schreib meine Artikel nämlich nicht umsonst relativ ausführlich, 
wenn ich denke, dass es das Thema verdient hat und wichtig ist.

> oder?
Genau: oder!

von Rosa-Kleidchen (Gast)


Lesenswert?

>Auch nicht.
>meinCharArray ist keine Pointer Variable.
nu erzähl mir mal, wie meinCharArray im Speicher steht und wie der 
Inhalt aussieht.

>Mir scheint du solltest dir wirklich noch mal durchlesen was ich da
>weiter oben geschrieben habe anstatt da mit Halbwissen zu brillieren.
>Ich schreib meine Artikel nämlich nicht umsonst relativ ausführlich,
>wenn ich denke, dass es das Thema verdient hat und wichtig ist.
Zuerst einmal brilliere ich nicht. Anzeichen sind "so ist das und fertig 
und schau dir mal meine Ausführungen an"! Du solltest die Posts auch 
nicht persönlich nehmen und sachlich bleiben. Das zeugt von 
Sozialkompetenz.

Ich habe auch den Eindruck, das ich an deinem Verständnis vorbei rede 
und du mich nicht verstehtst. Wie dem auch sei...
Rosa

von Karl H. (kbuchegg)


Lesenswert?

Rosa-Kleidchen schrieb:

> Ich habe auch den Eindruck, das ich an deinem Verständnis vorbei rede
> und du mich nicht verstehtst.

Och.
Ich versteh sehr viel. Und meistens hol ich mir die relevanten 
INformationen auch dann noch aus den Postings raus, wenn mein Gegenüber 
das gar nicht beabsichtigt hat.

Und bei dir versteh ich, dass du den Zusammenhang Pointer-Variablen, 
Adressen  und Arrays noch nicht komplett richtig verstanden hast. Es 
gibt schlicht und ergreifend bei

char meinCharArray[] = "abcdef";

keine Stelle im Speicher an der die Adresse der Daten abgespeichert sein 
würde. Das hier

> meinCharArray: 0x00001120: 0x00002222 ..
> Array: 0x00002222: 0x00000000 0x00000001 0x00000002

ist Unsinn.
meinCharArray ist keine Pointervariable, in der die Adresse der 
eigentlichen Daten abgelegt wäre.

> Wie dem auch sei...

Genau.
In diesem Sinne.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rosa-Kleidchen schrieb:
> nu erzähl mir mal, wie meinCharArray im Speicher steht und wie der
> Inhalt aussieht.

Speziell für dich:

Die beiden Variablen seien wie folgt definiert:
1
char meinCharArray[] = "abcdef";
2
char *myptr = meinCharArray;

Angenommen, der verwendete Prozessor hat 16-Bit-Adressen, legt Daten im 
Speicher little-endian ab, meinCharArray liegt an der Adresse 0x1234, 
und myptr liegt an der Adresse 0x3456. Dann sieht das Speicherbbild 
der Variablen folgendermaßen aus:
1
meinCharArray:
2
3
Adresse  Inhalt   Erläuterung
4
5
        | .... |
6
        |––––––|
7
 0x1234 | 0x61 |  'a'
8
        |––––––|
9
 0x1235 | 0x62 |  'b'
10
        |––––––|
11
 0x1236 | 0x63 |  'c'
12
        |––––––|
13
 0x1237 | 0x64 |  'd'
14
        |––––––|
15
 0x1238 | 0x65 |  'e'
16
        |––––––|
17
 0x1239 | 0x66 |  'f'
18
        |––––––|
19
 0x123A | 0x00 |  '\0'
20
        |––––––|
21
        | .... |
22
23
24
myptr:
25
26
Adresse  Inhalt   Erläuterung
27
28
        | .... |
29
        |––––––|
30
 0x3456 | 0x34 |  lowbyte(0x1234)
31
        |––––––|
32
 0x3457 | 0x12 |  highbyte(0x1234)
33
        |––––––|
34
        | .... |

Entsprechend der Speicherbelegung liefert sizeof für die Variablen die 
Werte 7 und 2.

Wie du siehst, taucht die Array-Startadresse 0x1234 nur als Inhalt von 
myptr auf, und das auch nur, weil myptr so initialisiert worden ist. 
Das Array selbst besteht im Speicherabbild nur aus seinem Inhalt, also 
den 6 Buchstaben und dem String-Terminator '\0'.

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.