Skip to content

Instantly share code, notes, and snippets.

@wilhelmy
Created December 6, 2013 22:10
Show Gist options
  • Save wilhelmy/7832997 to your computer and use it in GitHub Desktop.
Save wilhelmy/7832997 to your computer and use it in GitHub Desktop.
People kept asking, thought I could give explaining things a shot for once
How2Pointer
===========
Speicher besteht aus linear angeordneten einzelnen Zellen, diese Zellen haben
Adressen, quasi der Index an der ein Wert steht. Adresswerte werden
üblicherweise im Hexadezimalsystem angegeben. Jede Zelle ist dabei 8 bit = 1
byte breit. Im unten stehenden Diagramm sind dabei 4 1-byte-Werte
zu einem 32-bit breiten sog. "Maschinenwort" zusammengefasst, die Adressen
zwischen diesen Adressen zeigen aber dazwischen (z.B. sind sowohl 0x0F5023A0 als
auch 0x0F5023A1 gültige Adressen, obwohl unten einige Werte dazwischen
übersprungen werden). Die Breite des Maschinenwortes entspricht dabei nicht
notwendigerweise der Adressbreite der Architektur (sicher habt ihr schonmal
etwas von 32-bit und 64-bit Prozessoren gehört), diese definiert dagegen, wie
groß Adressen sind die der Prozessor maximal ansprechen kann. Offensichtlich
kann es aber sinnvoll sein, Adressbreite und Breite des Maschinenwortes gleich
zu wählen, wenn man einen Prozessor designt. (Muss aber nicht, z.B. wegen
Abwärtskompatibilität)
Speicherzelle Adresse
+-------------+
| | 0x00000000 NULL-Pointer (*)
| undefiniert | 0x00000001 (dieser Bereich ist nicht notwendig undefiniert,
| | 0x00000002 aber es gibt Speicher, der nicht definierte Werte
| | 0x00000003 enthält. Beim Programmstart sind alle Werte un-
. . . definiert bzw. uninitialisiert, daher gibt
. . . int x; printf("%d", x); irgendwelchen Blödsinn
. . . aus)
| 0x000000012 | 0x0F5023A0
| 0x000000011 | 0x0F5023A4
| 0x000000042 | 0x0F5023A8 Hier stehen irgendwelche Werte
| 0x000001337 | 0x0F5023AB
| | 0x0F5023B0
| | 0x0F5023B4
. .
. .
. .
+-------------+
Im Adressbereich zwischen 0x0F5023A0 und 0x0F5023AB stehen dabei Werte, die im
Programm benutzt werden. (Unter der Annahme, dass dieser Speicher im
Adressbereich des Programms liegt. Da auf einem Computer mehrere Programme
gleichzeitig geladen sein können, wird der Adressraum unterteilt und jedem
Programm ein Adressbereich zugewiesen, der später bei Bedarf vergrößert oder
verkleinert werden kann. Das tun in C z.B. malloc, realloc und free bzw. die
systemspezifischen Funktionen, die diese drei intern aufrufen).
Ein Pointer auf int, z.B. int *a deklariert dabei eine Variable a vom Typ
"int *", was nicht das gleiche wie int ist. Dabei wird ein Eintrag im Speicher
angelegt, der breit genug ist um einen Zeiger aufzunehmen, also üblicherweise so
breit wie die Adressbreite der benutzten Prozessorarchitektur. Dieser Eintrag
nimmt Adressen auf, an denen ein int steht. Beispiel:
int a;
int *A = &a;
Hier wird eine int-Variable deklariert (d.h. Speicherplatz reserviert, auf
Prozessoren mit intel x86-Architektur typischerweise 4 byte = 32 bit), die nicht
initialisiert wird, also keine sinnvollen Daten enthält und anschließend wird
eine int*-Variable A deklariert, der die Adresse der Variablen a zugewiesen
wird. Anschließend kann indirekt über den Zeiger auf den Inhalt von a
zugegriffen werden, z.B.:
*A = 12;
printf("%d %d\n", a, *A);
Dies gibt 12 12 aus.
Insbesondere ist es wichtig, die Operatorpräzedenz (d.h. Ausführungsreihen-
folge) zu beachten: Der Inrekement-Operator ++ hat höhere Präzedenz als der
Zeiger-Dereferenzierungs- Operator *, d.h.
*A++
hat möglicherweise nicht den erwarteten Effekt, dass zuerst A dereferenziert
wird und anschließend der an dieser Position stehende Wert inkrementiert wird,
stattdessen wird die Zeigervariable selbst um die Größe des Typs in byte erhöht,
auf den sie zeigt (in diesem Fall int, also 4) und anschließend dereferenziert.
Dies greift im obigen Fall schon auf undefinierten Speicher zu, weil hinter a
nicht unbedingt etwas im Speicher steht. Richtig wäre in diesem Fall:
(*A)++
Wichtig ist im Pointer-Kontext auch, dass Arrays sequentiell im Speicher liegen,
wenn also A ein Pointer auf den ersten Wert eines int-Arrays ist, zeigt A+1 auf
den zweiten. Dabei wird ebenfalls die Größe des von A referenzierten Typs
berücksichtigt, d.h. wenn A vom Typ int* ist, zeigt A+1 4 bytes weiter. Der
Index-Operator [] tut nichts anderes als den Wert, der in den Klammern steht
wiefolgt zu dereferenzieren:
A[i] == *(A + i)
also insbesondere zeigen A+i und A[i] auch auf die gleiche Adresse:
&A[i] == A+i
Diese Gleichheit gilt immer, wenn der Index-Operator benutzt wird, also auch
auf Arrays, bzw ist der Indexoperator tatsächlich genau so definiert, dass
A[ausdruck]
zu
*(A + (ausdruck))
expandiert wird.
--
Autor: moritz barfooze de, insert an at and a dot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment