A.M. Hoornweg 144 Posted February 17, 2021 Hello all, is there a way to quickly initialize all local variables of a procedure with zero, maybe using FillChar? It would be very practical to use at the beginning of a procedure. I had a case recently where the Delphi compiler didn't throw a warning when I accessed a local variable before it had been initialized and it had me debugging for hours. Share this post Link to post
David Heffernan 2345 Posted February 17, 2021 Not going to be an easy way to do what you ask and I doubt it would address the real problem. Share this post Link to post
Arnaud Bouchez 407 Posted February 17, 2021 (edited) First, all managed types (string, variable, dynamic arrays) will be already initialized with zero by the compiler. What you can do is define all local variables inside a record, then call FillChar() on it. There won't be any performance penalty procedure MyFunction; var i: integer; loc: record x,y,z: integer; a: array[0..10] of double; end; begin writeln(loc.x); // write random value (may be 0) FillChar(loc, SizeOf(loc), 0); writeln(loc.x); // write 0 for sure for i := 0 to high(loc.a) do writeln(loc.a[i]); // will write 0 values end; But as drawback, all those variables will be forced to be on stack, so the compiler won't be able to optimize and use register for them - which is the case for the "i" variable above. So don't put ALL local variables in the record, only those needed to be initialized. Anyway, if you have a lot of variables and a lot of code in a method, it may be time to refactor it, and use a dedicated class to implement the logic. This class could contain the variables of the "record" in my sample code. You could keep this class in the implementation section of your unit, for safety. It will be the safer way to debug - and test! One huge benefit of a dedicated class for any complex process is that it could be tested. Edited February 17, 2021 by Arnaud Bouchez Share this post Link to post
Lars Fosdal 1792 Posted February 17, 2021 Slightly worrysome that there was no warning. Is it complicated to reproduce it and report it? Share this post Link to post
A.M. Hoornweg 144 Posted February 17, 2021 3 minutes ago, Lars Fosdal said: Slightly worrysome that there was no warning. Is it complicated to reproduce it and report it? It seems to vary with compiler versions. I use both XE and Sydney. Share this post Link to post
Lars Fosdal 1792 Posted February 17, 2021 It would be for Sydney that it is most interesting to report, me thinks. Share this post Link to post
Lajos Juhász 293 Posted February 17, 2021 I have found a case that there is no warning for records: procedure TForm1.FormCreate(Sender: TObject); var i: record a,b : integer; end; begin showmessage(IntToStr(i.a)); showmessage(i.a.ToString); end; Share this post Link to post
Stefan Glienke 2002 Posted February 17, 2021 (edited) @Lajos Juhász Already reported as RSP-24383 @A.M. Hoornweg Might be RSP-19835 that got you. Edited February 17, 2021 by Stefan Glienke 1 Share this post Link to post
Fr0sT.Brutal 900 Posted February 17, 2021 procedure test; var first: NativeInt; a: integer; f: double; rec: TSearchRec; last: NativeInt; begin FillChar(first, PByte(@first)-PByte(@last), 0); // Alas won't work :( readln; end; I thought about something like above but compiler appeared too smart and placed "last" right after "first". Share this post Link to post
Guest Posted February 17, 2021 2 hours ago, A.M. Hoornweg said: is there a way to quickly initialize all local variables of a procedure with zero, maybe using FillChar? Interesting question. It might be possible to some extent, but not recommended and dangerous, i am pasting it here as food for thought for curious minds // S Should be the last declared local variable procedure NilTheLocals(S:Pointer); inline; begin FillMemory(PNativeUInt(NativeUInt(AddressOfReturnAddress) - (NativeUInt(AddressOfReturnAddress) - NativeUInt(S))), NativeUInt(AddressOfReturnAddress)-NativeUInt(S)-SizeOf(Pointer),0); // change 0 to 1 to see the effect end; procedure TTT; var A:Integer; B:Integer; C:Integer; D:Integer; E:Integer; F:Integer; begin NilTheLocals(@F); // F is the last declared var //FillMemory(PNativeUInt(NativeUInt(AddressOfReturnAddress) - (NativeUInt(AddressOfReturnAddress) - NativeUInt(@F))), //NativeUInt(AddressOfReturnAddress)-NativeUInt(@F)-SizeOf(Pointer),0); A:=1; B:=2; C:=3; D:=4; E:=5; F:=6; Writeln(A); Writeln(B); Writeln(C); Writeln(D); Writeln(E); Writeln(F); end; This might work fine when compiler optimization disabled, but you should know this 1) with optimization enabled not all locals are stored on the stack, some are just reserved CPU registers, and that why it might not work, you can switch between 32bit and 64bit with optimization on and off, and watch the result yourself in the debugger local view. 2) while it looks safe for the example above, i have no idea if the compiler does rearrange the managed types, while the above does work and will not overflow the stack, but the question will it completely cover all the locals when there is managed types in between? i can't give an answer. Share this post Link to post
Lajos Juhász 293 Posted February 17, 2021 IMHO it's not the sam RSP-24383 is about var parameters. The parameter intToStr is not a var parameter. For example: procedure TForm1.FormCreate(Sender: TObject); var i: record a,b,c: integer; end; a: integer; begin i.a:=i.b; IntToStr(a); IntToStr(i.c); end; I have only one warning: [dcc32 Warning] Unit1.pas(35): W1036 Variable 'a' might not have been initialized Share this post Link to post
Guest Posted February 17, 2021 4 minutes ago, Kas Ob. said: 2) while it looks safe for the example above, i have no idea if the compiler does rearrange the managed types, while the above does work and will not overflow the stack, but the question will it completely cover all the locals when there is managed types in between? i can't give an answer. Now i see, it does rearrange the managed types to the top of the local stack, you can see it in assembly procedure TTT; var A: Integer; B: Integer; st: string; C: Integer; D: Integer; mt: string; E: Integer; F: Integer; begin NilTheLocals(@F); // F is the last declared var Gimli.dpr.291: begin 0041C288 55 push ebp 0041C289 8BEC mov ebp,esp 0041C28B 83C4EC add esp,-$14 0041C28E 53 push ebx 0041C28F 56 push esi 0041C290 57 push edi 0041C291 33C0 xor eax,eax 0041C293 8945FC mov [ebp-$04],eax // st 0041C296 8945F8 mov [ebp-$08],eax // mt 0041C299 33C0 xor eax,eax 0041C29B 55 push ebp 0041C29C 68C1C34100 push $0041c3c1 0041C2A1 64FF30 push dword ptr fs:[eax] 0041C2A4 648920 mov fs:[eax],esp Gimli.dpr.292: NilTheLocals(@F); // F is the last declared var 0041C2A7 8D4504 lea eax,[ebp+$04] 0041C2AA 8BD0 mov edx,eax 0041C2AC 8D45EC lea eax,[ebp-$14] 0041C2AF 2BD0 sub edx,eax 0041C2B1 83EA04 sub edx,$04 0041C2B4 8D4504 lea eax,[ebp+$04] 0041C2B7 8D4DEC lea ecx,[ebp-$14] 0041C2BA 2BC1 sub eax,ecx 0041C2BC 50 push eax 0041C2BD 8D4504 lea eax,[ebp+$04] 0041C2C0 59 pop ecx 0041C2C1 2BC1 sub eax,ecx 0041C2C3 B901000000 mov ecx,$00000001 0041C2C8 E8B784FEFF call @FillChar Share this post Link to post
David Heffernan 2345 Posted February 17, 2021 I can't believe that anybody would use such code in real programs. Wouldn't it just be better to initialise your local variables? 6 Share this post Link to post
A.M. Hoornweg 144 Posted February 17, 2021 4 minutes ago, David Heffernan said: I can't believe that anybody would use such code in real programs. Wouldn't it just be better to initialise your local variables? Normally yes. I only asked because I overlooked initializing a certain local variable and the compiler (Delphi XE, which is used in that specific project) didn't warn me. OTOH, if Delphi takes the trouble to zero-fill managed local variables anyway, it puzzles me why it does not simply fill the rest as well. A REP STOSD is lightning fast. Share this post Link to post
Der schöne Günther 316 Posted February 17, 2021 That may be true in your case, but zeroing a local variable "just in case" for a method that gets called over and over defenitely is a performance hit. 2 Share this post Link to post
David Heffernan 2345 Posted February 17, 2021 20 minutes ago, A.M. Hoornweg said: it puzzles me why it does not simply fill the rest as well Because doing so does take time. For sure it won't be noticeable for most functions, but for tiny leaf functions in hotspots, it can be. You realistically are not going to add code manually to every single function you write to do this. The compiler can't do it. Everything else is just noise in this thread. Share this post Link to post
Lars Fosdal 1792 Posted February 17, 2021 If the first thing I do is to set a local variable to a specific value - any previous initialization would be wasted. Managed vars are a different story - since approaching an arbitrary pointer would be unfortunate. Share this post Link to post
A.M. Hoornweg 144 Posted February 17, 2021 2 hours ago, David Heffernan said: I can't believe that anybody would use such code in real programs. Wouldn't it just be better to initialise your local variables? Generally, yes. But it would also be great to have a nifty tool at my disposal that lets me wipe all locals on the stack with a single call, especially if it is a complex routine with lots and lots of local vars. In such cases it would simply save a lot of boilerplate code. Delphi does the very same thing for class members and I would totally hate it if it didn't. I had hoped that this would be a trivial thing to write, since all StdCall procedures start with a byte sequence like "mov ebp,esp; add esp, -sizeof(localvars)". Share this post Link to post
Fr0sT.Brutal 900 Posted February 17, 2021 6 minutes ago, A.M. Hoornweg said: But it would also be great to have a nifty tool at my disposal that lets me wipe all locals on the stack with a single call, especially if it is a complex routine with lots and lots of local vars. In such cases it would simply save a lot of boilerplate code An intrinsic that would be called only when told so? Sounds useful. 1 Share this post Link to post
David Heffernan 2345 Posted February 17, 2021 18 minutes ago, A.M. Hoornweg said: But it would also be great to have a nifty tool at my disposal that lets me wipe all locals on the stack with a single call, especially if it is a complex routine with lots and lots of local vars. It doesn't exist. You'd have to ask Embarcadero. Only they can make it happen. Share this post Link to post
Guest Posted February 17, 2021 (edited) maybe, I said maybe ( I dont want the "genius" disturbing about me) As work with "things" so much complex like: What name of the var? What your type? etc.. etc.. etc... Then, we can use some that existent in OP itself! -- TVarType record with all types accepted in our "universe". If help you, please test with another "types"... here just a insignificant sample for a "genius" on duty! implementation {$R *.dfm} var lMyVarLocalGlobalForThiUnit: integer; lMyOtherVar : string; procedure prcInitItPlease(lVars: array of Variant); var i: integer; begin for i := low(lVars) to high(lVars) do begin case TVarData(lVars[i]).VType of { of course, exist anothers sub-ranges in some types, look at "System.pas", line 1379 } varInteger: lVars[i] := 0; varString: lVars[i] := ''; varDouble: lVars[i] := 0.0; varDate: lVars[i] := TDateTime(0); // first datetime accept in OP // // etc... for another possible types! - of course, maybe need refine for types ambigous, maybe "ordering" the verification // look at "System.pas", line 1379 end; end; end; procedure TForm1.Button1Click(Sender: TObject); begin prcInitItPlease([lMyVarLocalGlobalForThiUnit, lMyOtherVar]); // ShowMessage(Format('[%d], [%s] ', [lMyVarLocalGlobalForThiUnit, lMyOtherVar ])); end; procedure TForm1.Button2Click(Sender: TObject); var lMyVarLocalThiProc: TDateTime; lMyOtherVarLocal : Double; begin prcInitItPlease([lMyVarLocalThiProc, lMyOtherVarLocal]); // ShowMessage(Format('[%s], [%f] ', [DateTimeToStr(lMyVarLocalThiProc), lMyOtherVarLocal])); end; hug Edited February 17, 2021 by Guest Share this post Link to post
David Heffernan 2345 Posted February 17, 2021 11 minutes ago, emailx45 said: prcInitItPlease This just modifies local copies of the args, not before burning a ton of cycles boxing them in variants. Share this post Link to post
Guest Posted February 17, 2021 (edited) 7 minutes ago, David Heffernan said: This just modifies local copies of the args, not before burning a ton of cycles boxing them in variants. hey "genius" if dont like it, dont use! show us your code if you can do it! maybe that's why Delph is so bad in your opinion, it consumes tons of cycles ... These cycles that would be inert were it not for some software to use them! big! hug Edited February 17, 2021 by Guest Share this post Link to post
Guest Posted February 17, 2021 20 minutes ago, David Heffernan said: This just modifies local copies of the args, not before burning a ton of cycles boxing them in variants. hey "genius", nothing? Apparently, "genius" is just a "programming theorist", as well as "Sheldon Cooper, theoretical physicist" in "Bigbang - Theory" a teorical hug for you! Share this post Link to post
Stefan Glienke 2002 Posted February 17, 2021 Isn't it usually Sheldon who comes up with ridiculous stuff just to win an argument? 2 Share this post Link to post