7. Programmeinheiten
 
zurück
7.5 Parameterübergabe


Die Parameterübergabe findet zur Laufzeit statt, wobei der vorhandene Wert des Aktualparameters an den Formalparameter übergeben wird. Die Übergabe erfolgt entweder durch die Mechanismen Wertaufruf (call-by-value) oder Referenzaufruf (call-by-reference).

Beim Mechanismus Wertaufruf wird eine Kopie des Aktualparameters angelegt. Diese Kopie wird als Formalparameter im Rumpf der Programmeinheit verwendet. Wenn auf dem Formalparameter operiert wird (dies kann nur bei "out" oder "in out"-Parameter der Fall sein), wird das Original, also der Aktualparameter, nicht verändert. Erst wenn die Abarbeitung des Rumpfes beendet wird, wird die eventuell veränderte Kopie auf den Aktualparameter zurückübertragen (kopiert).

Beim Mechanismus Referenzaufruf wird dem Formalparameter die Adresse des Aktualparameters übergeben.

Wenn auf dem Formalparameter operiert wird, dann wird bereits während der Abarbeitung des aufgerufenen Rumpfes das Original, also der Aktualparameter, verändert. Bei Beendigung des Rumpfes entfällt eine Zurückübertragung.


Für verschiedene Parameter ist der Übergabemechanismus vorgeschrieben. Es werden drei Kategorien von Parametern unterschieden:

Der Kategorie von Parametern, die einen Werteaufruf erforderlich machen, gehören Parameter an, deren Typ entweder ein elementarer Typ oder ein privater Typ ist, dessen vollständiger Typ der Kategorie Wertaufruf angehört. Elementare Typen sind skalare Typen und Zugriffstypen. Parameter dieser Kategorie werden beim Aufruf in den entsprechenden Formalparameter kopiert (dies gilt ab Ada 95 auch für "out"-Parameter). Am Schluß der Ausführung des Rumpfes werden die neu berechneten Werte der Formalparameter in die Aktualparameter kopiert, sofern der Formalparameter nicht vom Modus "in" ist.

Wenn Zugriffstypen als "in"-Parameter verwendet werden, ist folgendes zu bedenken:
Der Formalparameter selbst, der als Adresse auf ein Objekt zeigt, kann nur gelesen werden. Somit kann die Adresse nicht verändert werden. Das Objekt jedoch kann über die ".all"-Schreibweise gelesen und auch verändert werden.

Für Parameter, deren Typen zusammengesetzte Typen sind, ist der Mechanismus Referenzaufruf erforderlich, wenn einer der folgenden Typen vorliegt:
  • erweiterbarer Typ (tagged type)
  • Prozeß (task) oder geschützter Typ (protected type)
  • ein nicht privater limitierter Typ (nonprivate type)
  • ein zusammengesetzter Typ (composite type) mit einer Unterkomponente, die der Kategorie Referenzaufruf angehört,
  • ein privater Typ, dessen vollständiger Typ ein Typ der Kategorie Referenzaufruf ist.
Für alle Parameter, die nicht zu einer der beiden Kategorien Wertaufruf oder Referenzaufruf gehören, ist die Parameterübergabe nicht auf einen der beiden Mechanismen festgelegt.

Zu den nicht spezifizierten Parametertypen gehören Reihungen, u.a. Zeichenketten und Verbunde.

Übersetzer-Herstellern ist es freigestellt, für diese nicht spezifizierten Parameter einen Mechanismus zu wählen.

Ein guter Ada-Übersetzer verwendet diejenige Art der Übergabe, die für den betreffenden Parameter effizient ist.

Es gibt einige seltene Fälle, in denen die Kategorie Referenzaufruf zu Problemen führen kann. Es handelt sich hierbei um Programmierfehler, die nicht offensichtlich sind, da mit zwei verschiedenen Formalparametern auf demselben Objekt operiert wird.

Für das folgende Beispiel sei angenommen, daß Parameter des Typs

type matrix is array (index range <>, index range <>) of float;

