Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu haben? (https://www.delphipraxis.net/175233-warum-macht-es-nichts-bei-rueckgabe-eines-interfaces-nichts-initialisiert-zu-haben.html)

Der schöne Günther 7. Jun 2013 17:27

Delphi-Version: XE2

Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu haben?
 
Ich brauchte eine Weile um zu merken, dass der Delphi-Compiler mich nicht warnt, wenn ich in einer Funktion den Rückgabewert nicht definiere und dieser ein Interface ist.

Warum ist das so?


Delphi-Quellcode:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

type

   IInterface = interface ['{3E4EDB7F-A266-48DE-8415-F73406A38C7C}']
   end;

   TForm1 = class(TForm)
      function testFunc(): IInterface;
      function testFunc2(): TForm1;
   procedure FormCreate(Sender: TObject);
   end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
   if testFunc() = nil then ShowMessage('nil');
   if testFunc2() = nil then ShowMessage('nil');
end;

function TForm1.testFunc: IInterface;
begin
   //
end;

function TForm1.testFunc2: TForm1;
begin
   //
end;

end.
Der Compiler warnt nur
Code:
[DCC Warnung] Unit1.pas(41): W1035 Rückgabewert der Funktion 'TForm1.testFunc2' könnte undefiniert sein
Bein Ausführen bekomme ich sowohl
Code:
'nil interface'
als auch
Code:
'nil class'
. Vor allem dass testFunc2() nil zurückgibt wundert mich. Ich hätte gedacht, es ist in der Funktion ein normaler Zeiger auf dem Stack der erst einmal nicht initialisiert wurde und dort erst einmal irgendetwas steht?

Uwe Raabe 7. Jun 2013 17:53

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
1) Ein Interface wird implizit immer mit nil initialisiert.
2) Bei der Rückgabe einer Klasseninstanz kommt wirklich irgendetwas zufälliges raus - in diesem Fall war es offenbar nil.

Der schöne Günther 30. Jan 2014 14:33

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
Ich habe gerade erneut mindestens eine Halbe Stunde damit verbraten, mich hiervon wieder durcheinander bringen zu lassen.

Gibt es irgendeine Möglichkeit, dass der Compiler mich warnt wenn ich folgendes tue:
Delphi-Quellcode:
function someFunc(): IInterface;
begin
   // Nichts
end;
Ich komme partout damit nicht zurecht und das passiert mir alle paar Monate immer wieder. :wall:

himitsu 30. Jan 2014 14:43

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1245990)
Gibt es irgendeine Möglichkeit, dass der Compiler mich warnt wenn ich folgendes tue:

Nein, gibt es nicht.

Interface-Variablen sind ja, wie schon genannt, immer initialisiert,
also gibt es da nichts zu meckern, weil es das ja schon ist.

Auch wenn das mit dem Result leider ein Sonderfall ist, denn was wird nicht in der Funktion initialisiert, sondern beim Aufrufer. (das Result wird hier, bei diesen Typen, in einen VAR-Parameter umgewandelt)
> dyn. Array, String, Interface und Variant
Und es macht Spaß, wenn die Funktion in einer Schleife aufgerufen wird.

Delphi-Quellcode:
function Mist: string;
begin
  Result := Result + 'a';
end;

for i := 1 to 5 do
  S := Mist; // oder S := S + Mist;
ShowMessage(S);

