如何制作两种类型的正确对齐,交错元素的数组?

时间:2015-04-21 04:38:57

标签: c memory-alignment

现在,我有以下B树的代表:

#define PLUM_BTREE_MIN_COUNT  85
#define PLUM_BTREE_MAX_COUNT 170

struct bentry {
    size_t key;
    size_t value;
};

struct btree {
    size_t         count;  // number of entries being used
    struct bentry  entries [PLUM_BTREE_MAX_COUNT];
    struct btree  *children[PLUM_BTREE_MAX_COUNT + 1];
};

以下操作非常常见:

struct btree src, dest;
size_t       from, to, count;
// initialized somehow

// this manipulates elements, say, 80..100
memcpy(dest->entries + to  ,
       src ->entries + from,
       count * sizeof (struct bentry));

// and then this manipulates elements 81..101
memcpy(dest->children + to   + 1,
       src ->children + from + 1,
       count * sizeof (struct btree *));

这两个memcpy电话真让我烦恼。我希望我可以将子指针和条目存储在内存中交错,例如,像这样:

struct btree;

struct bslot {
    struct btree  *child;
    struct bentry  entry;
};

struct btree {
    size_t        count;
    struct bslot  slots[PLUM_BTREE_MAX_COUNT];
    struct btree *child;
};

如果对齐和填充从来不是问题,那么对于任何struct btree foo,都可以保证:

foo.slots[PLUM_BTREE_MAX_COUNT].child == foo.child

所以我可以使用一个memcpy来电:

memcpy( &(dest->slots[to  ].entry),
        &(src ->slots[from].entry),
        count * sizeof (struct bslot) );

但是,当然,对齐和填充有时也是一个问题。有没有办法将子指针和条目存储在内存中,保证连续子指针之间的距离始终是固定常量,如下图所示?

----------------------------------------------
| p0 | e0 | p1 | e1 | ... | pn | en | p{n+1} |
----------------------------------------------
|    |    |    |    |     |    |    |        |
w    x   w+k  x+k  w+2k   y    z   y+k      z+k  <--- addresses

3 个答案:

答案 0 :(得分:0)

我相信你想使用打包的结构。如何实现这取决于您使用的编译器。在gcc中,你通过这样定义它来创建一个打包的结构:

struct __attribute__((__packed__)) btree
{
    size_t key;
    size_t value;
}; 

这样做是告诉编译器不要在内存边界上对齐结构,这样如果你创建了这些结构的数组,那么数组的大小就是numberOfStructs * exactSizeOfStruct(我的意思是你的大小)如果你把所有struct的内部元素加起来,或者sizeof())。任何指针算法都可以正常工作。

但是,这不能在GCC之外移植,必须使用预处理器宏进行检查。

答案 1 :(得分:0)

一种解决方案是在btree上使用struct btree __attribute__((__packed__)) {...};(或类似于#ifdef __GNUC__之类的东西,但对于不同的编译器选项),或者在没有struct padding的情况下编译,尽管这取决于如何便携式您希望如此。通过将结构声明为打包,编译器不会混淆对齐。否则,编译器或ABI在其实现中必须具有“优化”。如果无法阻止编译器更改编译环境中的结构,也可以痛苦地添加一些unsigned char pad[foo]的手动填充。

参见资源:

http://www.catb.org/esr/structure-packing/

https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html

Structure padding and packing

答案 2 :(得分:0)

通常最简单是最好的,这次也不例外。 (出于某种原因,昨天我只是没有看到它。)我本来可以使用一个灵活的数组成员:

// These macros must be #ifdef'd to account for multiple
// architectures.  The following values work on my machine
// (x86-64 Linux).
#define PLUM_BTREE_NODE_SIZE    4096
#define PLUM_BTREE_MIN_COUNT      85
#define PLUM_BTREE_MAX_COUNT     170

struct btree;

struct bentry {
    size_t key;
    size_t value;
};

struct bslot {
    struct btree  *child;
    struct bentry  entry;
};

struct btree {
    size_t       count;
    struct bslot slots[];
};

然后分配了适量的内存:

struct btree *tree = malloc( PLUM_BTREE_NODE_SIZE );

然后:

  • tree->slots[i].child适用于从i0的所有PLUM_BTREE_MAX_COUNT
  • tree->slots[i].entry适用于从i0的所有PLUM_BTREE_MAX_COUNT - 1