views:

76

answers:

2

I was trying to use a union to so I could update the fields in one thread and then read allfields in another thread. In the actual system, I have mutexes to make sure everything is safe. The problem is with fieldB, before I had to change it fieldB was declared like field A and C. However, due to a third party driver, fieldB must be alligned with page boundary. When I changed field B to be allocated with valloc, I run into problems.

Questions: 1) Is there a way to statically declare fieldB alligned on page boundary. Basically do the same thing as valloc, but on the stack?

2) Is it possible to do a union when field B, or any field is being allocated on the heap?. Not sure if that is even legal.

Here's a simple Test program I was experimenting with. This doesn't work unless you declare fieldB like field A and C, and make the obvious changes in the public methods.

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

class Test
{
   public:
      Test(void)
      {
         // field B must be alligned to page boundary
         // Is there a way to do this on the stack???
         this->field.fieldB = (unsigned char*) valloc(10);
      };

      //I know this is bad, this class is being treated like 
      //a global structure. Its self contained in another class.
      unsigned char* PointerToFieldA(void)
      {
         return &this->field.fieldA[0];
      }

      unsigned char* PointerToFieldB(void)
      {
         return this->field.fieldB;
      }

      unsigned char* PointerToFieldC(void)
      {
         return &this->field.fieldC[0];
      }

      unsigned char* PointerToAllFields(void)
      {
         return &this->allFields[0];
      }

   private:
      // Is this union possible with field B being 
      // allocated on the heap?
      union
      {
         struct
         {
            unsigned char  fieldA[10];

            //This field has to be alligned to page boundary
            //Is there way to be declared on the stack
            unsigned char* fieldB;
            unsigned char  fieldC[10];
         } field;

         unsigned char allFields[30];
      };
};


int main()
{
   Test test;

   strncpy((char*) test.PointerToFieldA(), "0123456789", 10);
   strncpy((char*) test.PointerToFieldB(), "1234567890", 10);
   strncpy((char*) test.PointerToFieldC(), "2345678901", 10);

   char dummy[11];
   dummy[10] = '\0';

   strncpy(dummy, (char*) test.PointerToFieldA(), 10);
   printf("%s\n", dummy);

   strncpy(dummy, (char*) test.PointerToFieldB(), 10);
   printf("%s\n", dummy);

   strncpy(dummy, (char*) test.PointerToFieldC(), 10);
   printf("%s\n", dummy);

   char allFields[31];
   allFields[30] = '\0';
   strncpy(allFields, (char*) test.PointerToAllFields(), 30);
   printf("%s\n", allFields);

   return 0;
}
+1  A: 

I don't think so - aligning on the stack is a bit more complicated,as you need to know where you are currently, allocate enough bytes to consume the 'current' page of memory and then allocate the data. On the stack, that's not a usual operation (ie you don't align anything on the stack).

However, some compilers have pragmas that will align structs, MSVC has the '__declspec align' where you can specify the alignment of data members and the compiler will insert the appropriate number of bytes.

It is possible to do a union where 1 member is allocated on the heap - the union will contain all your fields as usual, but the heap-allocated one will be just a pointer.

Lastly, valloc is obsolete - you should use memalign or posix_memalign instead.

gbjbaanb
In the sample code above, allFields do not print out all the fields A-C, so it's not working as I intended it to. I wanted allFields to print out fields A-C. It only prints field A before it gets garbage data. So it doesn't seem to be working for me. Thanks for the suggestion about using memalign and posix_memalign. Never had a need for page aligned memory till now.
Dennis Miller
+1  A: 

I don't think you can declare fieldB as a pointer and get the desired behavior (assuming I am understanding the question correctly). For the union to make sense as you are using it, you need to declare it as an array in the union.

I was kind of curious if it would be possible to overload the new operator for the class to force a specific member to be on a page boundary. I very quickly kludged together overloaded operators to do that. It causes an entire extra page to be allocated each time. It finds the offset of where that field would be and then adjusts the address by that amount. Since the extra memory is allocated (and assuming I did the math correctly), it would be safe. Very ugly, though.

It stuffs the allocation offset into a member in the class so that it knows the amount to "unoffset" the pointer by to free it. It is really scary code. It seems okay as an experiment but not so nice in production code.

#define PAGE_SIZE 0x1000

class test
{
public:
   int allocoffset;
   void* operator new( size_t );
   void operator delete( void* );
    union
      {
         __declspec( align(4096)) struct
         {
            unsigned char  fieldA[10];

            //This field has to be alligned to page boundary
            //Is there way to be declared on the stack
            unsigned char  fieldB[10];
            unsigned char  fieldC[10];
         } field;

         unsigned char allFields[30];
      };
};

void* test::operator new(size_t size)
{
   // Allocate an entire extra page so we can offset it by any amount
   // less than the page size to ensure alignment of fieldB
   unsigned char *p = (unsigned char*)malloc( sizeof( test ) + PAGE_SIZE );
   uintptr_t addr;
   uintptr_t diff;

   std::cout << "new " << (void*)p << std::endl;

   // now offset the returned memory by the amount needed to align
   // fieldB on a page boundary.
   addr = (uintptr_t)p + (uintptr_t)( offsetof( test, field.fieldB ));

   diff = PAGE_SIZE - ( addr & (PAGE_SIZE - 1 ));

   p += diff;

   ((test*)p)->allocoffset = diff;

   return p;
}

void test::operator delete( void *p )
{
   // offset by appropriate amount that we allocated it by
   p = (void*)( (unsigned char*)p - ((test*)p)->allocoffset );
   std::cout << "delete " << p << std::endl;
   free(p);
}

int main()
{
   test *t;

   t = new test;

   std::cout << "allocation offset " << t->allocoffset << std::endl;
   std::cout << "address of fieldB " << (void*)&t->field.fieldB << std::endl;

   delete t;
}

Here is example output:

new 00353FA0
allocation offset 86
address of fieldB 00355000
delete 00353FA0
Mark Wilkins