Search code examples
cwindowspointerswinapi

Can you pass the same pointer to SystemTimeToTzSpecificLocalTime for both input and output?


Here's the function prototype of SystemTimeToTzSpecificLocalTime:

BOOL SystemTimeToTzSpecificLocalTime(
  [in, optional] const TIME_ZONE_INFORMATION *lpTimeZoneInformation,
  [in]           const SYSTEMTIME            *lpUniversalTime,
  [out]          LPSYSTEMTIME                lpLocalTime
);

As you can see, the second and third parameters are both pointers to SYSTEMTIME structures, so at least from the point of view of static type-checking, it should be possible to pass the same pointer for both parameters.

However, the documentation does not say anything about whether doing so is safe or not.

In my testing, I found that passing the same pointer for both parameters does work as expected. The function does modify the SYSTEMTIME structure in place, and the output is correct.

Here is a simple example:

#include <windows.h>
void main() {
    SYSTEMTIME tm;
    GetSystemTime(&tm);
    SystemTimeToTzSpecificLocalTime(NULL, &tm, &tm);
    wprintf(L"Local Time: %02d:%02d:%02d\n", tm.wHour, tm.wMinute, tm.wSecond);
}

My question is: is it safe to pass the same pointer for both parameters? Or should I use a new SYSTEMTIME structure for the output?

I couldn't find any information about this in the documentation. (https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime)

Thanks!


Solution

  • Raymond Chen talks about this in his Basic ground rules for programming – function parameters and how they are used

    There are some basic ground rules that apply to all system programming, so obvious that most documentation does not bother explaining them because these rules should have been internalized by practitioners of the art to the point where they need not be expressed. In the same way that when plotting driving directions you wouldn’t even consider taking a shortcut through somebody’s backyard or going the wrong way down a one-way street, and in the same way that an experienced chess player doesn’t even consider illegal moves when deciding what to do next, an experienced programmer doesn’t even consider violating the following basic rules without explicit permission in the documentation to the contrary:

    Output buffers.

    • An output buffer cannot overlap an input buffer or another output buffer.

    (Remember, every statement here is a basic ground rule, not an absolute inescapable fact. Assume every sentence here is prefaced with “In the absence of indications to the contrary”. If the caller and callee have agreed on an exception to the rule, then that exception applies. Coming up with this was hard, in the same way it’s hard to come up with a list of illegal chess moves. The rules are so automatic that they aren’t really rules so much as things that simply are and it would be crazy even to consider otherwise. As a result, I’m sure there are other “rules so obvious they need not be said” that are missing. (For example, “You cannot terminate a thread while it is inside somebody else’s function.”)

    One handy rule of thumb for what you can do to a function call is to ask, “How would I like it if somebody did that to me?” (This is a special case of the “Imagine if this were possible” test.)

    No, you cannot expect to consider it valid to point to the same buffer. It may work. That doesn't mean it's valid. That doesn't mean it won't break in the future.

    tl;dr: This way be dragons

    See also the unrelated:

    which deals with C language rules, rather than Windows rules.

    How would I like it if somebody did that to me?

    Raymond has a good line: what if someone did that to me. Lets say I'm implementing SystemTimeToTzSpecificLocalTime

    BOOL SystemTimeToTzSpecificLocalTime(
      [in, optional] const TIME_ZONE_INFORMATION *lpTimeZoneInformation,
      [in]           const SYSTEMTIME            *lpUniversalTime,
      [out]          LPSYSTEMTIME                lpLocalTime
    )
    {
       // First things first, we'll zero out the return value. 
       // Don't want them to encounter random garbage in case something goes sideways.
       ZeroMemory(lpLocalTime, sizeof(SYSTEMTIME));
     
       // ...the remainder of the function...
    }
    

    I am free to do that because I'm free to set the lpLocalTime, because it's the entire purpose of my function.