procedure test(out foo);

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Mathias
Beiträge: 6899
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

procedure test(out foo);

Beitrag von Mathias »

Ich habe da gerade etwas kurrliges entdeckt.
Man beachte die Deklaration "out raus", Was man mit "const rein" machen kann ist mir bekannt. Aber was könnte man mit "out raus" machen ?
Den es geht nicht mal das direkte zuordnen wie in der ausgeklammerten Zeile.

Code: Alles auswählen

  procedure test(const rein; out raus);
  begin
    WriteLn(PtrUInt(rein));
//    raus := rein;
  end;

var
  i: integer = 1234;
begin
  test(i, i);
end.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Warf
Beiträge: 2118
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: procedure test(out foo);

Beitrag von Warf »

Also als erstes einmal darfst du nicht den selben Wert den du als const parameter übergibst als var oder out parameter übergeben.

Wenn du einen Wert an einen const parameter übergibst versprichst du das der Wert konstant bleibt während dem Funktionsaufruf. Wenn du einen Wert als var oder out übergibst versprichst du das er geändert werden bzw. Ersetzt werden kann. Aber der Wert kann nicht gleichzeitig konstant bleiben und verändert werden. Das führt dann zu solchen Sachen:

Code: Alles auswählen

    type
      TMyRec = record
        A: Integer;
      end;
     
    procedure Foo(const r: TMyRec; var A: Integer);
    begin
      A:=42;
      WriteLn(r.A);
    end;
     
    var
      r: TMyRec;
    begin
      r.A:=32;
      Foo(r, r.A);
    end.
Das WriteLn gibt auf 32 und 64 Bit Systemen was unterschiedliches aus, einmal 32 und einmal 42.

Wozu untypisiertes out gut ist, im Grunde das selbe wie untypisiertes var nur das es eben ein out parameter ist

Code: Alles auswählen

procedure Move2(const Src; out Dst; Size: SizeInt);
var i: SizeInt;
begin
  for i:=0 to Size-1 do
    PByte(@Dst)[i] := PByte(@Src)[i]
end;
Als etwas ineffiziente alternative zur System Move Funktion.

Grundsätzlich sind out parameter wie var Parameter mit 2 unterschieden, zum einen ist nicht garantiert das der vorige wert bestehen bleibt (also wenn du einen string als out parameter übergibst wird der vor dem Funktionsaufruf gecleared), zum anderen gilt das als setzen des Wertes und man erhält keine Warnung:

Code: Alles auswählen

var
  i,j,k: Integer;
begin
  i:=42;
  Move(i,j); // Warnung j nicht initialisiert weil var Parameter 
  Move2(i,k); // keine Warnung weil out parameter 

around5
Beiträge: 16
Registriert: Fr 16. Aug 2024, 08:14

Re: procedure test(out foo);

Beitrag von around5 »

1. Bei einem nicht typisierte Parameter handelt es sich um einen sogenannten "formal type".
2. Diesen kann man vorerst mal nicht direkt verwenden, sondern nur den address-operator verwenden.
3. Um den Parameter-Wert zu bekommen bzw. zu setzen muss man erst einen TypeCast durchführen.

Also folgendes funktioniert (Sinnhaftigkeit mal beiseite gestellt):

Code: Alles auswählen

procedure test(const rein; out raus);
begin
  WriteLn(PtrUInt(rein));
  Integer(raus) := Integer(rein);
end;
4. Verwendet man bei rein und raus zwei verschiedene Variablen, dann funktioniert auch die Wert-Zuweisung (siehe Warf). Nach Ausführung des Codes hat die Variable j ebenso den Wert 1234.

Code: Alles auswählen

var
  i: Integer = 1234;
  j: Integer = 0;
begin
  test(i, j);
end.
5. Und ob man out-Parameter verwendet hängt vom Anwendungsfall ab. Ich persönlich verwende out-Parameter nicht (gerne). Beispielsweise bei Funktionen, die über das Funktionsergebnis einen Fehlercode zurückliefern, und das Ergebnis über den out-Parameter (oder auch umgekehrt).

Mathias
Beiträge: 6899
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: procedure test(out foo);

Beitrag von Mathias »

3. Um den Parameter-Wert zu bekommen bzw. zu setzen muss man erst einen TypeCast durchführen.

Also folgendes funktioniert (Sinnhaftigkeit mal beiseite gestellt):
Jetzt ist mit der Sinn klar, ich vergesse immer wieder, das ein Typencast auch auf der linken Seite möglich ist.
Für C-Anbindungen ist dies eine recht praktische Sache.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
theo
Beiträge: 10856
Registriert: Mo 11. Sep 2006, 19:01

