A.M. Hoornweg 144 Posted January 5, 2021 16 hours ago, Rollo62 said: I would disagree to that, because a compiler should understand that by ref and by value of the same value is requested, Unlike "shortstring", Delphi cannot pass managed types such as strings by value. It is always a pointer. I'm not 100% sure but I think an "out" parameter is like a "promise" to return a value no matter what, and also a promise not to use it for input. The problem being that it is possible to pass the same variable twice and thus circumvent the no-input promise. Share this post Link to post
Rollo62 536 Posted January 5, 2021 3 hours ago, A.M. Hoornweg said: Unlike "shortstring", Delphi cannot pass managed types such as strings by value. It is always a pointer. I know, but it uses the syntax of "by value", and this should give the compiler enough clues. Anyway, I would think that OUT will be touched first time only INSIDE of a function, at the very first line, like the "var" section, but not already outside by the caller. In practice I nearly all the time use VAR, instead of OUT, and have no (philosophical) issues with that. A VAR is IMHO usable as an out parameter, as I can use it to bring pre-settings with it. My typical pattern could be like this function TryProcessValue( var AValue : String; ANew : String; ACanIProceed : Boolean ) : Boolean; begin if ACanIProceed then begin AValue := ANew + ' I can happily proceed, and setup my OUT variable here'; Result := True; // This may additionally notify that AValue is valid end else begin AValue := ''; //<== Initialize AValue ( to ensure the OUT variable is well defined ) Result := False; // This notify that AValue is invalid end; end: So I don't need to test AValue itself, since I can use the function result itself, while still AValue could be checked too and has a defined value. Share this post Link to post
Guest Posted January 5, 2021 (edited) trying reprodure the "AV" or "colateral effect" by Marcos Cantus but I dont see it code by Marcos clicking like a "crazy" to reproduce it I dont see the "AV" or ghost-resulted on "Value" resulted just the "OK" on my ShowMessage() -- what is stranger procedure TForm1.Button1Click(Sender: TObject); var s1: string; // procedure Test(const Value: string); begin s1 := '456'; ShowMessage(Value); end; begin s1 := Copy('123', 1); Test(s1); end; initialization ReportMemoryLeaksOnShutdown := true; finalization end. .. hug Edited January 5, 2021 by Guest Share this post Link to post
Guest Posted January 5, 2021 13 minutes ago, emailx45 said: I dont see the "AV" or ghost-resulted on "Value" resulted "OK" is a shadow/ghost value here, it been copied form the button caption. Not seeing AV doesn't mean the code is right, also the lack of memory leak, both are will raise in different time with different heap or stack content, in other words it will be chance or luck, or simple a matter of time. Share this post Link to post
Guest Posted January 5, 2021 (edited) hi @Kas Ob. I see here when occurr the "stranger" change: on last step from ShowMessage() function InternalDoTaskMessageDlgPosHelp() .... unit VCL.Dialogs.pas, line 6669 === 666 9 and turn you upside down but the "what the explanation?" i Dont know --- as you say... memory leaks - short answer! Edited January 5, 2021 by Guest Share this post Link to post
balabuev 102 Posted January 5, 2021 (edited) On 1/2/2021 at 3:45 PM, David Heffernan said: Nothing to see here. Carry on using const for string parameters. The issue is vanishingly rare. If you encounter the issue, fix it. Don't go changing existing code unless you are actually affected by the issue. Agree. I'm always use "const" modifier before any parameter of managed type (arrays, strings, interfaces, references to procedure types) or of big record type, because it executes faster. I remeber one or two such issues, which, by the way, was quite hard to debug, but, imho, this is still not the reason to remove all "const" parameter modifiers. Edited January 5, 2021 by balabuev 1 Share this post Link to post
Guest Posted January 5, 2021 (edited) As Ruben Lopez comment on Marco's post, the value is presented as expected the abnormal resulted is presented by ShowMessage() not necessary by Test() function NOTE: now i can reproduce the change using a looping "FOR" and in 2º pass the value was changed! Sorry Ruben, it's real!!! hug Edited January 5, 2021 by Guest Share this post Link to post
Guest Posted January 5, 2021 Thanks @Mike Torrettinni your "sad" contributed very well for this topic! Share this post Link to post
Mike Torrettinni 198 Posted January 5, 2021 1 minute ago, emailx45 said: Thanks @Mike Torrettinni your "sad" contributed very well for this topic! just like: Fly Robin fly, right? 😉 I think we know how we contribute to each other topics. Share this post Link to post
Guest Posted January 5, 2021 1 hour ago, emailx45 said: "what the explanation? It is a bug !, And it is between RTL and compiler, this simple way to show how the refCount is wrongly handled between the compiler the the RTL function UStrLAsg, **do not use the following in your code,** procedure TestConst(const Value: string); begin Dec(PInteger(NativeUInt(Value) - 8)^); s1 := '456'; ShowMessage(Value); end; procedure TestConst2(const Value: string); begin Inc(PInteger(NativeUInt(Value) - 8)^); s1 := '456'; Dec(PInteger(NativeUInt(Value) - 8)^); ShowMessage(Value); end; The above refcount manipulation will fix it for const but not for var case. Share this post Link to post
Guest Posted January 5, 2021 (edited) 16 minutes ago, Mike Torrettinni said: just like: Fly Robin fly, right? 😉 I think we know how we contribute to each other topics. I like this ... bullet by bullet! the life is a "always learning" Edited January 5, 2021 by Guest Share this post Link to post
Mike Torrettinni 198 Posted January 5, 2021 (edited) 4 minutes ago, emailx45 said: I like this ... bullet by bullet! the life is a "always learning" Or we avoid each other threads, this would be nice. But is public forum, so no force. Edited January 5, 2021 by Mike Torrettinni Share this post Link to post
Guest Posted January 5, 2021 11 minutes ago, Kas Ob. said: And it is between RTL and compiler, hi @Kas Ob. then, maybe for that the "stranger change" dont occurr in first time or my "many click" but when using a "FOR looping" the compiler can have added "its" code beyond my, right? Share this post Link to post
Guest Posted January 5, 2021 1 minute ago, Mike Torrettinni said: r we avoid each other threads, this would be nice. But is public forum, so no force. No, no, no. I prefered your company than be only! you're wellcome, anyway! Share this post Link to post
Guest Posted January 5, 2021 3 minutes ago, emailx45 said: then, maybe for that the "stranger change" dont occurr in first time or my "many click" but when using a "FOR looping" the compiler can have added "its" code beyond my, right? Although i am not quite sure i do understand that, but It is hard to explain in short, it begins with the fact that const string should by default a refcount as -1 ($FFFFFFFF), that what the compiler in ideal world should ensured, while RTL will expect that, now become passing a string as const at runtime without the value mentioned (-1) and here begins the problems, if it is 0 or 1 these are different cases, also the behaviour is irrelevant wither it should be "OK" or "456" as long both are wrong, both came from different places in to different situations caused by bug in between RTL and the compiler, and different situation the code will work right. Share this post Link to post
Guest Posted January 5, 2021 (edited) i want to say: the compiler added some code when I used "FOR" (trying optimize the my code ), but dont when I call "Test()" simply. begin s1 := Copy('123', 1); for i: 0 to 100 do Test(s1); end; well, it's complex understanding why "msg" param changes to "button name" (later of LoadResString(...) pass have the value in ButtonCaptions: array[TMsgDlgBtn] of Pointer = (n0, n1, @SMsgDlgOK === 3º item )... unit VCL.Dialogs.pas, line 6147 --- as we see, the array is full of "possibles" pointers! what the co-relationship between it? maybe the "address" on memory "in that moment"? think that not... Edited January 5, 2021 by Guest Share this post Link to post
Guest Posted January 5, 2021 41 minutes ago, emailx45 said: well, it's complex understanding why "msg" param changes to "button name" (later of LoadResString(...) pass have the value in ButtonCaptions: array[TMsgDlgBtn] of Pointer = (n0, n1, @SMsgDlgOK === 3º item )... unit VCL.Dialogs.pas, line 6147 --- as we see, the array is full of "possibles" pointers! what the co-relationship between it? maybe the "address" on memory "in that moment"? think that not... Don't waste your time on this, but i will provide you with one example for one scenario, it when the refcount went to 0 the in wrong place then some RTL will call on free memory, means the memory is returned to MM with some chance it will be provided by MM on the next string as long the string length close to the length of the broken one (this is MM dependent), this free can happen in quite few places and some of them might not nil the string that hold the 0 ref, hence you will end up with two different string pointing to the same memory address, if the first should be '123' while the later was called form different place like the message dialog to hold the caption of the button 'OK', but it was and still shared pointer with the first one, and that why you get text in wrong places. Share this post Link to post
Dalija Prasnikar 1396 Posted January 5, 2021 This problem is pure ARC problem. One instance of something is destroyed when other one is created and assigned because const parameter did not trigger reference counting mechanism - this is why const is used as speed optimization in the first place. Same thing that happens with strings happens with reference counted objects, but it is easier to see what happens with objects. You need form with memo and button to test following code. IFoo = interface function GetCount: Integer; function GetNumber: Integer; end; TFoo = class(TInterfacedObject, IFoo) private FNumber: Integer; public constructor Create(ANumber: Integer); destructor Destroy; override; function GetCount: Integer; function GetNumber: Integer; end; constructor TFoo.Create(ANumber: Integer); begin inherited Create; FNumber := ANumber; Form1.Memo1.Lines.Add('Created ' + FNumber.ToString); end; destructor TFoo.Destroy; begin Form1.Memo1.Lines.Add('Destroyed ' + FNumber.ToString); inherited; end; function TFoo.GetCount: Integer; begin Result := FRefCount; end; function TFoo.GetNumber: Integer; begin Result := FNumber; end; procedure TForm1.Button1Click(Sender: TObject); var s1: IFoo; procedure Test(const Value: IFoo); begin s1 := TFoo.Create(456); Memo1.Lines.Add('Test Value ' + Value.GetNumber.ToString); end; begin s1 := TFoo.Create(123); Test(s1); end; Output is: Created 123 Created 456 Destroyed 123 Test Value 123 Destroyed 456 It is pretty obvious that object instance holding 123 is destroyed before its value is printed. Value.GetNumber.ToString is accessing dangling pointer - if the memory where object or string previously occupied is not reused, you may get expected value even though that instance is dead, but if memory is reused in the meantime you will read content of some other data. Also the whole thing might crash, or it might not. Basically you get undefined behavior. 3 Share this post Link to post
Guest Posted January 5, 2021 thanks @Kas Ob. and @Dalija Prasnikar then my observation about address... "maybe the "address" on memory "in that moment"?" had some relevance Share this post Link to post
Arnaud Bouchez 407 Posted January 7, 2021 All this is a void discussion. This code is just broken and should be fixed. It has nothing to do with const or whatever. The compiler is doing what it should, but the code is plain wrong. I fully agree with @David Heffernan here. About the "address", it should be pointer(Value) not @Value. pointer(value) returns the actual pointer of the string content in heap, so will change. It is a faster alternative to @value[1] which works also with value='' -> pointer(value)=nil. @Value returns the memory adress of the local Value variable on the stack, so won't change. 1 Share this post Link to post
Mike Torrettinni 198 Posted January 7, 2021 On 1/4/2021 at 2:46 AM, A.M. Hoornweg said: "Out" parameters are an even bigger can of worms. I had refactored some code recently to use "OUT" parameters instead of VAR parameters (in order to more clearly document the intended use) and it had side effects that were hard to figure out. I thought my debugger had gone bananas. Try single-stepping through this code and watch the values. In hindsight, the cause is clear, but I find the compiler should throw a warning if it encounters such a situation. I now avoid OUT parameters. procedure tform2.test(OUT somestring:String; Defaultvalue:String); begin Somestring:=Defaultvalue; end; procedure TForm2.Button1Click(Sender: TObject); var t:string; begin t:='Testing 1-2-3'; Test(t,t); Showmessage(t); end; I ran Pascal Expert through some of the open source projects and no STWA8 (https://www.peganza.com/PEXHelp/index.html?stwa8_for_loop_with_possible_b.htm) , what your example triggers, was found in those sources. So, I assume this is quite rare to find in real code. Share this post Link to post
balabuev 102 Posted January 7, 2021 (edited) 4 hours ago, Arnaud Bouchez said: About the "address", it should be pointer(Value) not @Value. Agree. But, in this case Pointer(Value) will point to already deallocated "old" string memory, while Pointer(s1) will point to actual string content. Without ShowMessage, old (deallocated) memory block remains unchanged, thats why it seems that everything works ok. ShowMessage internally use heap memory, and so, the content of "old" block potentially becomes replaced, thats why strange random string is reported in this case. Edited January 7, 2021 by balabuev Share this post Link to post
A.M. Hoornweg 144 Posted January 8, 2021 11 hours ago, Mike Torrettinni said: So, I assume this is quite rare to find in real code. That may very well be true. But when a developer is doing refactoring to make code more legible and concise, IMHO it is a good thing if the parameter list reflects the intended use of the parameters. So if a parameter is intended just for outputting something and not for modifying an existing value, the "out" modifier is clearer than "var". I find "const" parameters confusing at times, especially when passing stuff like arrays. If I pass an array as a const, am I supposed to be able to modify elements or not? The Delphi documentation (link at he bottom) says "... constant parameters are similar to value parameters, except that you cannot assign a value to a constant parameter within the body of a procedure or function..." . I don't want to nit-pick, but I find it very confusing and counter-intuitive that I can modify the array in such a case. Type tintarray=tarray<integer>; Procedure dosomething (CONST a:tintarray); var i:integer; begin for i:=0 to high(a)-1 do a[i]:=random(maxint); end; procedure TForm1.Button1Click(Sender: TObject); var x:tintarray; begin setlength(x,10); //newly allocated space is set to 0 dosomething(x); showmessage(format('%d',[x[0]])); end; http://docwiki.embarcadero.com/RADStudio/Sydney/en/Parameters_(Delphi)#Constant_Parameters Share this post Link to post
Dalija Prasnikar 1396 Posted January 8, 2021 I wrote more elaborate post on const parameters https://dalijap.blogspot.com/2021/01/are-const-parameters-dangerous.html 1 Share this post Link to post
Dalija Prasnikar 1396 Posted January 8, 2021 (edited) 3 hours ago, A.M. Hoornweg said: I find "const" parameters confusing at times, especially when passing stuff like arrays. If I pass an array as a const, am I supposed to be able to modify elements or not? The Delphi documentation (link at he bottom) says "... constant parameters are similar to value parameters, except that you cannot assign a value to a constant parameter within the body of a procedure or function..." . I don't want to nit-pick, but I find it very confusing and counter-intuitive that I can modify the array in such a case. This is nature of reference types. It is possible to modify their content. Only reference is passed as constant, not the actual content. Being confused about reference types and how they are passed to the procedures is not Delphi specific thing. For instance, Java passes all parameters as values, but in case of reference types (objects) that does not mean content of the object cannot be changed, unless it is made immutable through some other means (for instance, not having setter or similar methods that would mutate content). That causes two misconceptions, first. that objects references in Java are passed by reference, not by value and second that otherwise mutable objects cannot be changed inside method. Of course, comparing to Java, Delphi has many more parameter modifiers and that certainly complicates situation and possible variations. Edited January 8, 2021 by Dalija Prasnikar 1 Share this post Link to post