Mike Torrettinni 198 Posted April 4, 2022 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: 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
Attila Kovacs 629 Posted April 4, 2022 (edited) 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 April 4, 2022 by Attila Kovacs Share this post Link to post
Anders Melander 1783 Posted April 4, 2022 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
David Heffernan 2345 Posted April 5, 2022 (edited) 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 April 5, 2022 by David Heffernan 2 Share this post Link to post
Fr0sT.Brutal 900 Posted April 5, 2022 (edited) 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 April 5, 2022 by Fr0sT.Brutal 1 Share this post Link to post
PeterBelow 238 Posted April 5, 2022 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) 1 Share this post Link to post
Mike Torrettinni 198 Posted April 5, 2022 (edited) 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 April 5, 2022 by Mike Torrettinni deleted empty lines Share this post Link to post
Stefan Glienke 2002 Posted April 5, 2022 (edited) 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 April 5, 2022 by Stefan Glienke 1 1 Share this post Link to post
Mike Torrettinni 198 Posted April 5, 2022 (edited) 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: Edited April 5, 2022 by Mike Torrettinni Share this post Link to post
David Heffernan 2345 Posted April 5, 2022 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