Re: procedure test(out foo);

Beitrag von theo »

around5 hat geschrieben: Di 19. Nov 2024, 20:17 5. Und ob man out-Parameter verwendet hängt vom Anwendungsfall ab. Ich persönlich verwende out-Parameter nicht (gerne).
Es kann schon nützlich sein. Z.B. macht es einen beim Verwenden einer ansonsten unbekannten Funktion direkt klar, dass man dort keinen Wert mitgeben muss bzw. die übergebene Variable nicht initialisieren muss (Und der Compiler weiss das dann auch).
Auch erspart es einem gewisse "Hints" (Hint: Variable "C" does not seem to be initialized).

https://www.freepascal.org/docs-html/ref/refsu66.html

around5
Beiträge: 16
Registriert: Fr 16. Aug 2024, 08:14

Re: procedure test(out foo);

Beitrag von around5 »

Das mit out alleine war zu kurz (nur auf die konkrete Frage bezogen) gegriffen. Bin überhaupt kein Fan von var/out, weil es ua. zu "versteckten" Fehlern führen kann. Aber es gibt natürlich auch die Fälle, wo es anders nicht geht.

Mathias
Beiträge: 6899
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: procedure test(out foo);

Beitrag von Mathias »

Bin überhaupt kein Fan von var/out, weil es ua. zu "versteckten" Fehlern führen kann.
Meinst du jetzt generell, oder nur bei der Variante in meinem ersten Post ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

around5
Beiträge: 16
Registriert: Fr 16. Aug 2024, 08:14

Re: procedure test(out foo);

Beitrag von around5 »

Generell vermeide ich var/out. Bei der Übergabe von Klassen-Instanzen ist das natürlich aufgrund von "prinzipiellem byReference" fast eh egal, aber veränderbare Parameter sind ein No-Go. Wie gesagt, es gibt Fälle/Situationen, da geht es nicht anders.

Mathias
Beiträge: 6899
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: procedure test(out foo);

Beitrag von Mathias »

Generell vermeide ich var/out.
Wie machst du das den, kopierst du jedesmal alles in den Stack ?
Und wie machst du es, wen in der procedure an der Variable was geändert wird ?

Machst du das wie die C-Freunde ?

Code: Alles auswählen

  procedure Test(i: PInteger);
  begin
    i^ := 100;
  end;

var
  wert: integer = 0;
begin
  test(@wert);
  WriteLn(wert);
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

around5
Beiträge: 16
Registriert: Fr 16. Aug 2024, 08:14

Re: procedure test(out foo);

Beitrag von around5 »

Weil ich das so nicht programmieren wollte.

Ich kenne die Verwendung von var/out speziell aus jener Zeit, wo es wirklich um jeden CPU-Takt und um die maximale Stackgröße gegangen ist, und um auch das letzte bisschen an Performance bei einem Algorithmus herauszuholen. Heute, nach vielen Jahren (tlw. qualvoller) Erfahrung, und auch unterstützt durch den technischen Fortschritt, geht mir Klarheit vor Performance. Das liegt aber auch an der Kategorie der Programme, die ich schreibe. Da ist eher der User der bestimmende Faktor.

Warf
Beiträge: 2118
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: procedure test(out foo);

Beitrag von Warf »

Mittlerweile mit generics kann man natürlich auch viel einfachere Sachen machen:

Code: Alles auswählen

// alt:
function TryDoSomething(out res: Integer): Boolean;
...
if not TryDoSomething(x) then
  WriteLn('Error')
else
  WriteLn(x);
// Bzw
if TryDoSomething(x) then
  WriteLn(x);
// Neu mir unit nullable
function TryDoSomething: specialize TNullable<Integer>;
...
opt := TryDoSomething;
if not opt then
  WriteLn('Error')
else
  WriteLn(opt.Value);
//Bzw (in trunk)
if TryDoSomething.unpack(x) then
  WriteLn(x);
Genauso wenn man mehrere Ausgaben will gibt's mittlerweile die tuples unit (in trunk) statt das man Output Argumente übergeben muss.

Das gesagt, zu den formalen Parametern zurück, die Idee hinter ihnen ist halt pointer zu verstecken, bzw. Zu limitieren. Die Idee ist in Theorie gut, in der Praxis führt diese Umsetzung aber einfach zu Verwirrung, weil viele Leute annehmen das dabei der Typ inferriert wird (also wie bei generics mit implicit specialization), was auch Sinn machen würde denn sehr viele Sprachen erlauben sowas schon seit Jahrzehnten (die erste müsste ML in den 80ern gewesen sein), aber genau das wird eben nicht gemacht.

