如何在成对结构中可移植地保存两个内存字?
How to portably save two words of memory in paired structures?
我有一个总是成对出现的数据结构struct foo
。现在,每个 struct foo
都带有一个指向其对中另一个 struct foo
的指针:
struct foo {
struct foo *other_half;
/* ... */
};
由于我的程序需要很多 (> 1'000'000) struct foo
,我很想减少每个程序的大小。有没有办法摆脱other_half
指针并通过其他方式找到一对struct foo
的另一半?
考虑一个数组 struct foo foos[2]
,其中两个 struct foo
对齐到 2 * sizeof (struct foo)
或更大。观察 foos[0]
对齐到 2 * sizeof (struct foo)
或更大,而 foos[1]
仅对齐到 sizeof (struct foo)
。您可以使用该信息来确定指向此类对齐 struct foo[2]
的随机 struct foo*
是否指向第一个或第二个成员。
要获得足够对齐的内存,请编写自定义分配器或使用 C11 aligned_alloc
函数。请注意,并不是真的需要让内存完全对齐,只需清除我们在 other_half
中测试的位就足够了。
一个函数的简单实现是找到一对 struct foo
的另一半,给定一个指向一半的指针,如下所示:
struct foo *other_half(struct foo *half) {
if ((uintptr_t)half % (2 * sizeof *half) == 0)
return half + 1;
else
return half - 1;
}
但是,如果 sizeof (struct foo)
不是 2 的幂,则此函数效率不高,因为它涉及缓慢的模运算。为了加快速度,考虑 sizeof (struct foo)
的因式分解,其形式为 2n · q。很容易看出检查 ((uintptr_t)half & (uintptr_t)1 << n) == 0
就足够了,因为 2 * sizeof (struct foo)
是 2n + 1 的倍数因此位置 0 到 n 中的位已关闭。
在编译时计算 n 有点棘手,但幸运的是我们只需要 1 << n
,它可以用一点魔法计算为 -sizeof (struct foo) & sizeof (struct foo)
:
struct foo *other_half(struct foo *half) {
if ((uintptr_t)half & -sizeof *half & sizeof *half)
return half - 1;
else
return half + 1;
}
另一种设计,技巧较少,但每个 foo
使用单字节;或位,如果您将值与其他字段打包在一起。
#include
#include
struct foo {
char Index; // can be packed together with some other field
/* ... */
};
typedef struct foo pair[2];
void init_pair(pair *p){
for(size_t i = 0; i < sizeof(*p); i++){
(*p)[i].Index = (char)(i);
}
}
struct foo *pair_index(struct foo *piece, size_t index){
return &piece[index - (size_t)piece->Index];
}
struct foo *pair_other(struct foo *piece){
return pair_index(piece, piece->Index ^ 0x01);
}
示例:
int main(int argc, char const *argv[])
{
pair p;
init_pair(&p);
struct foo *first = &(p[0]);
struct foo *second = &(p[1]);
printf("%p %p\n", pair_index(first, 0), pair_index(first, 1));
printf("%p %p\n", pair_index(second, 0), pair_index(second, 1));
printf("%p %p\n", pair_other(second), pair_other(first));
return 0;
}
我有一个总是成对出现的数据结构struct foo
。现在,每个 struct foo
都带有一个指向其对中另一个 struct foo
的指针:
struct foo {
struct foo *other_half;
/* ... */
};
由于我的程序需要很多 (> 1'000'000) struct foo
,我很想减少每个程序的大小。有没有办法摆脱other_half
指针并通过其他方式找到一对struct foo
的另一半?
考虑一个数组 struct foo foos[2]
,其中两个 struct foo
对齐到 2 * sizeof (struct foo)
或更大。观察 foos[0]
对齐到 2 * sizeof (struct foo)
或更大,而 foos[1]
仅对齐到 sizeof (struct foo)
。您可以使用该信息来确定指向此类对齐 struct foo[2]
的随机 struct foo*
是否指向第一个或第二个成员。
要获得足够对齐的内存,请编写自定义分配器或使用 C11 aligned_alloc
函数。请注意,并不是真的需要让内存完全对齐,只需清除我们在 other_half
中测试的位就足够了。
一个函数的简单实现是找到一对 struct foo
的另一半,给定一个指向一半的指针,如下所示:
struct foo *other_half(struct foo *half) {
if ((uintptr_t)half % (2 * sizeof *half) == 0)
return half + 1;
else
return half - 1;
}
但是,如果 sizeof (struct foo)
不是 2 的幂,则此函数效率不高,因为它涉及缓慢的模运算。为了加快速度,考虑 sizeof (struct foo)
的因式分解,其形式为 2n · q。很容易看出检查 ((uintptr_t)half & (uintptr_t)1 << n) == 0
就足够了,因为 2 * sizeof (struct foo)
是 2n + 1 的倍数因此位置 0 到 n 中的位已关闭。
在编译时计算 n 有点棘手,但幸运的是我们只需要 1 << n
,它可以用一点魔法计算为 -sizeof (struct foo) & sizeof (struct foo)
:
struct foo *other_half(struct foo *half) {
if ((uintptr_t)half & -sizeof *half & sizeof *half)
return half - 1;
else
return half + 1;
}
另一种设计,技巧较少,但每个 foo
使用单字节;或位,如果您将值与其他字段打包在一起。
#include
#include
struct foo {
char Index; // can be packed together with some other field
/* ... */
};
typedef struct foo pair[2];
void init_pair(pair *p){
for(size_t i = 0; i < sizeof(*p); i++){
(*p)[i].Index = (char)(i);
}
}
struct foo *pair_index(struct foo *piece, size_t index){
return &piece[index - (size_t)piece->Index];
}
struct foo *pair_other(struct foo *piece){
return pair_index(piece, piece->Index ^ 0x01);
}
示例:
int main(int argc, char const *argv[])
{
pair p;
init_pair(&p);
struct foo *first = &(p[0]);
struct foo *second = &(p[1]);
printf("%p %p\n", pair_index(first, 0), pair_index(first, 1));
printf("%p %p\n", pair_index(second, 0), pair_index(second, 1));
printf("%p %p\n", pair_other(second), pair_other(first));
return 0;
}