Pointer Anfängerfrage

Gast #5280941
Lesenswert?

Hallo,

ich lese gerade den "C Primer Plaus" und mache die erste Übung zu 
Pointern und bin schon am verzweifeln.
1
const float rain[YEARS][MONTHS] = { { 4.3, 4.3, 4.3, 3.0, 2.0, 1.2, 0.2,
2
      0.2, 0.4, 2.4, 3.5, 6.6 }, { 8.5, 8.2, 1.2, 1.6, 2.4, 0.0, 5.2,
3
      0.9, 0.3, 0.9, 1.4, 7.3 }, { 9.1, 8.5, 6.7, 4.3, 2.1, 0.8, 0.2,
4
      0.2, 1.1, 2.3, 6.1, 8.4 }, { 7.2, 9.9, 8.4, 3.3, 1.2, 0.8, 0.4,
5
      0.0, 0.6, 1.7, 4.3, 6.2 }, { 7.6, 5.6, 3.8, 2.8, 3.8, 0.2, 0.0,
6
      0.0, 0.0, 1.3, 2.5, 5.2 } };
7
  int year, month;
8
  float subtot, total;
9

10
  printf(" YEAR RAINFALL (inches)\n");
11
  for (year = 0, total = 0; year < YEARS; year++) { // for each year, sum rainfall for each month
12
    for (month = 0, subtot = 0; month < MONTHS; month++)
13
    subtot += rain[year][month];
14
    printf("%5d %15.1f\n", 2000 + year, subtot);
15
    total += subtot; // total for all years
16
  }



Es soll die Zeile "subtot += rain[year][month]" durch Pointer ersetzt 
werden.
Ich habe folgende Lösung die nicht funktioniert:
subtot +=  *(rain + ((year * MONTHS) + month) ) ;

Wie bin ich auf diese Lösung gekommen?
Dies steht im Buch:
*(dates + 2) /* value of the 3rd element of dates */
Gast #5280962
Lesenswert?

Hallo!
Das Beispiel mit dates aus Deinem Buch war fuer ein
1D Array. In Deinem Fall musst Du das Prinzip zweimal
anwenden:
*(rain + year) ergibt das 1D Array mit den monatlichen Regenfaellen
fuer ein gegebens Jahr (year).
*(*(rain + year) + month) ergibt dann das entsprechende Element
aus diesem Array fuer einen gegebenen Monat (month).
Du benoetigst also:
subtot += *(*(rain + year) + month);
Das sollte das Buch aber eigentlich erlaeutern, falls es etwas taugt.
Gast #5280964
Lesenswert?

Das dazugehörige Basiswissen ist, dass in C die Schreibweise Array[] 
äquivalent ist zu *(Array + Index). Sprich: setzt du einen Pointer ptr 
auf das erste Arrayelemente, kannst du mit einem Inkrement des Pointers 
zum nächsten springen. In deinem Fall sind es zwei verschachtelte 
Arrays. Bringt dich das weiter?
Gast #5280986
Lesenswert?

Kellernerd schrieb:
> subtot += *(*(rain + year) + month);

Diese Antwort ist richtig.

Leider verstehe ich es nicht. Ich glaube ich hab mir die Sache einfach 
falsch vorgestellt. Ich dachte die Elemente stehen hintereinander im 
Speicher und ich muss abzählen.
Auf diese Lösung wäre ich in 100 Jahren nicht gekommen.
#5281000
Lesenswert?

rain ist ein Array von einem Array von const float.

rain[0] gibt die Adresse vom ersten Jahr und ist vom Typ "Zeiger auf 
const float"
rain[1] gibt die Adresse vom zweiten Jahr

rain gibt die Adresse vom ersten Array und ist vom Typ "Zeiger auf Array 
12 von const float"


Da Array[index] und *(Array+index) identisch sind, hast du für
rain[year][month] dann *(rain[year]+month)) und schließlich
*(*(rain+year)+month))
#5281001
Lesenswert?

Anmerkungen:

Da Array[index] und *(Array+index) identisch sind, und für die Addition 
das Kommutativgesetz gilt, kann man auch *(index+Array)  schreiben.
Darum ist auch index[Array] in C erlaubt

rain und rain[0] ergeben dieselbe Adresse, sind aber von 
unterschiedlichem Typ.

rain+1 zeigt auf das zweite Jahr, während rain[0]+1 auf den zweiten 
Monat im ersten Jahr zeigt (wie &rain[0][1] )
Gast #5281045
Lesenswert?