Mathias
Beiträge: 6899
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: procedure test(out foo);

Beitrag von Mathias »

around5 hat geschrieben: Mi 20. Nov 2024, 13:13 Weil ich das so nicht programmieren wollte.

Ich kenne die Verwendung von var/out speziell aus jener Zeit, wo es wirklich um jeden CPU-Takt und um die maximale Stackgröße gegangen ist, und um auch das letzte bisschen an Performance bei einem Algorithmus herauszuholen. Heute, nach vielen Jahren (tlw. qualvoller) Erfahrung, und auch unterstützt durch den technischen Fortschritt, geht mir Klarheit vor Performance. Das liegt aber auch an der Kategorie der Programme, die ich schreibe. Da ist eher der User der bestimmende Faktor.
Da bin ich anderer Meinung, man kann auch heute noch Optimierern. Bei rechenintensiven Anwendungen merkt man dies dann schon. Dies spart dann auch Strom.
Damit sind Python Programmierer ökologisch sehr schlecht.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Warf
Beiträge: 2118
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: procedure test(out foo);

Beitrag von Warf »

Mathias hat geschrieben: Mi 20. Nov 2024, 20:01 Da bin ich anderer Meinung, man kann auch heute noch Optimierern. Bei rechenintensiven Anwendungen merkt man dies dann schon. Dies spart dann auch Strom.
Damit sind Python Programmierer ökologisch sehr schlecht.
Alles ne Frage der Relation. Die meisten Programme machen tatsächlich nix als warten. Warten auf Nutzerinputs, auf das Dateisystem, auf das Netzwerk, etc. Ich hab grade mal htop aufgemacht und meine Durchschnittliche Prozessorlast ist <1% während ich mehrere GUI Programme offen habe und ein ps -ax zeigt das ich über 500 prozesse laufen hab. Der Grund ist ganz einfach weil die alle nix machen bis sie irgendeinen input bekommen.

Das was bei meinem Rechner aktuell den meisten Strom frist ist vermutlich der 4k Monitor, nicht die python scripts, nicht der Linux Kernel der in C geschrieben ist, nicht die Signal Desktop App die in Javascript geschrieben ist. Die ganzen Sachen machen nix im vergleich zu dem Monitor

Warf
Beiträge: 2118
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: procedure test(out foo);

Beitrag von Warf »

Mathias hat geschrieben: Mi 20. Nov 2024, 13:02
Generell vermeide ich var/out.
Wie machst du das den, kopierst du jedesmal alles in den Stack ?
Und wie machst du es, wen in der procedure an der Variable was geändert wird ?

Machst du das wie die C-Freunde ?

Code: Alles auswählen

  procedure Test(i: PInteger);
  begin
    i^ := 100;
  end;

var
  wert: integer = 0;
begin
  test(@wert);
  WriteLn(wert);
Warum nicht:

Code: Alles auswählen

function Test: Integer;
begin
  Result := 100;
end;

var
  wert: Integer;
begin
  wert := Test;
  WriteLn(Wert);
end.
Zum Thema versteckte Fehler, das Problem mit var/out ist das es auf der Callsite versteckt ist.

Nimm die folgende Zeile:

Code: Alles auswählen

Test(x);
Kann Test den Wert von X ändern oder nicht? Das kannst du nur von dieser Zeile nicht wissen, du musst in die Funktionsdefinition schauen. Das versteckt information und macht es damit schwerer code zu lesen und zu verstehen. Theoretisch wenn du einen block code liest müsstest du auf jede einzelne Funktion Strg+Click machen um rauszufinden ob der Parameter var/out oder nicht ist. Wenn du denkst das eine funktion einen Wert nicht ändern kann, das aber dann doch tut, dann kann das sehr schnell zu fehlern führen.
Daher würde ich sogar sagen das der Pointer weg mit "@" sogar effektiv der bessere ist, denn nach dem motto: Besondere Semantik braucht besondere Syntax, wenn du von einem wert den Pointer nimmst weist du schon direkt beim lesen "Hey hier passiert grade was spezielles"

Mathias
Beiträge: 6899
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: procedure test(out foo);

Beitrag von Mathias »

Daher würde ich sogar sagen das der Pointer weg mit "@" sogar effektiv der bessere ist, denn nach dem motto: Besondere Semantik braucht besondere Syntax, wenn du von einem wert den Pointer nimmst weist du schon direkt beim lesen "Hey hier passiert grade was spezielles"
Dies hat noch ein Vorteil, man kann auch nil übergeben.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten