tags:

views:

225

answers:

4

I'm curious if it's a good idea to use a union when accessing Win32 APIs that return variable length structures, to avoid manually managing the allocated memory.

Consider the following:

void displayServices(std::wostream& log, std::tr1::shared_ptr<void> manager, LPENUM_SERVICE_STATUS currentServiceToDisplay, DWORD number, bool whitelist)
{
    static union //Don't care about being thread safe
    {
     QUERY_SERVICE_CONFIG svcConfig;
     unsigned char bufferSizer[8000];
    };
    for(DWORD idx = 0; idx < number; currentServiceToDisplay++, idx++)
    {
     DWORD garbage = 0;
     std::tr1::shared_ptr<void> currentServiceHandle(
      OpenService(reinterpret_cast<SC_HANDLE>(manager.get()), currentServiceToDisplay->lpServiceName, SERVICE_QUERY_CONFIG),
      serviceCloser);
     QueryServiceConfig(reinterpret_cast<SC_HANDLE>(currentServiceHandle.get()),
          &svcConfig, 8000, &garbage);
     //Use svcConfig for something here.
    }
}

Are there any major issues with this?

+1  A: 

Other than using a lot more stack space than necessary, no.

Update:

Yeah, totally missed the static keyword, sorry.

It will work fine as long as you don't need to use it from multiple threads, and there's no chance of the function being re-entrant.

Tim Sylvester
`static` results in the union not being put on the stack though, right?
Billy ONeal
"Using up a lot more stack space than necessary" may be a lot better than dynamically allocating memory, in some contexts.
Tom
+2  A: 

There aren't any major issues that I know of. But rather than using a union, I think just declaring the buffer and then using pointers to get the structure you want for it will be more clear.

I guess what I don't really get from your example is what the point of doing it this way is.

Edit: What I mean is, do this:

void displayServices(std::wostream& log, std::tr1::shared_ptr<void> manager, LPENUM_SERVICE_STATUS currentServiceToDisplay, DWORD number, bool whitelist)
{
    unsigned char bufferSizer[8000];
    QUERY_SERVICE_CONFIG *pSvcConfig = reinterpret_cast<QUERY_SERVICE_CONFIG*>(bufferSizer);

    for(DWORD idx = 0; idx < number; currentServiceToDisplay++, idx++)
    {
     DWORD garbage = 0;
     std::tr1::shared_ptr<void> currentServiceHandle(
      OpenService(reinterpret_cast<SC_HANDLE>(manager.get()), currentServiceToDisplay->lpServiceName, SERVICE_QUERY_CONFIG),
      serviceCloser);
     QueryServiceConfig(reinterpret_cast<SC_HANDLE>(currentServiceHandle.get()),
          pSvcConfig, sizeof(bufferSizer), &garbage);
     //Use pSvcConfig for something here.
    }
}

To me, at least, this is more clear.

SoapBox
Can't do that. The structure size is not defined until runtime. If you take a look at the QUERY_SERVICE_CONFIG structure, there are variable length strings (READ: LPTSTR) in the structure. QueryServiceConfig requires some space to store these strings in, so it asks for a variable length buffer. The structure is the beginning of the pointed memory range, and the dynamic length strings are stored after that in the buffer.
Billy ONeal
But all you're doing is adding memory on to the end here, you can do that by just defining a buffer (say 8000+sizeof(SERVICE_QUERY_CONFIG)) and then casting that to be the structure you want. it does the same thing as you do here, but it's more clear what you're trying to accomplish.
SoapBox
@BillyONeal: Sorry, but I can't make sense of your comment. What SoapBox proposed (and I did as well later) is exactly functionaly equivalent to what you did with your union, except clearer (no need for union). There can't be any arguments about any dynamic strings here. The function will use the memory after the structure to store the dynamic strings, just like you said. There's nothing in SoapBox's variant that would somehow interfere with that.
AndreyT
After seeing your edit, I agree with what you're saying. Only thing to note is possible alignment faults doing that might cause. Not an issue for x86 machines though... thanks :)
Billy ONeal
A: 

Another way, just using a static array of your base type:

void displayServices(std::wostream& log, std::tr1::shared_ptr<void> manager, LPENUM_SERVICE_STATUS currentServiceToDisplay, DWORD number, bool whitelist)
{
    static QUERY_SERVICE_CONFIG svcConfig[8000/sizeof(QUERY_SERVICE_CONFIG)]; // Or just pick an arbitrary number

    for(DWORD idx = 0; idx < number; currentServiceToDisplay++, idx++)
    {
        DWORD garbage = 0;
        std::tr1::shared_ptr<void> currentServiceHandle(
                OpenService(reinterpret_cast<SC_HANDLE>(manager.get()), currentServiceToDisplay->lpServiceName, SERVICE_QUERY_CONFIG),
                serviceCloser);
        QueryServiceConfig(reinterpret_cast<SC_HANDLE>(currentServiceHandle.get()),
                                                svcConfig, sizeof(svcConfig), &garbage);
        //Use svcConfig for something here.
    }
}
Dark Falcon
Nope. The size of the QUERY_SERVICE_CONFIG structure is dynamic. If you get just the size of the structure, QueryServiceConfig fails.
Billy ONeal
He doesn't "get just the size of the structure". He gets the size of the entire `svcConfig` array, which is a large buffer.
AndreyT
+3  A: 

One problem with your union approach is that the whole idea of using an union is rather forced and completely unnecessary. All you seem to want to do is the replace dynamic memory allocation with a local static buffer of some "large" size. Then just do it explicitly

unsigned char buffer[8000];    
QUERY_SERVICE_CONFIG *svcConfig = (QUERY_SERVICE_CONFIG *) buffer;
...
QueryServiceConfig(reinterpret_cast<SC_HANDLE>(currentServiceHandle.get()), 
  svcConfig, sizeof buffer, &garbage);
// Use `svcConfig` here, keeping in mind it is a pointer now

What you were trying to achive by using union specifically is not clear to me. Do you think that the approach with the union is somehow more elegant? I don't think so.

Update: It has been noted in one of the comments that this approach allegedly might not work with a compiler performing strict aliasing optimizations. The comment is absolutely incorrect for more than one reason. Firstly, the above approach does not rely on any aliasing-dependent behavior at all, since all access is supposed to be performed through svcConfig pointer and svcConfig pointer only. Secondly, strict aliasing optimizations are never performed if the aliased data is a character array. The language specification explicitly permits aliased access through character arrays, which is why all optimizing compilers disable their strict aliasing optimizations when a character array is involved. Any compiler that fails to do so is broken and thus not worth consideration.

AndreyT
MSVC, GCC, and ICC all recognize that type of union as explicit aliasing. Your approach is not recommended for use within a single function scope, because strict-aliasing optimizations may break it. Fortunately, because QueryServiceConfig is a separate function that won't ever get inlined, it will not break... but it's a good habit to get into using unions for this type of overlay, instead of just casting pointers.
Tom
@Tom: Incorrect. Firstly, the code above does not have any aliasing. The access is always made through `svcConfig` pointer and only that pointer. There's no need to access the `buffer` array at all, so there's no aliasing. (continued)
AndreyT
Secondly, even if there was acces to `buffer` array (i.e. even if there was aliasing there) strict aliasing optimizatins would *not* break it. It is explictly allowed by the language to reinterpret other data types as *character arrays*. All struict aliasing compilers recognize that fact and all such compilers explicitly state that strict aliasing optimizations don't apply when character arrays are involved.
AndreyT
Ah, I stand corrected. I did not realize that char-arrays had a specific exemption from the strict-aliasing rules.
Tom
Regardless, I still stand by unions for this sort of thing. I wonder, if `QUERY_SERVICE_CONFIG` has alignment requirements, is there a guarantee that `buffer` will be aligned simply due to its presence on the stack?
Tom
@Tom: That's actually a very valid and important argument. It should be noted that the method in my answer only works if the structure has no alignment requirements. Or additional steps must be taken to ensure proper alignment.
AndreyT
Or just use the union, which forces both proper size AND alignment. Thanks for a good alternate solution though :)Tom: The buffer's not on the stack ;) That's why it's declared `static`.
Billy ONeal