便携式如何使用指针的低位作为标志?

时间:2013-11-15 00:52:17

标签: c++ pointers optimization portability

例如,如果有一个类需要指针和bool。为简单起见,将在示例中使用int指针,但指针类型无关紧要,只要它指向size()大于1的内容。

使用{ bool , int *}数据成员定义类将导致类的大小是指针大小的两倍,并且浪费了大量空间

如果指针未指向char(或size(1)的其他数据),则可能低位将始终为零。该类可以使用{int *}定义,或者为方便起见:union { int *, uintptr_t }

bool是通过根据逻辑bool值设置/清除指针的低位并在需要使用指针时清除该位来实现的。

定义的方式:

struct myData
{
 int * ptr;
 bool flag;
};
myData x;

// initialize
x.ptr = new int;
x.flag = false;

// set flag true
x.flag = true;

// set flag false
x.flag = false;

// use ptr
*(x.ptr)=7;

// change ptr
x = y;                // y is another int *

建议的方式:

union tiny
{
 int * ptr;
 uintptr_t flag;
};
tiny x;

// initialize
x.ptr = new int;

// set flag true
x.flag |= 1;

// set flag false
x.flag &= ~1;

// use ptr
tiny clean=x;      // note that clean will likely be optimized out
clean.flag &= ~1;  // back to original value as assigned to ptr
*(clean.ptr)=7;

// change ptr
bool flag=x.flag;
x.ptr = y;             // y is another int *
x.flag |= flag;

这似乎是未定义的行为,但这有多便携?

4 个答案:

答案 0 :(得分:10)

只要在尝试将指针用作指针之前恢复指针的低位,就可以可能“合理地”移植,只要你的系统,你的C ++实现,以及你的代码符合某些假设。

我不一定能给你一个完整的假设清单,但不是我的头脑:

  • 您没有指向任何大小为1个字节的内容。这不包括charunsigned charsigned charint8_tuint8_t。 (假设为CHAR_BIT == 8;在具有16位或32位字节的外来系统上,可能会排除其他类型。)
  • 大小至少为2个字节的对象始终在偶数地址对齐。请注意,x86不要求这个;您可以在奇数地址访问4字节int,但速度稍慢。但编译器通常会安排将对象存储在偶数地址中。其他架构可能有不同的要求。
  • 指向偶地址的指针将其低位设置为0.

对于最后一个假设,我实际上有一个具体的反例。在Cray矢量系统(J90,T90和SV1是我自己使用过的)上,机器地址指向64位字,但Unicos下的C编译器设置CHAR_BIT == 8。字节指针在软件中实现,在一个字内的3位字节偏移存储在64位指针的其他未使用的高阶 3位中。因此,指向8字节对齐对象的指针很容易将其低位设置为1。

有Lisp实现(example使用低位2位指针存储类型标记。我模糊地回忆起这会导致移植过程中出现严重问题。

结论:对于大多数系统,您可以 侥幸逃脱。未来的架构在很大程度上是不可预测的,我很容易想象你的计划会打破下一个大新事物。

需要考虑的一些事项:

您可以将布尔值存储在类类的位向量中吗? (保持指针与位向量中相应位之间的关联留作练习)。

如果看到指针的低位设置为1,请考虑将代码添加到失败并显示错误消息的所有指针操作。使用#ifdef删除生产版本中的检查代码。如果您在某个平台上遇到问题,请在启用检查的情况下构建代码版本,看看会发生什么。

我怀疑,随着应用程序的增长(它们很少缩小),您需要存储的不只是bool和指针。如果发生这种情况,空间问题就会消失,因为无论如何你已经在使用那个额外的空间了。

答案 1 :(得分:4)

在“理论”中:据我所知,它是未定义的行为。

在“现实”中:它可以在日常的x86 / x64机器上工作,也可能在ARM上工作吗? 我无法在此之外发表声明。

答案 2 :(得分:2)

它非常便携,而且,当您接受原始指针以确保它符合对齐要求时,您可以assert。这样可以防止不可思议的未来编译器以某种方式混淆你。

唯一不这样做的原因是可读性成本和与" hacky"相关的一般维护。类似的东西。除非有明显的收获,否则我会回避它。但它有时是完全值得的。

答案 3 :(得分:0)

符合这些规则,它应该非常便携。