Pointer-Arithmetik

Ein Pointer oder allgemein eine Adresse kann als Integer-Zahl interpretiert werden: Im Speicher werden Daten Byteweise abgelegt und jede Adresse zeigt auf ein ganz bestimmtes Byte. Da jedoch viele Werte mehr Platz benötigen als 1 Byte, zeigen Pointer in C und C++ nur auf die Adresse im Speicher, welche das erste Byte des Wertes representiert. Würde auf ein Byte zugegriffen werden, welches nicht das erste Byte eines Wertes representiert, so würden die adressierten Daten strukturell falsch interpretiert werden und resultieren in sinnlosen Werten. Um diese Falsch-Interpretation möglichst zu vermeiden, wurde die Pointer-Arithmetik eingeführt, welche spezielle Regeln für das Auftreten eines Pointers bei verschiedenen Operatoren festlegt.

Details

Die Pointer-Arithmetik spezifiziert spezielle Verhaltensregeln bei Additions-, Subtraktions-, Zuweisungs- und Vergleichs-Operatoren. Da diese Operatoren stets zwei Operanden erwarten, müssen die verwendeten Pointer-Typen kompatibel zueinander sein. Pointer-Typen sind grundsätzlich dann kompatibel, wenn sie gleich sind. In C++ sind jedoch Pointer-Typen zusätzlich kompatibel, wenn ein Pointer-Typ einer abgeleiteten Klasse des anderen Pointer-Typs entspricht. Der Wert NULL ist sowohl in C als auch in C++ stets kompatibel (siehe dazu Beschreibung des Null-Pointers).

Addition, Subtraktion

Tritt bei einer Addition oder Subtraktion ein Pointer zusammen mit einer Integer-Zahl auf, so multipliziert der Compiler vor der Addition diese Zahl mit einer Einheit, welche durch den Typ des Pointers festgelegt ist. Diese Einheit umfasst die Grösse der durch den Pointer adressierten Daten. Bei einem Pointer auf einen int-Typ beispielsweise wird bei der Pointer-Arithmetik mit der Einheit 4 gerechnet, bei einem Pointer auf einen double-Typ wird mit 8 Byte als Einheit gerechnet, bei einem Pointer auf einen struct-Typ, welcher insgesamt 34 Bytes benötigt, wird mit der Einheit 34 gerechnet. Die jeweilige Einheit kann mittels des sizeof-Operators ermittelt werden.

Im folgenden Beispiel sind einige Operationen aufgeführt, zusammen mit der dadurch errechneten Adresse.

4
i      at 0x1234
x       = 0x1234
x+(4*1) = 0x1238
x+(4*2) = 0x123c
x-(4*3) = 0x1240
sizeof(int);
int i;
int* x = &i;
x + 1;
x + 2;
x - 3;

Der Rückgabewert des Additions- und Subtraktions-Operators ist ein rvalue und im Falle der Verknüpfung eines Pointers mit einer Integer-Zahl wiederum der Typ des Pointers. Es ist somit möglich, diesen Wert einer Pointer-Variablen zuzuweisen. Infolge dessen sind diese Rechenregeln auch gültig für den Additions-Zuweisung und Subtraktions-Zuweisungs-Operator. Genau dasselbe gilt für den Pre-Increment- und Post-Increment-Operator, sowie den Pre-Decrement- und Post-Decrement-Operator, welche im Zusammenhang mit Pointer-Arithmetik somit die Bedeutung der Verschiebung des Pointers um eine Einheit erhalten.

Die Verwendung des Post-Increment-Operators wird sehr häufig in Kombination mit dem Dereferenz-Operator gesehen, wenn ein Array sequentiell durchgearbeitet werden muss. Diese Art der Pointer-Arithmetik wird gerne eingesetzt, um die Performance eines Codes zu steigern. Zum einen wird durch die Verwendung des Increment-Operators die Berechnung der Adresse auf eine simple Addition vereinfacht und zum anderen wird durch den Zugriff auf direkt benachbarte Daten der Cache eines Prozessors besser ausgenutzt. Im folgenden Beispiel wird ein Array mittels Pointer-Arithmetik gefüllt und danach ebenso angesprochen:










01234
#include <stdio.h>

int main(){
  int i;
  int array[5];
  int* p;
  p = array;
  for(i = 0; i<5; i++){*p++ = i;}
  p = array;
  for(i = 0; i<5; i++){printf("%d", *p++);}
  return 0;
}

Die Addition zweier Pointer ist nicht erlaubt. Sofern jedoch zwei Pointer denselben Typ haben, können sie voneinander subtrahiert werden: Die Subtraktion eines Pointers von einem anderen Pointer ergibt die Anzahl dazwischenliegender Elemente (sehr nützlich bei Arrays).




2
int array[5];
int* p1 = &array[1];
int* p2 = &array[3];
printf("%d\n", p2 - p1);

Zuweisungen, Vergleiche

In früheren Zeiten wurde aufgrund der Nähe der Sprache C zu Assembler Pointer-Arithmetik genutzt, um Pointer wie Integer-Zahlen zu behandeln und beliebig hin und her zu wechseln, sie zu vergleichen und gegenseitig zuzuweisen. In C ist dies ohne Probleme möglich, ergibt jedoch mit neueren Compilern Warnungen. In C++ werden solche beliebige Umwandlungen nicht mehr toleriert und ergeben Compiler-Fehler. Nur noch die durch die Pointer-Arithmetik spezifizierten Umwandlungen sind erlaubt.

Der Zuweisungs-Operator erlaubt es, eine Adresse in einer Variablen zu speichern. Wird versucht, einen unkompatiblen Typ zuzuweisen, so wird in C eine Warnung wie assignment from incompatible pointer type und in C++ gar ein Fehler cannot convert ... ausgegeben. Nebst dem normalen Zuweisungs-Operator sind zusätzlich auch die Additions-Zuweisung und die Subtraktions-Zuweisung für Pointer erlaubt. Hierbei sind jedoch die oben stehenden Regeln für die Addition und die Subtraktion zu beachten.

Ein Zuweisung von Pointern auf Integer-Typen ergibt in C eine Warnung wie ... makes integer from pointer without a cast. der umgekehrte Fall (Integer auf Pointer) ergibt die umgekehrte Warnung ... makes pointer from integer without a cast. In C++ ergibt jegliche Zuweisung unkompatibler Pointer-Typen einen invalid conversion-Fehler.

Die Operatoren ==, !=, <, <=, > und >= erlauben es, Pointer miteinander zu vergleichen. Ein Vergleich unkompatibler Typer resultiert in C in einer Warnung wie comparison of distinct pointer types lacks a cast und in C++ gar in einem entsprechenden Fehler. Diese Warnung sowie der Fehler kann jedoch durch ein explizites Casting behoben werden. Ein Vergleich zwischen Pointern und Integer-Typen ergibt in C eine Warnung wie comparison between pointer and integer, in C++ gar einen dementsprechenden Fehler. Ein Vergleich zwischen Fliesskommazahlen und Pointern ergibt stets einen invalid operands-Fehler.

Achtung: Bei einem Vergleich zweier Pointer werden nur die jeweiligen Adressen verglichen, nicht aber deren Inhalt. Der Inhalt eines Pointers kann mittels des Dereferenz-Operators * angesprochen werden. Um den Inhalt von ganzen Arrays oder Strings zu vergleichen, müssen Funktionen wie memcmp oder strcmp verwendet werden.