&rain[0][0] = 0028FE30
&rain[0][1] = 0028FE34
&rain[0][2] = 0028FE38
&rain[0][3] = 0028FE3C
&rain[0][4] = 0028FE40
&rain[0][5] = 0028FE44
&rain[0][6] = 0028FE48
&rain[0][7] = 0028FE4C
&rain[0][8] = 0028FE50
&rain[0][9] = 0028FE54
&rain[0][10] = 0028FE58
&rain[0][11] = 0028FE5C
&rain[1][0] = 0028FE60
&rain[1][1] = 0028FE64
&rain[1][2] = 0028FE68
&rain[1][3] = 0028FE6C
&rain[1][4] = 0028FE70
&rain[1][5] = 0028FE74
&rain[1][6] = 0028FE78
&rain[1][7] = 0028FE7C
&rain[1][8] = 0028FE80
&rain[1][9] = 0028FE84
&rain[1][10] = 0028FE88
&rain[1][11] = 0028FE8C
&rain[2][0] = 0028FE90
&rain[2][1] = 0028FE94
&rain[2][2] = 0028FE98
&rain[2][3] = 0028FE9C
&rain[2][4] = 0028FEA0
&rain[2][5] = 0028FEA4
&rain[2][6] = 0028FEA8
&rain[2][7] = 0028FEAC
&rain[2][8] = 0028FEB0
&rain[2][9] = 0028FEB4
&rain[2][10] = 0028FEB8
&rain[2][11] = 0028FEBC
&rain[3][0] = 0028FEC0
&rain[3][1] = 0028FEC4
&rain[3][2] = 0028FEC8
&rain[3][3] = 0028FECC
&rain[3][4] = 0028FED0
&rain[3][5] = 0028FED4
&rain[3][6] = 0028FED8
&rain[3][7] = 0028FEDC
&rain[3][8] = 0028FEE0
&rain[3][9] = 0028FEE4
&rain[3][10] = 0028FEE8
&rain[3][11] = 0028FEEC
&rain[4][0] = 0028FEF0
&rain[4][1] = 0028FEF4
&rain[4][2] = 0028FEF8
&rain[4][3] = 0028FEFC
&rain[4][4] = 0028FF00
&rain[4][5] = 0028FF04
&rain[4][6] = 0028FF08
&rain[4][7] = 0028FF0C
&rain[4][8] = 0028FF10
&rain[4][9] = 0028FF14
&rain[4][10] = 0028FF18
&rain[4][11] = 0028FF1C


Die Reihenfolge ist ne andere.

Das ist die Ausgabe von:
printf("&rain[%i][%i] = %p\n", year,month, &rain[year][month]);
Gast #5281059
Lesenswert?

Michael schrieb:
> [...]
> falsch vorgestellt. Ich dachte die Elemente stehen hintereinander im
> Speicher und ich muss abzählen.

Die Elemente stehen, wie von Dir vermutet und von anderen Kommentatoren 
bereits erlaeutert, tatsaechlich hintereinander im Speicher. Die von Dir 
angenommene Auswahl mittels
Gesamtindexberechnung gilt allerdings nur in eindimensionalen Arrays.
Du koenntest also ein 1D Array erzeugen und dort alle Niederschlagswerte
hintereinander hineinschreiben (= recht unuebersichtlich) oder aber auch
Deine Adresse eines zweidimensionalen Arrays (rain) als Adresse eines
eindimensionalen Arrays uminterpretieren (casten), um den von Dir
angenommen Zugriff zu realisieren:
1
subtot += *((const float *) rain + year * MONTHS + month);
Hier musst Du jedoch Vorwissen haben, wie die Werte eines
zweidimensionalen Arrays bei der Sprache C im Speicher abgelegt werden
(reihenweise oder spaltenweise) und ausserdem diese haessliche
Multiplikation mit einer Konstanten hinzufuegen. Schlimmer wird es
noch bei hoeheren Dimensionen (wenn Du z.B. noch Tag und Stunde 
haettest).
Das mehrdimensionale Array ist hier eleganter und leichter 
lesbar/verstaendlich.

Das Umschreiben in einen Zugriff mittels Zeiger soll dem Leser des
Buches wohl die Zusammenhaenge erlaeutern, denn es resultiert in
schlechterer Lesbarkeit und mehr Tipparbeit. Falls dies wirklich das
erste Beispiel zu Zeigern ist, ist der Buchautor etwas fies.

Ich wuerde Dir (bei Interesse) empfehlen, mehrere minimale
Beispielprogramme mit unterschiedlichen Arraydimensionen und
Zugriffsarten auszuprobieren  und diese genau mittels Debugger auf ihr 
Speicherlayout hin zu untersuchen.
Eeine spannende Erweiterung waere
z.B. ein Cast/Zugriff mittels (void *), so dass keine Kenntnis
mehr ueber die Groesse der Elemente im Array besteht und Du dann
entsprechend Zeigerarithmetik mit Bytes machen musst:
1
subtot += *((const void *) rain /* + ??? */);
.
Mit der Printf-Direktive %p kannst Du Dir Adressen ausgeben lassen.
Das ist fuer Experimente bei der Adressberechnung sehr hilfreich.
Z.B die Adresse Deines Arrays (rain) oder die eines Arrayelementes:
1
printf ("%p\n%p\n%p\n", rain, &rain, &rain[2][2]);
Wenn Du begruenden kannst, warum rain und &rain den
gleichen Wert liefern, hast Du den Themenkomplex recht gut verstanden.
Viel Spass beim Experimentieren und lass Dich nicht von der
Adressrandomisierung verwirren, bzw. schalte sie fuer Deine
Beispiele ab, da ansonsten Dein Array bei jedem Programmstart an einer
anderen Anfangsadresse liegt (dies ist eigentlich ein Schutz, damit
Angreifer die Adressen nicht so leicht erfahren und im Fall von
Programmierfehlern ausnutzen koennen).

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren