Jump to content
Mike Torrettinni

The Case of Delphi Const String Parameters

Recommended Posts

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
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 (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.

image.thumb.png.1ebff14d60fdc18943b8ece824009473.png

 

..

hug

Edited by Guest

Share this post


Link to post
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.

 

image.png

Share this post


Link to post
Guest
Posted (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  :classic_cheerleader: 9 and turn you upside down

 

image.thumb.png.bc0c8017528a5c57e7c56214d91cff26.png

 

but the "what the explanation?" i Dont know --- as you say... memory leaks - short answer!

Edited by Guest

Share this post


Link to post
Posted (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 by balabuev
  • Like 1

Share this post


Link to post
Guest
Posted (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

 

image.thumb.png.1af8208cb9d6356ad903d66eb2db93cb.png   image.thumb.png.0586d5f6019e8e69be1c8b53db9577c7.png

 

  • NOTE: now i can reproduce the change using a looping "FOR" and in 2º pass the value was changed! :classic_love:
  • Sorry Ruben, it's real!!!

hug

Edited by Guest

Share this post


Link to post
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
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 (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! :classic_cheerleader:

the life is a "always learning"

Edited by Guest

Share this post


Link to post
Posted (edited)
4 minutes ago, emailx45 said:

I like this ...   bullet by bullet! :classic_cheerleader:

the life is a "always learning"

Or we avoid each other threads, this would be nice. But is public forum, so no force.

Edited by Mike Torrettinni

Share this post


Link to post
Guest
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
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!  :classic_wink:

you're wellcome, anyway!

Share this post


Link to post
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 (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 by Guest

Share this post


Link to post
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

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.

  • Like 3

Share this post


Link to post
Guest

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

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.

  • Like 1

Share this post


Link to post
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
Posted (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 by balabuev

Share this post


Link to post
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
Posted (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 by Dalija Prasnikar
  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×