AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Funny local variable behavior

 
Reply to topic    AutoHotkey Community Forum Index -> Bug Reports
View previous topic :: View next topic  
Author Message
fincs



Joined: 05 May 2007
Posts: 1162
Location: Seville, Spain

PostPosted: Thu Dec 03, 2009 4:38 pm    Post subject: Funny local variable behavior Reply with quote

Just run this (I understand it's for performance but at least it should be documented):
Code:
Loop, 2
   func()

func()
{
   ; Note no static bugvar!
   static switchvar = 0
   if switchvar
   {
      ; Make the variable show its hidden contents
      NumPut(Asc("H"), bugvar, 0, "UChar")
      MsgBox % bugvar
   }else ; This function was ran for the first time
   {
      ; Initialize the variable
      bugvar = _idden variable contents reappear!
      switchvar = 1
   }
}

_________________
fincs
Get SciTE4AutoHotkey v3.0.00 (Release Candidate)
[My project list]
Back to top
View user's profile Send private message
jaco0646



Joined: 07 Oct 2006
Posts: 3113
Location: MN, USA

PostPosted: Thu Dec 03, 2009 5:52 pm    Post subject: Reply with quote

I get a blank message box (running the latest AHK version).
Back to top
View user's profile Send private message Visit poster's website
MasterFocus



Joined: 08 Apr 2009
Posts: 3035
Location: Rio de Janeiro - RJ - Brasil

PostPosted: Thu Dec 03, 2009 6:17 pm    Post subject: Reply with quote

jaco0646 wrote:
I get a blank message box (running the latest AHK version).

Same here.
_________________
"Read the manual. Read it again. Search the forum.
Try something before asking. Show what you've tried.
"

Antonio França
My stuff: Google Profile
Back to top
View user's profile Send private message Visit poster's website
fincs



Joined: 05 May 2007
Posts: 1162
Location: Seville, Spain

PostPosted: Thu Dec 03, 2009 8:43 pm    Post subject: Reply with quote

I'm also using the vanilla version of AutoHotkey v1.0.48.05 (no Unicode/_L stuff involved). Am I missing something here?

_________________
fincs
Get SciTE4AutoHotkey v3.0.00 (Release Candidate)
[My project list]
Back to top
View user's profile Send private message
Roland



Joined: 08 Jun 2006
Posts: 307

PostPosted: Thu Dec 03, 2009 9:43 pm    Post subject: Reply with quote

It's the #NoEnv directive that's causing this to happen Shocked
Back to top
View user's profile Send private message
jaco0646



Joined: 07 Oct 2006
Posts: 3113
Location: MN, USA

PostPosted: Thu Dec 03, 2009 11:12 pm    Post subject: Reply with quote

Confirmed.
Back to top
View user's profile Send private message Visit poster's website
MasterFocus



Joined: 08 Apr 2009
Posts: 3035
Location: Rio de Janeiro - RJ - Brasil

PostPosted: Fri Dec 04, 2009 2:05 am    Post subject: Reply with quote

#NoEnv wrote:
A future version of AutoHotkey might make this behavior the default.

Neutral
_________________
"Read the manual. Read it again. Search the forum.
Try something before asking. Show what you've tried.
"

Antonio França
My stuff: Google Profile
Back to top
View user's profile Send private message Visit poster's website
jballi



Joined: 01 Oct 2005
Posts: 748
Location: Texas, USA

PostPosted: Fri Dec 04, 2009 5:17 am    Post subject: Reply with quote

Unless I'm missing something, this is a documented condition (feature) and it includes local variables that are defined within functions. fincs just exposed a means of getting at memory that has not been released. You can duplicate this "problem" with global variables. Just create a global variable to something with a length that is less than 64 bytes, set the variable to null, and then use fincs' trick for reallocating the memory. Something like this:

Code:
#NoEnv
gVar=_idden va0iable con0ents reap0ear!56789012345678901234567890123
VarSetCapacity(gVar,0)
NumPut(Asc("H"),gVar,0,"UChar")
MsgBox % gvar

For all (OK, most) practical purposes, the variable does not exist when you use it again. The string length is zero and if you test the variable, i.e. If MyVariable, it will test as False. All string commands such as StringLeft, StringLower StringMid, etc. are not affected.

Them be my thoughts...
Back to top
View user's profile Send private message Send e-mail
infogulch



Joined: 27 Mar 2008
Posts: 649

PostPosted: Fri Dec 04, 2009 6:02 am    Post subject: Reply with quote

What's happening here is that the first character of the discarded variable is set to NULL, and since ahk's strings are null-terminated this essentially makes the string a length of 0. By overwriting the null character with another character, the remaining string is visible again until the next null character. (which is now at the end of the first string because, as jballi mentioned, it's length is < 64 bytes and it is never really freed in the true sense)

I think.

Razz

Edit:

"All string commands such as StringLeft, StringLower StringMid, etc. are not affected."

This, too can be explained by the fact that ahk keeps a separate record of the length of the string in a given variable, which is set to 0 when cleared. All the string commands refer to this value for validating values, which can save it a lot of time vs going through the entire string to find the null char every time someone uses a string command.
_________________
Scripts - License
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Fri Dec 04, 2009 8:51 am    Post subject: Reply with quote

What do you want to happen when you use NumPut on an "uninitialized" variable? Variables are not guaranteed to be zero-initialized unless you specifically do it yourself.

Roland wrote:
It's the #NoEnv directive that's causing this to happen
If #NoEnv is not present, AutoHotkey looks for an environment variable when the variable's internal "length" field is 0. Even if there is no environment variable, it does not use the variable's actual contents in that case. #NoEnv itself is not in any way responsible for the effect demonstarted by fincs and jballi.

OT: #NoEnv is important for performance because without it, every time you evaluate an empty variable, the script has to search for an environment variable by the same name. Even having this mechanism present (with or without #NoEnv) negatively affects performance of all scripts, though perhaps not noticably.
Back to top
View user's profile Send private message Visit poster's website
fincs



Joined: 05 May 2007
Posts: 1162
Location: Seville, Spain

PostPosted: Fri Dec 04, 2009 4:19 pm    Post subject: Reply with quote

Lexikos wrote:
What do you want to happen when you use NumPut on an "uninitialized" variable? Variables are not guaranteed to be zero-initialized unless you specifically do it yourself.

Just look at this case (I only want it to be documented!):
Code:
DecodeBase64(data){
   DllCall("crypt32\CryptStringToBinaryA", "uint", &data, "uint", 0, "uint", 1, "uint", 0, "uint*", len, "uint", 0, "uint", 0)
   VarSetCapacity(d_data, len)
   DllCall("crypt32\CryptStringToBinaryA", "uint", &data, "uint", 0, "uint", 1, "str", d_data, "uint*", len, "uint", 0, "uint", 0)
   ; This function doesn't work properly if you don't uncomment the NumPut() line.
   ; The scripter may be puzzled to see remnants from the last function result if
   ; the base64-encoded data doesn't contain a termination NULL (the most common case):
   ;NumPut(0, d_data, len, "UChar")
      
   return d_data
}

_________________
fincs
Get SciTE4AutoHotkey v3.0.00 (Release Candidate)
[My project list]
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Fri Dec 04, 2009 6:07 pm    Post subject: Reply with quote

I think you missed my point.

"All local variables start of blank each time the function is called," but this only means a null-terminator is set at the beginning of the variable. Any content following the null-terminator is initially undefined, so relying on its value is asking for trouble. Documenting that local variables might "retain" their contents between calls would be counterproductive since the contents are still undefined in other situations.

On the other hand, it might be worth clarifying how variables are "initialized". VarSetCapacity hints at it:
VarSetCapacity wrote:
FillByte: This parameter is normally omitted, in which case the memory of the target variable is not initialized (instead, the variable is simply made blank as described above).

Local variables are freed or cleared when the function returns in the same way as VarSetCapacity(var,0), so the following applies:
VarSetCapacity wrote:
For performance reasons, freeing a variable whose previous capacity was between 1 and 63 might have no effect because its memory is of a permanent type.
However, it only might have no effect, so you can't rely on it. If d_data extends beyond 63 bytes, it becomes non-permanent and will be freed when the function returns. Undocumented: In all subsequent calls it will receive non-permanent memory. VarSetCapacity will allocate exactly the length requested (plus 1) and will null-terminate in the correct position every time. This behaviour can be forced:
Code:
DecodeBase64(data){
    static s
    if !s  ; To demonstrate it only needs to be done once.
        s := VarSetCapacity(d_data,64), VarSetCapacity(d_data,0)
    ...
}

Btw, even if the variable's contents are freed, there's no guarantee that the same memory won't be allocated to that variable (or a different variable) at some later point. A variable could hypothetically even be allocated different memory with contents identical to what it had previously.
Back to top
View user's profile Send private message Visit poster's website
jaco0646



Joined: 07 Oct 2006
Posts: 3113
Location: MN, USA

PostPosted: Fri Dec 04, 2009 9:35 pm    Post subject: Reply with quote

First off, thanks to jballi, infogulch, and Lexikos. Your explanations were all very informative. I learned something today. Cool

Lexikos wrote:
What do you want to happen when you use NumPut on an "uninitialized" variable?
I think the issue is not about changing the behavior of NumPut, but about how variables should be referenced (e.g. after Numput has been used).

Lexikos wrote:
...it does not use the variable's actual contents in that case.
A variable's actual contents should never be used when its internal length field is 0. The message box should always show blank, regardless of #NoEnv.
Back to top
View user's profile Send private message Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Sat Dec 05, 2009 3:16 am    Post subject: Reply with quote

jaco0646 wrote:
A variable's actual contents should never be used when its internal length field is 0. The message box should always show blank, regardless of #NoEnv.
If the internal length field is inaccurate, why should it be used over the contents of the variable? It is usually disregarded in simple cases:
Code:
t = the test
NumPut(0,t,3,"uchar"), VarSetCapacity(t,-1), NumPut(Asc(" "),t,3,"uchar")
MsgBox 0, % StrLen(t), % t
Changing the behaviour to always respect the internal length field (for consistency) would require a copy to be made of the arg where ordinarily it could get away with using the string directly. In other words, the performance benefit of the internal length field could be negated.

While I was researching this, I noticed that some areas use the internal length field (indirectly) and then assume the arg is null-terminated at that position. In particular, parsing loops allocate a buffer based on the "length" of the arg, then disregard that length and do a simple string copy:
Code:
size_t space_needed = ArgLength(2) + 1;  // +1 for the zero terminator.
...
strcpy(buf, ARG2); // Make the copy.
This can cause stack or heap corruption:
Code:
VarSetCapacity(t,40000,3)
NumPut(0,t,0,"char"), VarSetCapacity(t,-1), NumPut(3,t,0,"char")
Loop, Parse, t
{}
Parsing loops use the stack for anything <= 40000 characters and the heap (malloc) otherwise.
jaco0646 wrote:
I think the issue is not about changing the behavior of NumPut, but about how variables should be referenced (e.g. after Numput has been used).
Right. I meant "after" vs "when".
Back to top
View user's profile Send private message Visit poster's website
jaco0646



Joined: 07 Oct 2006
Posts: 3113
Location: MN, USA

PostPosted: Sat Dec 05, 2009 4:03 am    Post subject: Reply with quote

Lexikos wrote:
...the performance benefit of the internal length field could be negated.
Ok, I can accept that. Performance trumps all. Smile I also was not aware that the internal length could be updated manually, which at least offers a bit of control over the behavior.
Back to top
View user's profile Send private message Visit poster's website
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Bug Reports All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group