Вопрос про союзы и кучу выделенной памяти - PullRequest
4 голосов
/ 27 августа 2010

Я пытался использовать объединение, чтобы я мог обновить поля в одном потоке, а затем прочитать все поля в другом потоке. В реальной системе у меня есть мьютексы, чтобы убедиться, что все безопасно. Проблема с полем B, прежде чем мне пришлось его изменить, fieldB было объявлено как поля A и C. Однако из-за стороннего драйвера fieldB должно быть выровнено по границе страницы. Когда я изменил поле B для выделения с помощью valloc, у меня возникли проблемы.

Вопросы: 1) Есть ли способ статически объявить fieldB выровненным по границе страницы. В основном, делать то же самое, что и valloc, но в стеке?

2) Возможно ли объединение, когда поле B или какое-либо поле выделяется в куче? Не уверен, что это даже законно.

Вот простая программа тестирования, с которой я экспериментировал. Это не сработает, если вы не объявите fieldB как поля A и C и не внесете очевидные изменения в открытые методы.

#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;
}

Ответы [ 2 ]

2 голосов
/ 27 августа 2010

Не думаю, что вы можете объявить fieldB в качестве указателя и получить желаемое поведение (при условии, что я правильно понял вопрос). Чтобы объединение имело смысл при его использовании, необходимо объявить его как массив в объединении.

Мне было любопытно, можно ли перегрузить новый оператор для класса, чтобы заставить конкретного члена находиться на границе страницы. Я очень быстро собрал перегруженные операторы, чтобы сделать это. Это приводит к тому, что каждый раз выделяется целая дополнительная страница. Он находит смещение, где будет это поле, а затем корректирует адрес на эту сумму. Поскольку дополнительная память выделяется (и, если я правильно сделал математику), это будет безопасно. Хотя очень уродливо.

Он вставляет смещение выделения в член класса так, чтобы он знал величину, на которую нужно "сместить" указатель, чтобы освободить его. Это действительно страшный код. В порядке эксперимента все выглядит хорошо, но не так приятно в рабочем коде.

#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;
}

Вот пример вывода:

new 00353FA0
allocation offset 86
address of fieldB 00355000
delete 00353FA0
1 голос
/ 27 августа 2010

Я так не думаю - выравнивание в стеке немного сложнее, так как вам нужно знать, где вы находитесь в данный момент, выделить достаточно байтов, чтобы использовать «текущую» страницу памяти, а затем выделить данные. В стеке это не обычная операция (т.е. вы ничего не выравниваете в стеке).

Однако у некоторых компиляторов есть прагмы, которые будут выравнивать структуры, у MSVC есть ' __ declspec align ', где вы можете указать выравнивание элементов данных, и компилятор вставит соответствующее количество байтов.

Можно создать объединение, в котором в куче выделен 1 член - объединение будет содержать все ваши поля, как обычно, но выделенное в куче будет просто указателем.

Наконец, valloc устарел - вместо этого вы должны использовать memalign или posix_memalign.

...