Zeiger in C und C++ - Universität Konstanz
Transcrição
Zeiger in C und C++ - Universität Konstanz
Zeiger in C und C++ Zeiger in C und C++ Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 1 Zeiger in C und C++ Zeiger in Java und C/C++ • Zeigervariable (kurz: Zeiger, engl.: pointer): eine Variable, die als Wert eine Speicheradresse enthält • Java: – Zeiger werden implizit für Referenztypen (Klassen und Arrays) verwendet, es gibt keine besondere Syntax für Zeigeroperationen • C/C++: – Zeiger sind eine eigene Gruppe von Datentypen und werden explizit durch den Programmierer als Zeiger deklariert Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 2 Zeiger in C und C++ Motivation • Warum benötigt man überhaupt Zeiger? – Hauptgrund: dynamische Speicherverwaltung • Adressen von Variablen werden vom Compiler festgelegt, d.h. der benötigte Speicherplatz muß zur Compilierzeit bekannt sein • Problem: die genauen Daten, die das Programm verarbeiten soll, sind zur Compilierzeit i.a. nicht bekannt, der Compiler kann nicht wissen, wieviel Speicherplatz benötigt wird • Lösung: Speicherplatz kann vom Programm zur Laufzeit angefordert (allokiert) werden, Zugriff auf diesen Speicherplatz erfordert Zeiger Datenstrukturen und Algorithmen WS2004/05 3 Universität Konstanz Zeiger in C und C++ Zeigerdeklaration • Zeiger sind in C/C++ typisiert, d.h. eine Zeigervariable enthält Informationen darüber, auf welchen Typ sie zeigt. • Zeigertypen werden bei der Deklaration durch das Zeichen "*" hinter dem Typ gekennzeichnet. – Beispiel: • Deklaration einer Variable vom Typ int (kein Zeiger): int i; • Deklaration eines Zeigers auf einen Wert vom Typ int: int* j; oft auch: int *j; Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 4 Zeiger in C und C++ Adressermittlung • Wie kann die Adresse, die ein Zeiger speichern soll, ermittelt werden? – erste Möglichkeit: Zeiger als Ergebnis einer Speicherallokation • die Speicherverwaltung liefert eine Adresse auf einen Speicherbereich, den das Programm benutzen darf • in C: – Funktion void* malloc(size_t size) (deklariert in stdlib.h) versucht, size Bytes Speicher zu allokieren Datenstrukturen und Algorithmen WS2004/05 5 Universität Konstanz Zeiger in C und C++ Adressermittlung – Ergebnis von malloc ist ein Zeiger vom Typ void* (untypisierter Zeiger), der mit allen Zeigertypen zuweisungskompatibel ist. – Achtung: Der Aufruf von malloc kann fehlschlagen, Ergebnis ist dann ein Zeiger mit dem Wert 0 (Nullpointer, zur Unterscheidung von der Zahl 0 oft durch die Konstante NULL bezeichnet). – Beispiel: Speicher für eine int-Variable allokieren: int *j=malloc(sizeof(int)); Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 6 Zeiger in C und C++ Adressermittlung • in C++: – Verwendung von malloc ist auch möglich, aber Zeiger vom Typ void* erfordern explizite Typumwandlung (type cast, Syntax wie in Java) – Beispiel: int *j=(int *)malloc(sizeof(int)); – Besser in C++: Verwendung des new-Operators, da bei Klassen automatisch Konstruktoren aufgerufen werden – Beispiel: int *j=new int; Datenstrukturen und Algorithmen WS2004/05 7 Universität Konstanz Zeiger in C und C++ Adressermittlung – Zweite Möglichkeit: Zeiger auf Adresse einer bereits existierenden Variablen • in C und C++: – der unäre Operator & liefert die Adresse der hinter ihm stehenden Variablen (nicht zu verwechseln mit dem binären Operator &, der das bitweise logische Und bildet) – Beispiel: int i; int *j=&i; j enthält die Adresse von i Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 8 Zeiger in C und C++ Speicherfreigabe • C/C++: Die Funktion free(void *ptr) (ebenfalls in stdlib.h deklariert) gibt einen mit malloc allokierten Speicherbereich frei. – Beispiel: free(j); • C++: Der Operator delete ruft bei Klassen den Destruktor auf und gibt den mit new allokierten Speicherbereich frei. – Beispiel: delete j; • Niemals mit malloc allokierten Speicher mit delete oder mit new allokierten Speicher mit free freigeben! Datenstrukturen und Algorithmen WS2004/05 9 Universität Konstanz Zeiger in C und C++ Dereferenzierung von Zeigern • Dereferenzierung: Zugriff auf den Wert, der an der in der Zeigervariablen gespeicherten Adresse steht ("der Wert, auf den der Zeiger zeigt") • Zur Dereferenzierung eines Zeigers dient der unäre Operator * (nicht zu verwechseln mit dem binären Multiplikationsoperator *). • Beispiel: int *i=new int; int j; *i=3; j=*i;//j hat nach dieser Zeile den Wert 3 Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 10 Zeiger in C und C++ Pointer Aliasing • Pointer Aliasing: Zwei Zeiger enthalten dieselbe Adresse oder ein Zeiger enthält die Adresse einer anderen sichtbaren Variablen. Der eine Zeiger ist dann ein Alias des anderen Zeigers bzw. der Variablen. • Unnötiges Pointer Aliasing sollte vermieden werden, da es dem Compiler die Optimierung erschwert. • Beispiel: int i=1; int *j=&i; *j=3;//i wird über Alias j geändert • Optimierungsproblem: i kann nicht in einem CPU-Register gehalten werden, da der Wert im Speicher geändert wird. Datenstrukturen und Algorithmen WS2004/05 11 Universität Konstanz Zeiger in C und C++ Arrays • Array in C und C++: Zusammenhängender Speicherbereich einer festen (zur Compilierzeit bekannten) Größe • Die Arrayvariable wird wie ein Zeiger auf den Anfang dieses Speicherbereichs behandelt. • Deklaration eines Arrays: Typ Name[Anzahl der Elemente] • Beispiel: Array mit 100 Elementen vom Typ int: int i[100]; • Zugriff auf Arrayelemente: wie in Java i[12]++; //Element mit Index 12 erhöhen • Es gibt kein Feld length bei Arrays in C/C++. Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 12 Zeiger in C und C++ Arrays • Vorsicht beim Zugriff auf Arrayelemente: – Es findet keine Bereichsüberprüfung statt. – Ändern eines Arrayelements mit einem Index außerhalb der Arraygröße führt zum Überschreiben von fremdem Speicher (kann eigenes Programm oder unter manchen Betriebssystemen auch andere Programme ungewollt verändern). – Beispiel: (das ist gültiger C-Code!) int data[10]; int index=-10000; data[index]=0; //BOOM! Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 13 Zeiger in C und C++ Pufferüberlauf - Buffer Overflow • Häufiger Fehler in C/C++-Programmen: die fehlende Bereichsüberprüfung wird nicht berücksichtigt, wenn externe Daten eingelesen werden, der Lesepuffer kann überlaufen (Buffer Overflow) – besonders konstruierte Eingabedaten können das Programm gezielt verändern – besonders gefährdet sind Programme, die Eingaben aus dem Internet verarbeiten (z.B. HTTP-Server) • Angreifer können den Fehler ausnutzen, um beliebigen Programmcode auf dem Zielrechner auszuführen (sog. Internet-Würmer führen solche Attacken automatisiert durch (Bsp: Code Red)) Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 14 Zeiger in C und C++ Pufferüberlauf - Buffer Overflow • Beispiel für ein Programm mit einem Buffer OverflowFehler (kopiert zeilenweise Eingabe nach Ausgabe): #include <stdio.h> void main() { char buf[1000]; //no line is longer while(gets(buf)!=NULL) //read line { puts(buf); //write line } } Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 15 Zeiger in C und C++ Pufferüberlauf - Buffer Overflow • Wie kann ein Angreifer beliebigen Programmcode ausführen? – Stack nach dem Aufrufen der main-Funktion: buf[0] buf[1] buf[999] Rücksprungadresse niedrige Adressen (Der Stack wächst auf x86Systemen in Richtung niedrige Adressen.) hohe Adressen Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 16 Zeiger in C und C++ Pufferüberlauf - Buffer Overflow – Der Angreifer erzeugt eine Textzeile, die länger als 1000 Zeichen ist. – Dadurch wird die Rücksprungadresse überschrieben und das Programm stürzt beim Verlassen der mainFunktion ab (im günstigsten Fall). – Die Kunst des Angreifers besteht darin, die Rücksprungadresse so zu überschreiben, das der neue Wert in den Puffer zeigt. Dort kann beliebiger Programmcode stehen (der vorher als Textzeile eingelesen wurde). • Deshalb: Niemals irgendwelche Annahmen über fremde Eingabedaten machen, ohne sie nachzuprüfen! Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 17 Zeiger in C und C++ Pufferüberlauf - Buffer Overflow • Beispiel für einen speziell konstruierten HTTP-Request-String (aus dem Logfile eines Webservers, erzeugt vom Wurm Code Red II, infizierte weltweit ca. 400000 MS Internet Information Server innerhalb weniger Stunden): GET /default.ida?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX%u9090% u6858%ucbd3%u7801%u9090%u6858%ucbd3%u7801%u9090% u6858%ucbd3%u7801%u9090%u9090%u8190%u00c3%u0003% u8b00%u531b%u53ff%u0078%u0000%u00=a nur zum Füllen des Puffers Datenstrukturen und Algorithmen WS2004/05 eingeschleuster Code Universität Konstanz 18 Zeiger in C und C++ Strings • Strings werden in C durch char-Arrays dargestellt, das Ende des Strings wird durch ein Element mit dem Wert 0 markiert (Strings in C sind nullterminiert). • Es können String-Konstanten verwendet werden (Notation wie in Java), der Compiler erzeugt dann selbst ein entsprechendes char-Array. • Beispiel: char *s1="Text"; char s2[5]; s2[0]='T'; s2[1]='e'; s2[2]='x'; s2[3]='t'; s2[4]=0; s1 und s2 zeigen beide auf einen String "Text" Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 19 Zeiger in C und C++ Zeigerarithmetik • In Java: Referenzen (Zeiger) sind fest und können nur durch Zuweisen einer anderen Referenz verändert werden. • In C/C++: Zu Zeigern können Werte addiert und subtrahiert werden (Zeigerarithmetik), dadurch ist z.B. das schrittweise Schieben eines Zeigers über ein Array möglich. • Addieren eines Wertes zu einem Zeiger erhöht den Zeiger um diesen Wert multipliziert mit der Größe des Typs, auf den der Zeiger zeigt. (Subtraktion funktioniert analog) • Mit Zeigern vom Typ void* ist keine Zeigerarithmetik möglich (void hat keine Größe). Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 20 Zeiger in C und C++ Zeigerarithmetik • Beispiel: Füllen eines Arrays mit 0 Ohne Zeigerarithmetik: #define size 100; int data[size]; int i; for(i=0;i!=size;i++) data[i]=0; Mit Zeigerarithmetik: #define size 100; int data[size]; int *i; for(i=data;i!=(data+size);i++) *i=0; Datenstrukturen und Algorithmen WS2004/05 21 Universität Konstanz Zeiger in C und C++ Referenzen in C++ • Nur in C++: Referenzen sind ein weiterer Zeigertyp, der nur für Funktionsparameter verwendet werden kann. • Referenzen sind Zeiger, werden aber automatisch dereferenziert (ähnlich wie in Java) und die Adresse kann nicht verändert werden. • Deklaration einer Referenz: wie Zeiger, aber & statt * • Beispiel: void setIntToOne(int &i) { i=1; } Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 22 Zeiger in C und C++ Referenzen in C++ • Dasselbe Beispiel mit normalen Zeigern statt Referenzen: void setIntToOne(int *i) { *i=1; } • Nach Möglichkeit sollte man Referenzen den Zeigern vorziehen, da der Compiler sie besser optimieren kann (kein Aliasing möglich). Datenstrukturen und Algorithmen WS2004/05 23 Universität Konstanz Zeiger in C und C++ Zusammenfassung • Zeiger sind ein mächtiges Werkzeug in C und C++ und werden oft benötigt, aber: – Fehlerhafter Umgang mit Zeigern ist die wahrscheinlich häufigste Ursache für Fehler in C/C++-Programmen. – Oft vorkommende Fehler sind: • Dereferenzierung von Zeigern, nachdem ihr Speicherbereich freigegeben (oder noch schlimmer: schon wieder anderweitig vergeben) wurde ("wilde Zeiger") • Überschreitung von Arraygrenzen (Buffer Overflow) – Sorgfältiges Arbeiten mit Zeigern ist notwendig. Datenstrukturen und Algorithmen WS2004/05 Universität Konstanz 24