Jump to content
Sign in to follow this  
Mike Torrettinni

Can undefined function result point to valid/used data in memory?

Recommended Posts

It's quite common to initialize Result of string function with SetLength(Result, len) and then work with the Result, like:

 

// Switch Upper to lower and lower to Upper case
function SwitchCase(const aStr: string): string;
var i: integer;
begin
  SetLength(Result, Length(aStr)); // without Result := '';
  for i := 1 to aStr.Length do
    if CharInSet(aStr[i], ['a'..'z']) then
      Result[i] := UpCase(aStr[i])
    else
      Result[i] := LoCase(aStr[i]);
end;

 

And looking at debugger:

 

image.thumb.png.9579c7289d5210e99fb6f4e03620e548.png

 

the Result holds some data, which is overwritten with the code after SetLength. Of course if we initialize Result := ''; before SetLength, then Result is a new string.

 

Before Result is initialized with SetLength, it is undefined, so it points to a random memory location, I guess.

 

Is that always unused memory location or is there a danger that we are actually overwriting valid memory block (part of other string, or long array.,..) and can cause issues in the running code?

 

 

Share this post


Link to post

If you are using the string as a pointer it's your responsibility to stay in its boundaries.

The memory around it could be anything. Even if it's "unused", the next moment it could be assigned to other things.

Edited by Attila Kovacs

Share this post


Link to post
2 hours ago, Mike Torrettinni said:

Before Result is initialized with SetLength, it is undefined

No. It's empty.

 

2 hours ago, Mike Torrettinni said:

is there a danger that we are actually overwriting valid memory block (part of other string, or long array.,..) and can cause issues in the running code?

I don't know who "we" are but there's nothing unsafe about the code you posted. It's just fundamental string handling.

Share this post


Link to post

Delphi strings are managed types so they are never ill-defined in the way that unmanaged types can be before first assignment. 

 

Note that this assumes correct practise. So if you use GetMem rather than New to allocate memory for a managed type then the above statement is not correct, but then doing that would be incorrect practise. 

Edited by David Heffernan
  • Like 2

Share this post


Link to post
10 hours ago, Mike Torrettinni said:

Is that always unused memory location or is there a danger that we are actually overwriting valid memory block (part of other string, or long array.,..) and can cause issues in the running code?

Any allocations give you previously unused memory blocks, if that wouldn't be true even primitive Hello world app would randomly crash

Edited by Fr0sT.Brutal
  • Thanks 1

Share this post


Link to post
10 hours ago, Mike Torrettinni said:

It's quite common to initialize Result of string function with SetLength(Result, len) and then work with the Result, like:

 


// Switch Upper to lower and lower to Upper case
function SwitchCase(const aStr: string): string;
var i: integer;
begin
  SetLength(Result, Length(aStr)); // without Result := '';
  for i := 1 to aStr.Length do
    if CharInSet(aStr[i], ['a'..'z']) then
      Result[i] := UpCase(aStr[i])
    else
      Result[i] := LoCase(aStr[i]);
end;

the Result holds some data, which is overwritten with the code after SetLength. Of course if we initialize Result := ''; before SetLength, then Result is a new string.Is that always unused memory location or is there a danger that we are actually overwriting valid memory block (part of other string, or long array.,..) and can cause issues in the running code?

 

 

A function return value of type string as well as other compiler-managed types (e.g. dynamic arrays) is actually implemented as an additional Var parameter, so your function would be equivalent to

procedure SwitchCase(const aStr: string; var Result:string);

The compiler initializes the string variable passed for Result at the point of call, so it will always be valid. It is not guaranteed to be nil (string.empty), though. The compiler may reuse another hidden local variable used previously in the calling method.

 

Such things are documented in the Delphi Language guide, see https://docwiki.embarcadero.com/RADStudio/Sydney/en/Program_Control_(Delphi)

  • Thanks 1

Share this post


Link to post
1 hour ago, PeterBelow said:

The compiler initializes the string variable passed for Result at the point of call, so it will always be valid. It is not guaranteed to be nil (string.empty), though. The compiler may reuse another hidden local variable used previously in the calling method.

 

Such things are documented in the Delphi Language guide, see https://docwiki.embarcadero.com/RADStudio/Sydney/en/Program_Control_(Delphi)

Thanks, I did read the link, but didn't get to the same conclusion as you describe it:

1 hour ago, PeterBelow said:

The compiler initializes the string variable passed for Result at the point of call, so it will always be valid. It is not guaranteed to be nil (string.empty), though. The compiler may reuse another hidden local variable used previously in the calling method.

I think I understand a bit better now, the difference of compiler initialization and our initialization of vars in the function.

 

Is my understanding correct, if I say it like this (in non-expert language):

 

the 'the compiler initializes' is not the same as we initialize local vars with localtIntVar := 0; or Result:= '';. The compiler initializes variables (including Result) (like assigns registers or memory pointers, or whatever, to each var) and we initialize the value of these variables.

Strings local vars are managed, so compiler initializes variable and the value to ''.

Integer local vars are not manage, so compiler only initializes variables and we need to initialize the value to 0 or whatever we need, manually.

And in case of function Result, the initialization doesn't really just point to a random/undefined memory space, but it might keep a list of previously ready-to-be-reused hidden variables or it uses some other location, but never actual valid data. (as Frost pointed out this would be fundamental flaw if it changed the valid data)

 

Right?

Edited by Mike Torrettinni
deleted empty lines

Share this post


Link to post
3 hours ago, PeterBelow said:

The compiler may reuse another hidden local variable used previously in the calling method.

From my experience it never does that - if you have 3 different lines with function calls returning a string the compiler will generate 3 distinct hidden local variables. Even if it's guaranteed they will never be used both such as calling different string returning functions in both branches of an if or in a case.

 

To understand what Peter showed before and how strings (and other managed type) results are handled look at this code:

 

function Test: string;
begin
  Result := Result + 'hello';
end;

procedure Main;
var
  s: string;
begin
  s := Test;
  s := Test;
  s := Test;
  Writeln(s);
end;

var
  s: string;
  i: Integer;
begin
  // case 1
  Main;

  // case 2
  s := Test;
  s := Test;
  s := Test;
  Writeln(s);

  // case 3
  for i := 1 to 3 do
    s := Test;
  Writeln(s);
end.

 

1. When you assign the string result to a local variable the compiler directly passes the local variable as that hidden result var parameter - hence you will see the output 'hellohellohello' 

2. When you assign the string result to a global variable (or a field of an object) the compiler generates distinct hidden variables for each occurence of the call - three in this case - they are also initialized to zero like any explicit variable. It then assigns that to s. This is why you will see 'hello'

3. When doing multiple calls in a loop the compiler had only generated one hidden variable - of course which will be reused for each call - now again you will see 'hellohellohello'

Edited by Stefan Glienke
  • Like 1
  • Thanks 1

Share this post


Link to post
2 hours ago, Stefan Glienke said:

1. When you assign the string result to a local variable the compiler directly passes the local variable as that hidden result var parameter - hence you will see the output 'hellohellohello' 

Thank you, very clear example.

 

I was trying to see if compiler will make mistake or get confused if I put a 'surprise!' in there, like:

procedure Main2;
var
  s,s2: string;
begin
  s := Test;
  s := Test;
  s2 := s;
  s2 := Test;
  Writeln('s  = ' + s);
  Writeln('s2 = ' + s2);
end;

but still works as expected:

image.png.6fd658f366f9f8598006c3f75c1746d6.png

 

Edited by Mike Torrettinni

Share this post


Link to post
2 hours ago, Mike Torrettinni said:

but still works as expected:

Nothing here is "as expected". This is all undefined behaviour that is subject to change in future compiler releases. It's a mistake to read the return value before assigning to it. 

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
Sign in to follow this  

×