Stevie 30. Jan 2014 15:14

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
Eine Warnung in solch einem Fall wäre mehr wert als so manche Warnings, die der Compiler ausspuckt :(
Leider aufgrund der schon erwähnten Architektur nicht möglich ohne bei potenziell korrektem Code Warnings zu produzieren.

Denn das hier kann je nach Aufruf funktionieren (was nicht heißt, dass das guter Code ist):
Delphi-Quellcode:
function Foo: IFoo;
begin
  Result.Bar;
end;
:shock:

himitsu 30. Jan 2014 15:38

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
Eigentlich müsste die Prüffunktion dafür, beim Result, einfach nur zu Funktionsbeginn "vergessen" daß die Variable schon initisialisert ist.
Also einfach den VAR-Parameter wie einen OUT-Parameter behandeln, wenn es mal ein Result war.

Aber ob man es nach 15 Jahren schafft das jetzt noch zu reparieren?

jbg 30. Jan 2014 20:03

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
Zitat:

Zitat von himitsu (Beitrag 1246004)
Eigentlich müsste die Prüffunktion dafür, beim Result, einfach nur zu Funktionsbeginn "vergessen" daß die Variable schon initisialisert ist.

Wenn ich mich recht entsinne, dann hat Danny Thorpe, seinerzeit Compiler-Engineer, mal gesagt/geschrieben, dass die Warnungen vom Code-Generator erzeugt werden und nicht vom Parser. Der Code-Generator weiß somit nicht mehr, dass das Interface ein Rückgabewert war, denn er sieht nur den "var" Parameter vom Typ Interface.

Zitat:

Also einfach den VAR-Parameter wie einen OUT-Parameter behandeln, wenn es mal ein Result war.
Für den Compiler ist var und out mehr oder weniger dasselbe. Out hat nur eine technische Bedeutung bei OleVariants. (Ich nutze persönlich out aber trozdem zur "Dokumentation").

Zitat:

Aber ob man es nach 15 Jahren schafft das jetzt noch zu reparieren?
Wo ein Wille da ein Weg. Aber der Wille tendiert im Moment mehr zu "wir streichen die Datentypen zusammen".


Zitat:

Zitat von Stevie (Beitrag 1246000)
Eine Warnung in solch einem Fall wäre mehr wert als so manche Warnings, die der Compiler ausspuckt :(

Eine solche Warnung, vor allem bei dyamischen Arrays würde auch der Delphi IDE gut tun. Ich erinnere mich da an so manches Performance-Problem, wie z.B. "mit jedem Kompilieren dauert das nächste Kompilieren länger".
Der Grund war Code, der das dynamische Rückgabe-Array nicht abgelöscht und es fleißig mit "SetLength(Result, Length(Result) + 1)" in einer Schleife vergrößert hat (man hätte ja auch die Anzahl vorher bestimmen können).

Pseudocode:
Delphi-Quellcode:
begin
  for I := 0 to Items.Count - 1 do
  begin
    FMyArrayObjectField := Items[I].ToArray;
    ...
  end;
end.

function TMyItem.ToArray: TArray<string>;
begin
  // hier das fehlende "Result := nil;"
  for I := 0 to Item.ChildCount - 1 do
  begin
    SetLength(Result, Length(Result) + 1);
    Result[High(Result)] := Item.Childs[I];
  end;
end;

Stevie 30. Jan 2014 22:52

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
Zitat:

Zitat von jbg (Beitrag 1246044)
Eine solche Warnung, vor allem bei dyamischen Arrays würde auch der Delphi IDE gut tun. Ich erinnere mich da an so manches Performance-Problem, wie z.B. "mit jedem Kompilieren dauert das nächste Kompilieren länger".
Der Grund war Code, der das dynamische Rückgabe-Array nicht abgelöscht und es fleißig mit "SetLength(Result, Length(Result) + 1)" in einer Schleife vergrößert hat (man hätte ja auch die Anzahl vorher bestimmen können).

Pseudocode:
Delphi-Quellcode:
begin
  for I := 0 to Items.Count - 1 do
  begin
    FMyArrayObjectField := Items[I].ToArray;
    ...
  end;
end.

function TMyItem.ToArray: TArray<string>;
begin
  // hier das fehlende "Result := nil;"
  for I := 0 to Item.ChildCount - 1 do
  begin
    SetLength(Result, Length(Result) + 1);
    Result[High(Result)] := Item.Childs[I];
  end;
end;

lol aua :wall: Erstens nicht initialisiert und zweitens lustig in der Schleife das Array reallokieren.

himitsu 30. Jan 2014 23:10

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
Zitat:

Zitat von Stevie (Beitrag 1246051)
zweitens lustig in der Schleife das Array reallokieren.

Durch den FastMM wird das aber ein bissl ausgeglichen, da der, durch ein paar kleine Optimierungen, nicht wirklich den Speicher bei jedem Durchlauf umkopiert.
- Inplace-Reallocation bei mittleren und großen Blöcken ... wenn dahinter noch Platz ist, wird das einfach angehängt
- und beim Reallocationen wird nicht nur das angeforderte angepasst ... wird ich Schritten erledigt, so daß die nächsten Reallocationen schon genügend Platz haben

Zitat:

Zitat von Stevie (Beitrag 1246051)
Erstens nicht initialisiert

Wie gesagt, wenn man es genau nimmt, ist das ja initialisiert, da das halt ein Managed-Type ist ... Nur eben nicht so/dort, wie/wo man es "erwarten" würde.
Drum funktioniert das oftmals "zufälliger" Weise, aber in Schleifen aufgerufen, hat man dann seinen Spaß.

Es ist wirklich zu schade, daß hier die Prüfung "Variable nicht initialisiert" nicht greift. :cry:
Bin auch schonmal in diese Falle getreten.

Der Pseudocode von jbg sieht intern praktisch so aus.
So erkennt man dann auch ganz schnell, warum das schief gehen muß.
Und bei einmaligem/erstmaligem Ausführen, passt es ja zufällig noch.

Delphi-Quellcode:
procedure TMyItem.ToArray(var Result: TArray<string>);
begin // aber hier wird es eben nicht "nochmal" initialisiert
  for I := 0 to Item.ChildCount - 1 do
  begin
    SetLength(Result, Length(Result) + 1);
    Result[High(Result)] := Item.Childs[I];
  end;
end;

var
  FMyArrayObjectField: TArray<string>;
begin // FMyArrayObjectField wurde quasi in diesem Begin initialisiert
  for I := 0 to Items.Count - 1 do
  begin
    Items[I].ToArray(FMyArrayObjectField);
    ...
  end;
end;
Und aus dem
Delphi-Quellcode:
begin
  for I := 0 to Items.Count - 1 do
  begin
    Items[I].ToArray; // ohne Zuweisung
    ...
    EinProperty := Items[I].ToArray;
  end;
end;
wird das
Delphi-Quellcode:
var
  GeheimeTempVar: TArray<string>:
begin
  for I := 0 to Items.Count - 1 do
  begin
    Items[I].ToArray(GeheimeTempVar);
    ...
    Items[I].ToArray(GeheimeTempVar);
    SetterDes_EinProperty(GeheimeTempVar);
  end;
end;

Stevie 30. Jan 2014 23:33

AW: Warum macht es nichts, bei Rückgabe eines Interfaces nichts initialisiert zu habe
 
Zitat:

Zitat von himitsu (Beitrag 1246055)
Durch den FastMM wird das aber ein bissl ausgeglichen, da der, durch ein paar kleine Optimierungen, nicht wirklich den Speicher bei jedem Durchlauf umkopiert.
- Inplace-Reallocation bei mittleren und großen Blöcken ... wenn dahinter noch Platz ist, wird das einfach angehängt
- und beim Reallocationen wird nicht nur das angeforderte angepasst ... wird ich Schritten erledigt, so daß die nächsten Reallocationen schon genügend Platz haben

Genau, warum auch sauberen Code schreiben... Einfach ma Taktzyklen sinnlos verblasen, obwohl man vorher schon weiß, wie groß das Array sein muss :roll: Fällt bei 100 Elementen nicht ins Gewicht, aber dass nicht nur im kleinen geschludert wird, sieht man ja daran, dass Andreas jedesmal genug zu optimieren hat und aus Compiletimes im Minutenbereich nur noch Sekunden macht (Generics, *hust*)

Zitat:

Zitat von himitsu (Beitrag 1246055)
Wie gesagt, wenn man es genau nimmt, ist das ja initialisiert, da das halt ein Managed-Type ist ... Nur eben nicht so/dort, wie/wo man es "erwarten" würde.

In der Routine ist Result nicht initialisiert, punkt. Dass ein u.U initialisierter Wert reinkommt (oder auch nicht), spielt keine Rolle, da man eine Routine isoliert betrachten muss. Und unter dieser Betrachtungsweise ist sie defekt.


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:08 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz