Unterprogramme, Pointer und die Übergabe von Arrays

Unterprogramme

Wie schon im Abschnitt über Funktionen erwähnt, versteht man unter einem Unterprogramm im eigentlichen Sinn eine Prozedur, welche die Werte der an sie übergebenen Variablen verändern darf. Im Gegensatz zu Funktionen, die nur einen einzigen Wert (eben den "Funktionswert") an das rufende Programm zurückliefern können (F kennt allerdings auch Array-wertige Funktionen), wird man also ein Unterprogramm dann verwenden, wenn das Ergebnis einer Berechnung aus mehreren Werten besteht. Es muß in der Parameterliste des Unterprogramms nur eine entsprechende Anzahl von Variablen zur Aufnahme dieser Werte vorgesehen werden. Zusätzlich kann ein Unterprogramm auch noch die Werte globaler Variablen verändern.
Während C nicht zwischen Funktionen und Unterprogrammen unterscheidet (alle Prozeduren sind Funktionen), heißt in F ein Unterprogramm Subroutine und hat die folgende Gestalt
subroutine subroutine_name ( argument_list )
     type,intent(in)::arg_sublist_1
     ...
     type,intent(out)::arg_sublist_2
     ...
     type,intent(inout)::arg_sublist_3
     ...
     local declarations
     ...
     executable statements
     ...
     return
end subroutine subroutine_name
Das Attribut intent bei der Deklaration der Argumente des Unterprogramms kennzeichnet, welche Parameter von der Prozedur nur gelesen (in), nur geschrieben (out) oder sowohl gelesen als auch geschrieben (inout) werden dürfen. Falls die Parameterliste argument_list leer ist, kann mit dem rufenden Programm nur über globale Variablen kommuniziert werden. Für diese muß aber im Unterprogramm kein intent definiert werden.
Unterprogramme müssen, ebenso wie Funktionen, in F in einen module-Block eingeschlossen werden, und der Name des Unterprogramms muß global sichtbar gemacht werden:
module module_name
     public::subroutine_name
     ...
contains
     subroutine subroutine_name (...)
          ...
     end subroutine subroutine_name
     ...
end module module_name
Der Aufruf eines Unterprogramms erfolgt mit dem Befehl call
call subroutine_name ( argument_list )
Da in F Parameter per Adresse (call by name) übergeben werden, genügt es, in der Argumentliste einfach die Namen der zu übergebenden Variablen anzuführen. Das Unterprogramm greift direkt auf die Speicherplätze dieser Variablen zu, und alle an ihnen vorgenommenen Veränderungen sind im rufenden Programm unmittelbar sichtbar.

Pointer

In C sind alle Prozeduren Funktionen. Also muß man, um den Mechanismus von Unterprogrammen zu implementieren, Funktionen schreiben, die den Wert eines oder mehrerer Argumente verändern können. Da in C aber Argumente per Wert (call by value) übergeben werden, kann dies nur dadurch geschehen, daß man für jene Variablen, die vom Unterprogramm verändert werden sollen, nicht deren Wert, sondern ihre Adresse im Speicher übergibt. Dann kann, wie im Fall von F, das Unterprogramm direkt mit diesen Speicherinhalten rechnen, und alle Änderungen wirken sich automatisch im rufenden Programm aus.
(Da solche Unterprogramme keinen Funktionswert im eigentlichen Sinn liefern, kann man ihnen den Typ void geben. Häufig definiert man sie auch als Typ int und gibt als "Funktionswert" einen ganzzahligen Code zurück, der anzeigt, ob im Unterprogramm Fehler aufgetreten sind oder nicht.)
Die Adresse einer Variablen im Speicher liefert der Adreßoperator "&", der dem Namen der Variablen vorangestellt wird
&x = Adresse der Variablen x
Diese Konstruktion wird z.B. beim Aufruf der scanf-"Funktion" verwendet, wenn von der Tastatur die Werte von x und y eingelesen werden sollen
scanf("%f,%f",&x,&y);
Hier werden also die Adressen von x und y an das Unterprogramm übergeben, das damit unmittelbar auf die dort liegenden Speicherinhalte Zugriff hat und von sich aus mit den eingelesenen Werten überschreiben kann.
Um im Unterprogramm mit diesen Adressen arbeiten zu können, braucht man Variablen zur Speicherung von Adressen. Solche Variablen nennt man Pointer, weil sie auf den Speicherplatz bzw. den dort gespeicherten Wert "zeigen". (Pointer gibt es sowohl in F als auch in C, doch braucht man in F Pointer eher selten, und auch in C führt die sparsame Verwendung von Pointern zu lesbarerem und sichererem Code.)
Obwohl Speicheradressen natürlich ganzzahlig sind, haben Pointer in C einen eigenen Typ, der sich nach dem Typ des Wertes richtet, auf den der Pointer zeigt, d.h. es gibt "Pointer auf int", "Pointer auf float" usw. Pointer müssen deklariert werden wie andere Variablen auch
type *pointer_1,*pointer_2,...;
Dabei ist pointer_1, pointer_2, ... der Name des Pointers und type der Typ des Wertes, auf den der Pointer zeigt.
Den Wert an der Speicherstelle, auf die ein Pointer zeigt, erhält man mit dem Indirektionsoperator "*", der dem Namen des Pointers vorangestellt wird
*p = an der Adresse p gespeicherter/zu speichernder Wert
Mit den so angesprochenen Speicherinhalten kann man rechnen wie mit gewöhnlichen Variablen, d.h. sie können auf beiden Seiten von Zuweisungen vorkommen, z.B.
float *xp,*yp,*zp;
float u,v,w;
...
xp=&u;
yp=&v;
zp=&w;
...
u=*yp+w;
*zp=*xp+v;
...
Um also in C ein Unterprogramm zu schreiben, trennt man die Argumente in solche, die von der Prozedur nur gelesen [entsprechend intent(in) von F], und solche, die auch gesetzt werden [entsprechend intent(out) oder intent(inout)], und übergibt die ersteren per Wert, die letzteren aber per Adresse (Pointer), z.B.
void sub(float rr1,float rr2,...,float *ww1,float *ww2,...) {
     ...
     *ww1=...;
     *ww2=...;
     ...
}
...
int main(...) {
     ...
     float r1,r2,...,w1,w2,...;
     ...
     sub(r1,r2,...,&w1,&w2,...);
     ...
}

Übergabe von (mehrdimensionalen) Arrays in F

Assumed-Shape Arrays  
Das allgemeine Problem bei der Übergabe von mehrdimensionalen Arrays an Unterprogramme besteht darin, daß alle (also auch mehrdimensionale) Arrays linear auf den Speicher abgebildet werden. So werden in F etwa die Elemente einer Matrix a(m,n) spaltenweise fortlaufend gespeichert
a(1,1), ..., a(m,1), a(1,2), ..., a(m,2), ...,a(1,n), ..., a(m,n)
d.h. das Element a(i,j) steht i+(j-1)*m-1 Positionen hinter der Startadresse (und wird auch mittels "Startadresse plus Offset" angesprochen). Um eine an ein Unterprogramm übergebene Matrix korrekt adressieren zu können, muß das Unterprogramm also neben der Startadresse von a auch noch die Spaltenlänge m kennen.
Die Übergabe von Arrays an Unterprogramme ist in F aber dennoch sehr einfach: Es muß nur die Startadresse (d.h. der Name) des Arrays explizit als Parameter übergeben werden, die Informationen über die Dimensionen des Arrays werden dann automatisch übertragen (aber nicht die Startwerte der Indizes, wenn diese nicht bei 1 beginnen). In den Deklarationen der Unterprogramm-Parameter kann man daher die Dimensionen solcher Arrays nach dem Muster
dimension(:,:)
unbestimmt lassen. Diesen Mechanismus nennt man Assumed-Shape Arrays.
Die aktuelle Anzahl der Elemente in der Dimension dimension eines Arrays array kann man im Unterprogramm mit der size-Funktion
size(array,dimension )
abfragen.
Das folgende Beispiel zeigt die Übergabe einer Matrix und eines Vektors, dessen Startindex nicht 1, sondern imin ist
subroutine sub1(a,v,imin)
     real,dimension(:,:),intent(in)::a
     real,dimension(imin:),intent(inout)::v
     integer::m,n
     ...
     m=size(a,1)
     n=size(a,2)
     ...
end subroutine sub1
Automatic Arrays  
Häufig braucht man in Unterprogrammen temporäre Arrays, deren Größe von Aufruf zu Aufruf variieren kann. Solche Objekte muß man in F nicht explizit dynamisch erzeugen, sondern es genügt, sie als Automatic Arrays zu deklarieren. Das sind lokale Arrays, deren Dimensionen von den Parametern des Unterprogramms abhängig gemacht werden können.
Im folgenden Beispiel sind b und c Automatic Arrays
subroutine sub2(a,m,...)
     integer,intent(in)::m
     real,dimension(:,:),intent(inout)::a
     real,dimension(2*m)::b
     real,dimension(size(a,1))::c
     ...
end subroutine sub2

Übergabe von (mehrdimensionalen) Arrays in C

Bei der Verwendung von Arrays in C treten zwei Schwierigkeiten auf:
Es empfiehlt sich daher, in C alle Arrays dynamisch zu erzeugen und die syntaktische Verwandtschaft von Arrays und Pointern auszunützen.
Diese besteht einerseits darin, daß man, wenn a ein Pointer ist, den Wert, der i Speicherstellen nach dem Wert steht, auf den der Pointer zeigt, sowohl in der Form *(a+i) als auch in der Form a[i] ansprechen kann. (Da aus dem Typ des Pointers hervorgeht, wieviel Platz zur Speicherung des Wertes notwendig ist, muß man den Pointer jeweils nur um 1 erhöhen, um auf den nächsten Wert im Speicher zu zeigen, unabhängig von der tatsächlichen Schrittweite in Bytes.) Andererseits ist diese Nomenklatur rekursiv, d.h. wenn a[i] kein Wert, sondern wiederum ein Pointer ist, dann kann man den Wert j Speicherstellen nach dem Wert, auf den der Pointer bei a[i] zeigt, mit a[i][j] ansprechen, usw.
Um z.B. eine m*n Matrix a vom Typ float zu erzeugen, kann man folgendermaßen vorgehen: Es wird ein Pointer auf einen Vektor von Pointern vom Typ float definiert
float **a;
Der Speicherplatz für den Vektor von Pointern {a[0], a[1], ..., a[m-1]} muß mit malloc (oder calloc) angefordert werden
a=(float**)malloc(m*sizeof(float*));
(m ist die Anzahl der Zeilen der Matrix). Nun wird für jedes Element a[i] des Vektors von Pointern noch der Speicherplatz für die Zeile der Matrix reserviert, auf deren Beginn a[i] zeigen soll
for(i=0;i<=m-1;i=i+1) {
     a[i]=(float*)malloc(n*sizeof(float));
}
(n ist die Anzahl der Spalten der Matrix). Die "Matrixelemente" können dann in der üblichen Form a[i][j] angesprochen werden.
Das folgende Beispiel zeigt, wie man mit Hilfe dieses Mechanismus relativ einfach eine m*n Matrix a erzeugen und an ein Unterprogramm übergeben kann. Dabei müssen zwar die Dimensionen der Matrix explizit mitübergeben werden, damit man im Unterprogramm weiß, in welchen Bereichen sich (z.B. in Schleifen) die Indizes i und j bewegen dürfen; zur Berechnung der Adresse eines Elements a[i][j] braucht der Compiler aber nicht die Spaltenanzahl n, da die Adressen aller Zeilenanfänge in dem Pointer-Vektor a[i] stehen, auf den das Unterprogramm direkt zugreifen kann.
void sub(float **a,int m,int n,...) {
     int i,j,m,n;
     ...
     a[i][j]=...;
     ...
}
...
int main(...) {
     int i,m,n;
     float **a;
     ...
     a=(float**)malloc(m*sizeof(float*));
     for(i=0;i<=m-1;i=i+1) {
          a[i]=(float*)malloc(n*sizeof(float));
     }
     ...
     sub(a,m,n,...);
     ...
}



File translated from TEX by TTH, version 3.59.
On 25 May 2005, 13:46.