von einem Ada-Übersetzer der Kategorie Referenzaufruf zugeordnet werden. Das sogenannte "Aliasing" wird meist an einer Matrixmultiplikation veranschaulicht. Diese wird zunächst durch den Operator "*" realisiert:

function "*" (a,b : in matrix) return Matrix is
   c: Matrix(a'range(1), b'range(2)) := (others=>(others=>0.0));
begin
   if a'length(2) /= b'length(1) then
      raise matrix_dimension_error;
   end if;
   for i in c'range(1) loop
      for j in c'range(2) loop
         for k in a'range(2) loop         -- or b'range(1)
            c(i,j) := c(i,j) + a(i,k) * b(k,j);
         end loop;
      end loop;
   end loop;
   return c;
exception
   when constraint_error =>
      raise matrix_dimension_error;
end "*";

Anmerkung:

Der Algorithmus fängt nur den Fall inkompatibler Matrixdimensionen ab (Spaltenzahl von a ist ungleich Zeilenzahl von b). Es wird davon ausgegangen, daß beide Matrizen mit dem Element (1,1) beginnen.

Die Benutzung in folgender Weise ist unproblematisch:

   x : matrix(1 .. n, 1 .. m) := ...;
   y : matrix(1 .. m, 1 .. k) := ...;
   z : matrix(1 .. n, 1 .. k);
begin
   z := x * y;

Eine Benutzung in folgender Form ist auch unproblematisch, da der Übersetzer Speicher für die lokale Matrix "c" in "*" anlegt und diese am Ende von "*" auf "x" kopiert:

   x : matrix(1 .. n, 1 .. m) := ...;
   y : matrix(1 .. m, 1 .. m) := ...;
begin
   x := x * y;

Problematisch ist aber die folgende nahezu äquivalente Prozedur:

procedure multipliziere(a,b : in matrix; c : out matrix) is
begin
   -- ...
   c(i,j) :=
   -- ...
end multipliziere;

Eine Benutzung der folgenden Form ist unproblematisch:

   x : matrix(1 .. n, 1 .. m) := ...;
   y : matrix(1 .. m, 1 .. k) := ...;
   z : matrix(1 .. n, 1 .. k);
begin
   multipliziere(a => x, b => y, c => z);

Zu wahrscheinlich ungewollten Ergebnissen führt aber der folgende Einsatz der Prozedur:

   x : matrix(1 .. n, 1 .. m) := ...;
   y : matrix(1 .. m, 1 .. m) := ...;
begin
   multipliziere(a => x, b => y, c => x);

Hier werden folgende Skalarprodukte zur Berechnung der ersten Zeile von x ausgewertet:

x(1,1) := x(1,1) * y(1,1) + ... + x(1,m) * y(m,1);
x(1,2) := x(1,1) * y(1,2) + ... + x(1,m) * y(m,2);
...
x(1,m) := x(1,1) * y(m,1) + ... + x(1,m) * y(m,m);


Bei der Berechnung von x(1,2) wird also ein bereits überschriebener Wert von x(1,1) benutzt, bei der Berechnung von x(1,m) sind schon alle Werte von x(1,1) bis x(1,m-1) verändert worden. In der ersten Zeile der Ergebnismatrix stimmt noch das erste Element. In den weiteren Zeilen 2 bis n sind alle Werte falsch. Abhilfe schafft hier nur die zum Operator "*" vollkommen äquivalente Prozedur

procedure multipliziere(a,b : in matrix; c : out matrix) is
   d: matrix(a'range(1), b'range(2)) := (others=>(others=>0.0));
begin
   -- ...
   d(i,j) :=
   -- ...
   c := d;
end multipliziere;

Hier wird die lokale Matrix "d" am Ende der Ausführung auf den "out"-Parameter "c" kopiert. Zu beachten ist auch, daß Algorithmen wie die Matrizenaddition (oder Subtraktion) nicht zu solchen Problemen führen, da sie nur zwei geschachtelte Schleifen benötigen und damit auf jedes Element der drei beteiligten Matrizen nur einmal nach der Art:

c(i,j) := a(i,j) + b(i,j);

zugreifen.


 
zurück
 Index   Ada Tour - Dokumentation  
© 2003 Förderverein Ada Deutschland e.V.