|
Bei dynamischer Allokation geht man folgendermaßen vor:
- Es wird also zuerst eine Zeigervariable entsprechenden Typs definiert, die
aber noch keinen sinnvollen Wert besitzt.
- Dann wird für das Objekt, auf das die Zeigervariable verweisen soll,
ausreichend Speicher allokiert und nun der Zeigervariablen die Adresse dieses Speichers
zugewiesen.
Es ist wichtig, sich den Unterschied zum äquivalenten statischen Array
klarzumachen. Während dyn eine Variable ist, deren Speicherplatz
sich aus &dyn ergibt, ist stat eine konstante Adresse.
Interessant ist die Frage, was denn dann &stat ist. Intern und formal wird
dazu folgender Trick verwendet: &stat entspricht stat, die
Adressen sind also identisch.
Die dynamische Speicherbelegung erfolgt mittels spezieller, in der Standardbibliothek
enthaltenen, Speicherallokationsfunktionen. size_t ist dabei ein
maschinenabhängig definierter Datentyp unsigned int, definiert in
<stdlib.h>, <stdio.h.h>, <stddef.h>,
etc.).
- void *malloc(size_t size)
belegt einen size Bytes großen zusammenhängenden Bereich
und liefert die Anfangsadresse davon zurück.
- void *calloc(size_t nobj, size_t size)
liefert die Anfangsadresse zu nobj*size Bytes großem Bereich
zurück; der Inhalt dieses Bereiches ist mit dem Wert 0 initialisiert.
- void * realloc(void *ptr, size_t size)
Veränderung der Größe eines allokierten Speicherblocks.
- void free(void *)
wird schließlich verwendet, um nicht mehr benötigten dynamisch belegten
Speicherplatz wieder freizugeben.
- size_t sizeof(something)
ist ein Operator, wobei something nicht nur eine einfache oder strukturierte
Variable sein kann, sondern auch ein Datentyp. Der Operator liefert die Länge
des vom Argument belegten Arguments). Es ist ratsam, statt einer konstanten Größe
eine Datenelementes anzugeben, die Größe mit sizeof() zu
bestimmen. Erstens kann man sich nicht vertun und zweites sind soche Programme
portabler. Also z. B. statt calloc(2,100) besser calloc(sizeof(int),100)
verwenden.
Die Speicherallokations-Funktionen liefern die Anfangsadresse des allokierten Blocks als
void-Pointer (void *) es ist daher kein Type-Cast bei Zuweisung an
Pointer-Variable erforderlich. Um dem Programm mehr Klarheit zu geben, schadet es aber
auch nicht, Type-Cast zu verwenden.
Die Funktionen malloc und calloc liefern bei Fehler (z. B.
wenn der angeforderte Speicherplatz nicht zur Verfügung steht) den
Nullpointer NULL zurück. Nach jedem Aufruf sollte deshalb deren
Rückgabewert getestet werden! Dies kann mit void assert(int)
geschehen. assert() ist ein Makro und benötigt das Headerfile
<assert.h> und u. U. <stdio.h>. Ist das Argument
NULL, wird das Programm abgebrochen, der Modulname und die Programmzeilennummer
des assert-Aufrufs ausgegeben.
Für malloc, calloc und free wird das Headerfile
<stdlib.h> oder <alloc.h> benötigt.
Der für die dynamische Speicherverwaltung zur Verfügung stehende
Speicherbereich wird als Heap bezeichnet. Die Lebensdauer dynamisch
allokierten Speichers ist nicht an die Ausführungszeit eines Blocks
gebunden. Nicht mehr benötigter dynamisch allokierter Speicher ist explizit
freizugeben (free()). Ein erstes Beispiel:
int *ip;
ip = (int *) malloc(n*sizeof(int));
ip zeigt hier auf n Speicherplätze vom Datentyp
int. Mit
free(ip);
kann der bereitgestellte Speicherplatz wieder freigegeben werden.
Im folgenden Beispielprogramm wird dynamisch Speicherplatz für Objekte
bereitgestellt, die einfachen Variablen der Datentypen int,
float und double entsprechen. Nach dem Ablegen von
eingelesenen Zahlenwerten auf diesen Objekten und dem anschließenden
Ausdrucken wird der zugeteilte Speicherplatz wieder freigegeben.
#include <stdio.h>
#include <stdlib.h>
main()
{
int *ip; float *fp; double *dp;
/* Speicher anfordern */
ip = (int *) malloc (sizeof(int));
fp = (float *) malloc (sizeof(float));
dp = (double *) malloc (sizeof(double));
printf("Drei Zahlen eingeben:\n");
scanf("%d %f %lf", ip, fp, dp);
printf("%d, %f, %f\n", *ip, *fp, *dp);
/* Speicher freigeben */
free(ip);
free(fp);
free(dp);
}
Das folgende Demonstrationsprogramm zur Verwendung der Bibliotheksfunktionen
zur dynamischen Speicherverwaltung liest eine beliebige Anzahl von
double-Werten vom Terminal und gibt sie dann wieder aus. Beachten Sie die
Verwendung von sizeof() und die Fehlerprüfung.
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int i, anz;
double *dfeld;
printf("\nAnzahl der double-Werte ? ");
scanf("%d", &anz);
if (anz <= 0)
{
printf("\nAnzahl muß >0 sein !\n");
exit(1);
}
if ((dfeld = malloc(anz*sizeof(double))) == NULL)
{
printf("\nSpeicherplatz reicht nicht aus !\n");
exit(1);
}
/* Einlesen der double-Werte */
for (i = 0; i < anz ; i++)
{
printf (" Doublewert Nr. %d bitte:",i);
scanf ("%f",dfeld+i);
}
printf ("\n");
/* Ausgabe der double-Werte */
for (i = 0; i < anz ; i++)
printf (" Doublewert Nr. %d = %f:\n",i, dfeld[i]);
free(dfeld);
}
Funktion zum Aneinanderhängen zweier Strings in einem neu
bereitzustellenden Speicherbereich. Im Gegensatz zur Bibliotheksfunktion
strcat ist hier sichergestellt, daß genügend
Speicherplatz bereitsteht.
#include <stdlib.h>
#include <string.h>
char *addstring(char *s1, char *s2)
{
char *as;
if ((as=malloc(strlen(s1)+strlen(s2)+1)) != NULL)
{
strcpy(as, s1);
strcat(as, s2);
}
return as;
}
Die folgende Funktion split teilt eine Zeichenkette an einer
vorzugebenden Stelle in zwei Teilstrings. im Beispiel wird auch der
Aufruf in main gezeigt.
#include <stdio.h>
#include <string.h>
char *strchr(char *string_ptr, char find)
/* Suche Positeon des Zeichens find im String, falls
gefunden, Zeiger auf die Positeon von find, falls
nicht return(NULL) */
{
while (*string_ptr != find)
{
if (*string_ptr == '\0')
return (NULL); /* nicht gefunden */
string_ptr++;
}
return(string_ptr);
}
char * split(char *line, char find)
{
char *ptr1; /* Pointer auf den ersten Teil */
char *ptr2; /* Pointer auf den zweiten Teil */
ptr1 = line; /* Zeilenanfang */
ptr2 = strchr(line,find); /* suche Zeichen */
if (ptr2 == NULL)
return(NULL);
ptr2++; /* Erstes Zeichen von Teil 2 */
return(ptr2);
}
int main(void)
{
char line[80]; /* Eingabezeile */
char *ptr; /* Pointer auf den zweiten Teil */
gets(line, sizeof(line), stdin);
ptr = split(line,',');
*(ptr-1) = '\0'; /* Komma auf Zeilenende setzen */
if (ptr == NULL)
{
fprintf(stderr, "FEHLER: Kein Komma in %s\n", line);
exit (8);
}
printf("Erster: %s Zweiter: %s\n", line, ptr);
return (0);
}
Unterprogramm zum Umwandeln deutscher Umlaute in ihre Ersatzdarstellung:
char* umlaut(char* eingabe)
{
int i = 0, j = 0;
char *tmp1 = NULL;
char *tmp2 = NULL;
/* Wir sparen uns das Ermitteln der Laenge des */
/* Eingabestrings und nehmen mal den worst case an */
tmp1= malloc(2*strlen(eingabe) + 1);
if(tmp1 == NULL) return(NULL);
/*Kopieren und Ersetzen */
while(eingabe[i] != '\0')
{
switch (eingabe[i])
{
case 'ä': tmp1[j++] = 'a'; tmp1[j++]='e'; break;
case 'ö': tmp1[j++] = 'o'; tmp1[j++]='e'; break;
case 'ü': tmp1[j++] = 'u'; tmp1[j++]='e'; break;
case 'Ä': tmp1[j++] = 'A'; tmp1[j++]='e'; break;
case 'Ö': tmp1[j++] = 'O'; tmp1[j++]='e'; break;
case 'Ü': tmp1[j++] = 'U'; tmp1[j++]='e'; break;
case 'ß': tmp1[j++] = 's'; tmp1[j++]='s'; break;
default: tmp1[j++] = eingabe[i];
}
i++;
}
tmp1[j] = '\0';
/* Nun den String genau passend zurueckgeben */
tmp2 = malloc(strlen(tmp1) + 1);
if(tmp2 == NULL) return(NULL);
strcpy(tmp2,tmp1);
free(tmp1);
return(tmp2);
}
Einlesen einer unbekannten Anzahl von Zeilen (max. Anzahl 100).
Nach Abschluss der Eingabe erfolgt Ausgabe der Zeilen in
umgekehrter Reihenfolge (letzte Zeile zuerst).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXANZ 100
#define MAXLEN 136
int ZeilenEin(char *ZeilFeld[], int Max)
{
char *Next, Zeile[MAXLEN];
int Laenge, ZeilZahl=0;
while (gets(Zeile) != NULL)
{
Laenge = strlen(Zeile);
if ((ZeilZahl < MAXANZ) && (Next=malloc(Laenge+1)) != NULL)
{
strcpy(Next, Zeile);
ZeilFeld[ZeilZahl++] = Next;
}
else
return ZeilZahl;
}
return ZeilZahl;
}
void ZeilenAus(char *ZeilFeld[], int Anz)
{
int i;
for (i = Anz; i > 0; )
{
printf("%s\n",ZeilFeld[--i]);
free(ZeilFeld[i]);
}
}
int main(void)
{
char *Zeilen[MAXANZ];
int Anz;
Anz=ZeilenEin(Zeilen,MAXANZ);
printf("\n\n%d Zeilen gelesen\n\n",Anz);
ZeilenAus(Zeilen,Anz);
return(0);
}
Das folgende Programmfragment zeigt, wie man Zeichenketten speichersparend
verwalten kann. Es wird zunächst nur ein Array von Zeigern definiert.
Beim Einlesen der Strings wird jeweils genügend Speicher allokiert und
die Zeichenkette an einem Array-Element "aufgehängt".
#include <stdio.h>
#include <string.h>
#define MAX 1000 /* Maximalzahl Feldelemente */
char *string_feld[MAX]; /* Feld mit String-Zeigern */
int count; /* Anzahl der eingeg. Strings */
int einlesen(char *feld[]); /* Eingabefunktion */
void ausgabe(int anzahl,char *feld[]); /* Ausgabefunktion */
int main(void)
{
count = einlesen(string_feld);
ausgabe(count,string_feld);
}
int einlesen(char *feld[])
/* Liest Strings auf die Variable feld ein, liefert Anzahl zurueck */
{
int anz; /* Zaehler fuer Strings */
char *ptr; /* Zeiger zum Pruefen auf EOF */
char puffer[2000]; /* Puffer fuer akt. String */
anz = 0;
do
{
ptr = gets(puffer); /* String einlesen */
if (ptr != NULL) /* EOF == (ptr == NULL) */
{ /* Platz reservieren */
feld[anz] = (char *) malloc(strlen(puffer) + 1);
/* einschl. '\0' am Ende */
strcpy(feld[anz],puffer); /* String in feld kopieren */
anz++; /* Zaehler erhoehen */
}
}
while (ptr != NULL); /* lesen bis EOF */
return(anz);
}
void ausgabe(int anzahl,char *feld[])
/* Ausgabe der Strings in feld, der Parameter gbt die Anzahl an */
{
int i;
puts("\n"); /* Leerzeile */
for (i=0; i<anzahl; i++)
puts(feld[i]); /* haengt \n automatisch an */
}
Im letzten Beispiel wird dynamisch Speicherplatz für Objekte
bereitgestellt, die den Datentyp double besitzen und die
Form von ein- und zweidimensionalen Feldern haben. Die Größe
der Felder, d. h. Zeilenzahl und Spaltenzahl der Matrix und
Anzahl der Komponenten des Arrays werden erst zur Laufzeit des
Programms eingelesen. Anschließend werden die eingelesenen
Zahlenwerte als Matrixelemente, bzw. Arraykomponenten abgelegt
und wieder ausgedruckt. Am Ende wird der bereitgestellte
Speicherplatz wieder freigegeben.
#include <stdio.h>
#include <stdlib.h>
main()
{
double **a, /* Eingabematrix */
*u; /* Eingabevektor */
int i,j,n,m;
printf("Zeilenzahl m und Spaltenzahl n eingeben: ");
scanf("%d %d", &m, &n);
/* Speicherplatz bereitstellen */
a = (double **) malloc (m*sizeof(double *));
for (i=0; i<m; i++)
a[i] = (double *) malloc (n*sizeof(double));
u = (double *) malloc (n*sizeof(double));
printf("%dx%d Matrix A eingeben\n", m, n);
for(i=0; i<m; i++)
for(j=0; j<n; j++)
scanf("%lf", &a[i][j]);
printf("%d Vektor u eingeben\n", n);
for(j=0; j<n; j++)
scanf("%lf", &u[j]);
printf("Eingabedaten: Matrix A\n");
for(i=0; i<m; i++)
{
for(j=0; j<n; j++)
printf("%f ", a[i][j]);
printf("\n");
}
printf("Eingabedaten: Vektor u\n");
for(j=0; j<n; j++)
printf("%f ", u[j]);
printf("\n");
/* Speicherplatz freigeben */
for (i=0; i<m; i++)
free (a[i]);
free (a);
free (u);
}
Hier zeigt a auf m Speicherplätze vom Datentyp
(double *). Jeder dieser Speicherplätze a[i]
ist selbst Zeiger auf n Speicherplätze vom Datentyp
double. Ebenso zeigt u auf n Speicherplätze
vom Datentyp double.
Zeiger und Strukturen
Im folgenden Beispiel wird eine Struktur für eine Adresse verwendet:
struct kunde
{
int kundennr;
char vorname[15];
char nachname[20];
char strasse [25];
char plz[5];
char ort[25];
};
Im folgenden Programmbeispiel geht es darum, eine beliebige Zahl von Adressen
einzulesen. Zur Speicherung wird kein Array verwendet, sondern ein
zusammenhängender Speicherbereich, der dynamisch allokiert wird und
über einen Pointer angesprochen werden kann. Dazu wird zuerst gefragt, wieviele
Adressen eingegeben werden sollen und dementsprechnde Speicherplatz mit
malloc() belegt.
Bei der Verwendung von Zeigern im Zusammenhang mit Strukturen wird häufig
folgender Ausdruck benötigt, um auf eine einzelne Komponente zuzugreifen:
(*zeiger).strukturkomponente
Dafür gibt es folgende abgekürzte Schreibweise:
zeiger -> strukturkomponente
Die Zeichen - und > bilden einen Pfeil und bringen so sehr anschaulich das
Zeigerkonzept zum Ausdruck.
Da wir nicht nur eine Strukturvariable haben, sondern (implizit) ein ganzes Feld,
benötigen wir Schleifen zum Einlesen und Ausdrucken. Der Schleifenindex i
beginnt bei 0 und wird so lange erhöht, bis die Anzahl der Kunden erreicht ist.
Dieser Index muß zum Wert des Zeigers addiert werden, so daß Ausdrücke
der Form
(zeiger + i) -> strukturkomponente
entstehen. Hat i beispielsweise den Wert 3, dann wird der Wert
3 * sizeof(struct kunde) zum Zeiger addiert. Der Zeiger zeigt dadurch auf das
drittnächste Element.
int main(void)
{
struct kunde
{
int kundennr;
char vorname[15];
char nachname[20];
char strasse [25];
char plz[5];
char ort[25];
};
char antwort,hilf[5];
int i, anzahl;
struct kunde *person;
printf("Wie viele Kunden wollen Sie eintragen? ");
scanf("%d",&anzahl);
gets(hilf); /* Liest RETURN-Zeichen, alternativ fflush(stdin) */
/* Platz fuer anzahl kunden reservieren */
person = (struct kunde *) malloc(anzahl * (sizeof(struct kunde)));
printf("Geben Sie die Kundendaten ein:\n");
for (i = 0; i < anzahl; i++)
{
printf("\nKundennummer: "); gets(hilf);
(person+i)->kundennr = atoi(hilf);
printf("\nVorname.....: "); gets((person+i)->vorname);
printf("\nNachname....: "); gets((person+i)->nachname);
printf("\nStraße: "); gets((person+i)->strasse);
printf("\nPostleitzahl: "); gets((person+i)->plz);
printf("\nWohnort.....: "); gets((person+i)->ort);
}
printf("\n\nSie haben folgende Daten eingegeben:\n");
for (i = O; i < anzahl; i++)
{
printf("\nKundennummer: %d\n",(person+i)->kundennr);
printf("%s %s\n",(person+i)->vorname,(person+i)->nachname);
printf("%s\n",(person+i)->strasse);
printf("%s %s\n",(person+i)->plz,(person+i)->ort);
}
return(0);
}
Beispiel: Anwendung von Bibliotheksfunktionen. In time.h sind Funktionen und
Strukturen zur Ermittlung von Datum und Uhrzeit enthalten. Für die Ausgabe
braucht man einn Zeiger tp, der auf eine in time.h
definierte Struktur tm zeigt.
#include <stdio.h>
#include <time.h>
time_t zeitpunkt; /* time_t definiert in time.h */
struct tm *tp; /* tm definiert in time.h */
int main(void)
{
time(&zeitpunkt); /* Uhrzeit und Datum im internen Format:
/* Sekunden seit 1.1.1970 GMT */
printf("%ld\n",zeitpunkt); /* soviele sind es */
tp=localtime(&zeitpunkt); /* umwandeln im etwas Lesbareres */
printf("Uhrzeit: %02d:%02d:%02d\n",
tp->tm_hour, tp->tm_min, tp->tm_sec);
printf("Tag: %02d\n",tp->tm_mday);
printf("Monat: %02d\n",tp->tm_mon+1);
printf("Jahr: %04d\n",tp->tm_year+1900);
return(0);
}
Gefahren bei der Rückgabe von Zeigern
Bei Zeigern, die als Rückgabewert einer Funktion dienen, muß
darauf geachtet werden, daß sie nicht alf lokale Objekte verweisen,
die nach dem Funktionsaufruf verschwunden sind.
Negativ-Beispiel 1: Die lokale Variable existiert nur während des
Funtionsaufrufs. Die Ausgabe ist unbestimmt, sie könnte zufällig
auch 1234 lauten.
int * murks(void)
{
int x = 1234;
return(&x);
}
int main(void)
{
int * ip;
ip = murks();
printf("%d\n",*ip);
}
Negativ-Beispiel 2: Die Funktion soll ein Duplikat des als Parameter
übergebenen Strings erzeugen und einen Zeiger darauf zurückgeben.
Der Speicherplatz für das lokale Feld neu wird jedoch beim
Verlassen der Funktion wieder freigegeben und die Kopie zerstört.
char * murks(char * txt)
{
char neu[100];
char *n;
n = neu;
while ((*n++ = *txt++) != '\0'); /* kopieren */
return(n);
}
int main(void)
{
char * strp;
strp = murks("Autsch!");
printf("%ds\n",strp);
}
Besser wäre es, dynamisch Speicher zu allocieren und den Pointer
zurückzugeben, dann entspricht die Funktion der Bibliotheksroutine
strdup:
char * kein_murks(char * txt)
{
char *n;
n = (char *) malloc(strlen(txt) + 1);
while ((*n++ = *txt++) != '\0'); /* kopieren */
return(n);
}
int main(void)
{
char * strp;
strp = kein_murks("So geht es!");
printf("%ds\n",strp);
}
|
|
|