2

我最近看到一个代码库,我担心它违反了对齐约束。我已经对其进行了清理以生成一个最小的示例,如下所示。简而言之,球员是:

  • 游泳池。对于“高效”的某些定义,这是一个有效分配内存的类。保证返回与请求大小对齐的一块内存。

  • 对象列表。此类存储对象的同类集合。一旦对象的数量超过某个阈值,它就会将其内部表示从列表更改为树。Obj_list的大小是一个指针(在 64 位平台上为 8 个字节)。它的商店当然会超过这个。

  • 聚合。此类代表系统中非常常见的对象。它的历史可以追溯到早期的 32 位工作站时代,它被“优化”(在同一个 32 位时代)以使用尽可能少的空间。Aggregate可以为空,也可以管理任意数量的对象。

在此示例中,聚合项始终从Pool分配,因此它们始终对齐。此示例中Obj_list的唯一出现是Aggregate对象中的“隐藏”成员,因此它们总是使用Placement new分配。以下是支持类:

class Pool
{
public:
   Pool();
   virtual ~Pool();
   void *allocate(size_t size);
   static Pool *default_pool();   // returns a global pool
};

class Obj_list
{
public:
   inline void *operator new(size_t s, void * p) { return p; }

   Obj_list(const Args *args);
   // when constructed, Obj_list will allocate representation_p, which
   // can take up much more space.

   ~Obj_list();

private:
   Obj_list_store *representation_p;
};

这里是聚合。请注意成员声明member_list_store_d

// Aggregate is derived from Lesser, which is twelve bytes in size
class Aggregate : public Lesser
{
public:
   inline void *operator new(size_t s) {
      return Pool::default_pool->allocate(s);
   }

   inline void *operator new(size_t s, Pool *h) {
      return h->allocate(s);
   }

public:

   Aggregate(const Args *args = NULL);
   virtual ~Aggregate() {};

   inline const Obj_list *member_list_store_p() const;

protected:
   char member_list_store_d[sizeof(Obj_list)];
};

我最关心的是那个数据成员。这是初始化和访问的伪代码:

Aggregate::Aggregate(const Args *args)
{
   if (args) {
      new (static_cast<void *>(member_list_store_d)) Obj_list(args);
   }
   else {
      zero_out(member_list_store_d);
   }
}

inline const Obj_list *Aggregate::member_list_store_p() const
{
   return initialized(member_list_store_d) ? (Obj_list *) &member_list_store_d : 0;
}

您可能会建议我们将 char 数组替换为指向Obj_list类型的指针,初始化为 NULL 或类的实例。这给出了正确的语义,但只是改变了内存成本。如果内存仍然很宝贵(它可能是,这是一种 EDA 数据库表示),在Aggregate对象确实有成员的情况下,用指向Obj_list的指针替换 char 数组将花费更多的指针。

除此之外,我真的不想分心这里的主要问题,即对齐。我认为上述构造是有问题的,但在标准中找不到比对 'system/library' new的对齐行为的一些模糊讨论更多的内容。

那么,上述构造除了导致偶尔的管道停顿之外还有什么作用吗?

编辑:我意识到有一些方法可以使用嵌入式 char 数组替换该方法。最初的建筑师也是如此。他们丢弃了它们,因为内存非常宝贵。现在,如果我有理由接触该代码,我可能会更改它。

然而,我的问题是,关于这种方法固有的对齐问题,我希望人们能解决这个问题。谢谢!

4

5 回答 5

2

好的 - 有机会正确阅读它。当您将 char 数组作为 Obj_list 访问时,您遇到了对齐问题,并调用了未定义的行为。您的平台很可能会做以下三件事之一:让您侥幸逃脱,让您在运行时受到惩罚或偶尔因总线错误而崩溃。

解决此问题的便携式选项是:

  • 使用 malloc 或全局分配函数分配存储,但您认为这太昂贵了。
  • 正如 Arkadiy 所说,让你的缓冲区成为 Obj_list 成员:

    Obj_list list;
    

但你现在不想支付建设成本。您可以通过提供仅用于创建此实例的内联无操作构造函数来缓解这种情况 - 正如发布的默认构造函数所做的那样。如果你遵循这条路线,强烈考虑调用 dtor

list.~Obj_list();

在对这个存储进行新的放置之前。

否则,我认为您将留下不可移植的选项:要么依赖您的平台对未对齐访问的容忍度,要么使用您的编译器为您提供的任何不可移植的选项。

免责声明:我完全有可能错过了工会或类似的把戏。这是一个不寻常的问题。

于 2008-10-30T13:24:56.687 回答
1

编译器将根据其默认值选择对齐方式,这可能会在 GCC / MSVC 下以四字节结束。

如果存在需要特定对齐的代码(SIMD/DMA),这应该是一个问题。在这种情况下,您应该能够使用编译器指令来确保 member_list_store_d 对齐,或者将大小增加 (alignment-1) 并使用适当的偏移量。

于 2008-10-29T17:29:48.490 回答
1

如果要确保结构对齐,只需执行

// MSVC
#pragma pack(push,1)

// structure definitions

#pragma pack(pop)

// *nix
struct YourStruct
{
    ....
} __attribute__((packed));

确保聚合中 char 数组的 1 字节对齐

于 2008-10-29T23:55:29.660 回答
1

你能简单地在 Aggregate 中有一个 Obj_list 的实例吗?IOW,类似于

类聚合:公共小{...保护:Obj_list列表;};

我一定遗漏了一些东西,但我不知道为什么这很糟糕。

至于你的问题 - 它完全依赖于编译器。但是,大多数编译器默认情况下会在字边界对齐每个成员,即使成员的类型不需要以这种方式对齐以进行正确访问。

于 2008-10-30T00:07:05.317 回答
0

使用 malloc 或全局运算符 new[]分配 char 数组member_list_store_d,其中任何一个都将为任何类型提供对齐的存储。

编辑:只需再次阅读 OP - 您不想为另一个指针付费。早上再读一遍。

于 2008-10-29T23:44:10.557 回答