Today, doing some tests to answer some questions from internet users, I came across a situation, until then, never faced!
First, let's describe the scenario in use: - MSWindows 10 21H2 - RAD Studio 11.1 Alexandria - VCL project 32bit (nothing special) - 1 Form main (created by IDE - automatically) - 1 Form second (not used at all - just is in my project) - 1 Button to tests!
Now the question itself:
procedure TForm1.Button1Click(Sender: TObject); var MyHelloVar: TForm; begin MyHelloVar.Close; // what this do it? Raise to "Access Violation" or Not? end;
Well, this command should, in fact, throw an "Exception" (or an "Access Violation" error).
But in fact it does not throw any error. And yet, it executes the command "CLOSE" correctly.
Now comes the explanation: This is another one of Delphi's "Bugs" (which I hadn't come across yet).
As explained by engineer "Andreas Rejbrand": "Local variables of unmanaged types (like class types, like TForm) are uninitialized, so you are calling TForm.Close on a random pointer (whatever happens to be in your computer's RAM at that place). Then anything can happen. Different things can happens every time you run the code. This is a bug."
So, stay tuned!
In Delphi, a wrong command is not always a command error!
So never fully trust reviews like:
If Assigned( xxxxxx ) then ....
or
If not(xxxx = nil) then ....
Because, really, it is not always possible to know whether or not a variable (object instance) was actually created.
Therefore, the procedure "Destroy" should not be called (directly) from any object, such as: MyForm.Destroy;
Hello World! :_>
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
测试了一下,很有意思的一个现象: Form2内容如下: type TForm2 = class(TForm) private { Private declarations } public { Public declarations } XXX:string; DataArray:array[0..100] of array[0..100] of Integer;
procedure SayHello; end;
var Form2: TForm2;
implementation
{$R *.dfm}
{ TForm2 }
procedure TForm2.SayHello; begin ShowMessage('Hello from Form2 and XXX='+XXX); end;
---------- 主窗体Form1的内容: var Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject); var F2: TForm2; begin if Assigned(F2) then begin // F2.SayHello; //1 // F2.XXX:='xxxxx'; //2
ShowMessage('F2.DataArray.Size=' + Length(F2.DataArray).ToString); //3 ShowMessage('F2.XXX=' + F2.XXX); //4 end else ShowMessage('F2 Not exists'); end;
特意在Project设置里禁止自动生成Form2 在debug模式下,//3 OK ,//4 AVz错误 在release模式下,//3和//4都OK 在release模式下,如果注释掉2,3,4,只保留1,会AV错误;注释掉2,3,保留1,4,OK
I finally understand why some people desperately want to delete the pointer in Delphi. Because he can't understand the pointer. These people have exerted a very bad influence on the development of Delphi.
----------------------------------------------
-
var abc:TForm // TAnyThing begin try ... abc=nil; //init the var abc in your own way. ... except ... if Assigned(abc) then FreeAndNil(abc) ... end;
----------------------------------------------
Bye bye DDRFAN...
I think the big problem with "Object Pascal" about valid "pointers", like my initial post, it's that the compiler generate randomics values to initializate the vars or objects!
I my sample, you can see that "MyHelloVar" is not really = nil or null value. Because "MyHelloVar : TForm", where "TForm" is a reference to a class, so, "MyHelloVar = something" not "MyHelloVar = NOTHING".
for that, Assigned(MyHelloVar)=IsNotNIl or (MyHelloVar=nil)=IsFALSE because "MyHelloVar=AddressUsedByTFormOnMemory" be "0", or any other value.
For this, it's always recomended initializate any var_or_object before your use, for example;
var MyVar:string; // managed by compiler == a garbage-collector take care MyFrm:TMyFormX; // unmanaged begin MyVar:=''; MyFrm:=nil; try ... finally ... MyFrm.Free; // OK because Free verify if "Self=nil" ... end;
Self = my instance_object ---------- if Self=nil = "IF" my pointer used to reference my var_instance is nil or not!
if NOT nil then FREE IT! ---------- the object will be released from memory, BUT YOUR DATAs NOT!
The data of my object still on memory until the memory be necessary to another object or any other.
then, this action, create this stranger beraviours!
new vars appointing for old data on memory!
this topic it's really complex to understand at all. Many enginners and MVP have distincts understooding about it
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
The core issue is the use of stacks. An instruction to modify the ESP register can quickly allocate memory. No GetMem, No AllocMem. It is the fastest. This is determined by the underlying mechanism. For string type, its memory allocation is not on the stack at all. What I want to say is that you need to understand the difference. Understand how assembly code implements these features. Maybe you will have a different view. In my opinion, if this is a bug and be fixed. Then, Delphi has no use value.
----------------------------------------------
-
@emailx45 For local vars, Delphi just gives a position to reference it and keep it as is. Coder should & must guarantee the initialization and cleanup working as expected.
----------------------------------------------
-
It is this demand that can not clearly establish the positioning of Delphi language that leads to a lot of useless efforts.
For example, ARC wants to solve all the life cycle problems. It completely ignores the orientation of native language. Leading to 10 years of waste.
If the positioning of Delphi is consistent with PHP and JavaScript, then it is pretty good. But Delphi is not a script, it is native, it must runs fastest.
All these effort try to slow down Delphi. It's so sad.
----------------------------------------------
-
After all that's been said, I think that if there isn't a market for every single programming language out there today, then they've died of their own accord.
But as we see, there are millions of people with countless and precious abilities, so there will always be a place in the sun for everyone.
Of course, it is the "market" who determines whether or not something is relevant.
He was the determining factor for the Silk Road and all other forms and means of homo-sapiens expansion.
If there is a market, there will be a programmer dissatisfied with his own achievements!
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
I dont want to say nothing about "Delphi ScriptED"! nothing! nothing!
my English is very poor!!! SORRYYYYYYYYYY.... ---------- I meant: "IF" there is a market for a product, "THEN" there will be someone to consume it!!! And SO, there will always be someone DISSATISFIED, too! ----------
My ignorance is bigger than the universe :))))
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
FREE ==> "mark" the object-instance to receive a "nil" value if Count-Reference is 0... like a "delete" in DB... after this, you can do the "COMMIT" real ( xxxx := NIL ) then the memory it's released for another tasks.
FreeAndNil(...) ==> verify and release it "IF" the vars (itself) is a var-pointer have a valid assignement to a valid object. For that, FreeAndNil() dont "crash" if a var-object is nil
--------- implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject); var MyStrangerBehaviousInDelphi: TForm; begin ShowMessage(integer(pointer(Self)).ToString); // try MyStrangerBehaviousInDelphi.Free; // works = no "AV" = works like "Self.HIDE()" = Form1.HIDE() // // MyStrangerBehaviousInDelphi:=nil; // comment and uncomment for test // FreeAndNil(MyStrangerBehaviousInDelphi); // works = no "AV" = works like "Self.HIDE()" = Form1.HIDE() // // FreeAndNil(nil); // works! BUT you have a "Access Violation" or an "EAccessViolation exception" -- "nil^" does not possible! // ShowMessage(integer(pointer(MyStrangerBehaviousInDelphi)).ToString); /// after... you have a "Abstract error" == the function exists (ToString), but the obj is gone! except on E: Exception do ShowMessage('My Except = ' + E.ClassName + sLineBreak + E.Message); end; end;
end.
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
for me, "VALID" it's all that exists and "it" is type expected"! something "that exists", but it's not the "type expected", it's not valid!
this is valid concept in Delphi too...
look the "warning message": "mismatched type", when we try to assign a value to a var-object that expects a specific "type"-value.
common situations where the code expected a value, and receive another! in runtime, you'll have a "exception" as way to show that "something it's wrong in your code"!
procedure TForm1.Button1Click(Sender: TObject); var a: integer; // valid types for each future values b: string; c: TForm; d: TImage; begin
// all values will be valid! same that "nil" // but the "types" not necessary it's a type expected // a := b; // c := d;
// fortunatelly, the compiler look this! and dont compile code! // but in runtime avaliation, this it's not occurrs until the run it! // then, we have a "exception" raised! or in some cases, the bug appears // only in a "specific situations" not reproduced at all... = developer-bug! // sorry my english!
end; ---------- // here, the Delphi power: where a type is equal a another type,"IF" its "name" it's the same! // not necessarely, its value!
//We can compare "name to type" as if it were the area of existence of a certain value! //That is, we are saying something like: Memory, "in this area here", you can only allocate spaces for this kind of values...
here, I'm not talking about "developer should take care about all possibilities in your code" no no no! I'm talking about the "special situations" (like my test above), where the Delphi should take care my "errors idiot" ... you see?
of course, that my code should know many situations, but, in my proposital-tests, it should not pass! at all...
it's hard to xpress myself in another language... sorry!
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
look this: Your code, my workaround! MODE: DEBUG or RELEASE! NO ERRORS ANYMORE!
----------
type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private public end;
var Form1: TForm1;
implementation
{$R *.dfm}
uses Unit2;
procedure TForm1.Button1Click(Sender: TObject); var F2: TForm2; begin ShowMessage(integer(pointer(self)).ToString);
if Assigned(F2) then begin ShowMessage(integer(pointer(F2)).ToString); F2.SayHello; // 1 F2.XXX := 'xxxxx'; // 2
ShowMessage('F2.DataArray.Size=' + Length(F2.DataArray).ToString); // 3 ShowMessage('F2.XXX=' + F2.XXX); // 4 end else ShowMessage('F2 Not exists'); end;
end.
---------- unit Unit2;
interface .... type TForm = class(Vcl.Forms.TForm) <== redefining the TForm class here! public procedure SayHello; end;
TForm2 = class(TForm) private { Private declarations } public XXX : string; DataArray: array [0 .. 100] of array [0 .. 100] of Integer; procedure SayHelloXXX; end;
var Form2: TForm2;
implementation
{$R *.dfm} { TForm2 }
procedure TForm2.SayHelloXXX; begin ShowMessage('Hello from Form2 and XXX=' + XXX); end;
{ TForm }
procedure TForm.SayHello; begin ShowMessage('Hello from TForm and XXX='); end;
end.
----------
NO ERRORS NOW! The TForm class "now" have a method "SayHello" and "F2".SayHello now works! because the TForm struture on memory have a new procedure "SayHello"!
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
.... then a new address will be used to reference a "SayHello" re-introduced and your "old AccessViolation" go back!!!
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
TForm2($0040001).SayHello; /// WORKS! TForm2(266240).SayHello; /// WORKS! // TForm2($0040001).SayHelloXXX; // raise a AV! as expected!
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
In Embarcadero's DocWiki, we can read the text that can prove my view that I tried to describe just above in my post: ---------- https://docwiki.embarcadero.com/RADStudio/Sydney/en/Variables_(Delphi)
Variables (Delphi) "A variable is an identifier whose value can change at run time. Put differently, a variable is a name for a location in memory; you can use the name to read or write to the memory location. Variables are like containers for data, and, because they are typed, they tell the compiler how to interpret the data they hold." ----------
Variables declared within a procedure or function are sometimes called local, while other variables are called global. Global variables can be initialized at the same time they are declared, using the syntax:
var identifier: type = constantExpression;
Local variables cannot be initialized in their declarations. Multiple variable declarations (such as var X, Y, Z: Real;) cannot include initializations, nor can declarations of variant and file-type variables.
- If you do not explicitly initialize a global variable, the compiler initializes it to 0. - Object instance data (fields) are also initialized to 0. - On the Win32 platform, the contents of a local variable are undefined until a value is assigned to them. <---
When you declare a variable, you are allocating memory which is freed automatically when the variable is no longer used. In particular, local variables exist only until the program exits from the function or procedure in which they are declared. For more information about variables and memory management, see Memory Management.
You can create a new variable that resides at the same address as another variable. To do so, put the directive absolute after the type name in the declaration of the new variable, followed by the name of an existing (previously declared) variable.
You can create dynamic variables by calling the GetMem or New procedure. Such variables are allocated on the heap and are not managed automatically. Once you create one, it is your responsibility ultimately to free the variable's memory; use FreeMem to destroy variables created by GetMem and Dispose to destroy variables created by New. Other standard routines that operate on dynamic variables include ReallocMem, AllocMem, Initialize, Finalize, StrAlloc, and StrDispose.
Long strings, wide strings, dynamic arrays, variants, and interfaces are also heap-allocated dynamic variables, but their memory is managed automatically.
Thread-local (or thread) variables are used in multithreaded applications. A thread-local variable is like a global variable, except that each thread of execution gets its own private copy of the variable, which cannot be accessed from other threads. Thread-local variables are declared with threadvar instead of var.
You can free a variant by setting it to Unassigned and an interface or dynamic array by setting it to nil.
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
procedure TForm1.Button1Click(Sender: TObject); var P: Pointer; begin // here my test to see if "P" have or not a value in runtime... Caption := integer(P).ToString; // P := nil; if Assigned(P) then MessageDlg('You won''t see this', mtInformation, [mbOK], 0); // GetMem(P, 1024); { P valid } FreeMem(P, 1024); { P no longer valid and still not nil } // // P := nil; // uncomment and the P = "0" if Assigned(P) then MessageDlg('You''ll see this', mtInformation, [mbOK], 0); // // here let's test if "P" have or not a value in runtime... Caption := Caption + ', ' + integer(P).ToString + ' ... on the end of all'; end;
result := YES, the value (not necessarely the same value) will be present same than "nil" is assigned again on var!
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3