【问题标题】:Question about unions and heap allocated memory关于联合和堆分配内存的问题
【发布时间】:2010-08-26 21:25:40
【问题描述】:

我试图使用联合来更新一个线程中的字段,然后读取另一个线程中的所有字段。在实际系统中,我有互斥锁以确保一切安全。问题在于 fieldB,在我不得不更改它之前 fieldB 被声明为字段 A 和 C。但是,由于第三方驱动程序,fieldB 必须与页面边界对齐。当我将字段 B 更改为使用 valloc 分配时,我遇到了问题。

问题: 1)有没有办法在页面边界上静态声明 fieldB 对齐。基本上和valloc做同样的事情,但是在栈上?

2) 是否可以在字段 B 或堆上分配任何字段时进行联合?不确定这是否合法。

这是我正在试验的一个简单的测试程序。除非您像字段 A 和 C 一样声明字段 B,并在公共方法中进行明显的更改,否则这是行不通的。

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

【问题讨论】:

  • 根据您对 valloc 的调用,您的意思是 fieldB 中包含的值需要页面对齐。是对的吗?无论如何,我能想到的任何解决方案都会浪费 PAGE_SIZE - 1 个字节,这会浪费很多堆栈。为什么要在栈上分配呢?
  • 你是对的,fieldB需要页面对齐。我想没有必要在堆栈上分配。我只是好奇这可能吗?当一切都在堆栈上分配时,它似乎工作。当我使用 valloc 时,我遇到了可怕的崩溃。如果你有一个所有东西都在堆上的解决方案,我会打开它。

标签: c++ memory-management unions heap-memory


【解决方案1】:

我认为您不能将 fieldB 声明为指针并获得所需的行为(假设我正确理解了这个问题)。为了使联合在您使用时有意义,您需要在联合中将其声明为数组。

我有点好奇是否可以为类重载 new 运算符以强制特定成员位于页面边界上。我很快就将重载的运算符组合在一起来做到这一点。它会导致每次分配一个完整的额外页面。它找到该字段所在位置的偏移量,然后按该量调整地址。由于分配了额外的内存(并且假设我正确地进行了数学运算),所以它是安全的。不过很丑。

它将分配偏移量填充到类中的一个成员中,以便它知道“取消偏移”指针的数量以释放它。这真是可怕的代码。作为一个实验似乎还可以,但在生产代码中不是很好。

#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

【讨论】:

    【解决方案2】:

    我不这么认为——在堆栈上对齐有点复杂,因为你需要知道你当前在哪里,分配足够的字节来消耗内存的“当前”页面,然后分配数据。在堆栈上,这不是通常的操作(即您不对齐堆栈上的任何内容)。

    但是,一些编译器具有对齐结构的编译指示,MSVC 具有“__declspec align”,您可以在其中指定数据成员的对齐方式,编译器将插入适当数量的字节。

    可以在堆上分配 1 个成员的联合 - 联合将像往常一样包含您的所有字段,但堆分配的只是一个指针。

    最后,valloc 已过时 - 您应该改用 memalign 或 posix_memalign。

    【讨论】:

    • 在上面的示例代码中,allFields 不会打印出所有字段 A-C,因此它没有按我的预期工作。我希望 allFields 打印出字段 A-C。它只在获取垃圾数据之前打印字段 A。所以它似乎对我不起作用。感谢有关使用 memalign 和 posix_memalign 的建议。直到现在都不需要页面对齐的内存。
    猜你喜欢
    • 1970-01-01
    • 2011-01-23
    • 2010-12-10
    • 1970-01-01
    • 2021-08-27
    • 2011-03-24
    • 1970-01-01
    • 2011-11-28
    • 1970-01-01
    相关资源
    最近更新 更多