Jump to content
A.M. Hoornweg

Quickly zero all local variables?

Recommended Posts

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

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 by Arnaud Bouchez

Share this post


Link to post

Slightly worrysome that there was no warning. Is it complicated to reproduce it and report it?

Share this post


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

It would be for Sydney that it is most interesting to report, me thinks.

Share this post


Link to post

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

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

I can't believe that anybody would use such code in real programs. Wouldn't it just be better to initialise your local variables?

  • Like 6

Share this post


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

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.

  • Like 2

Share this post


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

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

  • Like 1

Share this post


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

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 by Guest

Share this post


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

Share this post


Link to post
Guest
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" :classic_cheerleader:

 

a teorical hug for you!

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

×