January 7th, 2019

The GetRegionData function fails if the buffer is allocated on the stack. Is it allergic to stack memory or something?

If you pass a NULL buffer to the Get­Region­Data function, the return value tells you the required size of the buffer in bytes. You can then allocate the necessary memory and call Get­Region­Data a second time.

DWORD bytesRequired = GetRegionData(hrgn, 0, NULL);
RGNDATA* data = (RGNDATA*)malloc(bytesRequired);
data->rdh.dwSize = sizeof(data->rdh);
DWORD bytesUsed = GetRegionData(hrgn, bytesRequired, data);

This version of the code works just fine. We call the Get­Region­Data function to obtain the number of bytes required, then allocate that many bytes, and then call Get­Region­Data again to get the bytes.

However, this version doesn’t work:

struct REGIONSTUFF
{
   ...
   char buffer[USUALLY_ENOUGH];
   ...
};

REGIONSTUFF stuff;
DWORD bytesRequired = GetRegionData(hrgn, 0, NULL);
RGNDATA* data = (RGNDATA*)(bytesRequired > sizeof(stuff.buffer) ?
                           malloc(bytesRequired) : stuff.buffer);
data->rdh.dwSize = sizeof(data->rdh);
DWORD bytesUsed = GetRegionData(hrgn, bytesRequired, data);

The idea here is that we preallocate a stack buffer that profiling tells us is usually big enough to hold the desired data. If the required size fits in our preallocated stack buffer, then we use it. Otherwise, we allocate the buffer from the heap. (Related.)

This version works fine in the case where the number of bytes required is larger than our preallocated stack buffer, so that the actual buffer is on the heap.

But this version fails (returns zero) if we decide to use the preallocated stack buffer.

Is Get­Region­Data allergic to stack memory?

No. That’s not the problem.

My psychic powers told me that the ... at the start of struct REGIONSTUFF had a total size that was not a multiple of four. The buffer member therefore was at an address that was misaligned for a RGNDATA, causing the code to run afoul of one of the basic ground rules for programming:

  • Pointers are properly aligned.

And indeed, it turns out that the members at the start of the structure did indeed have a total size that was not a multiple of four. Let’s say it went like this:

struct REGIONSTUFF
{
   HGRN hrgn;
   char name[15];
   char buffer[USUALLY_ENOUGH];
};

To fix this, you need to align the buffer the same way as a RGNDATA. One way to do this is with a union.

struct REGIONSTUFF
{
   HGRN hrgn;
   char name[15];
   union {
     char buffer[USUALLY_ENOUGH];
     RGNDATA data;
   } u;
};

REGIONSTUFF stuff;
DWORD bytesRequired = GetRegionData(hrgn, 0, NULL);
RGNDATA* data = (RGNDATA*)(bytesRequired > sizeof(stuff.u.buffer) ?
                           malloc(bytesRequired) : stuff.u.buffer);
data->rdh.dwSize = sizeof(data->rdh);
DWORD bytesUsed = GetRegionData(hrgn, bytesRequired, data);

Another way is to use an alignment annotation. The appropriate annotation varies depending on which compiler you are using.

   // Microsoft Visual C++
   __declspec(align(__alignof(RGNDATA)))
   char buffer[USUALLY_ENOUGH];

    // gcc
   char buffer[USUALLY_ENOUGH]
   __attribute__((aligned(__alignof__(RGNDATA))));

   // C++11
   alignas(RGNDATA)
   char buffer[USUALLY_ENOUGH];
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.