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

Gle*_*aum 18 c++ optimization portability pointers

如果有一个类需要一个指针和一个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 *
Run Code Online (Sandbox Code Playgroud)

建议的方式:

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;
Run Code Online (Sandbox Code Playgroud)

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

Kei*_*son 11

只要在尝试将其用作指针之前恢复指针的低位,只要您的系统,C++实现和代码满足某些假设,它就可能 "合理"可移植.

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

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

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

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

一句话:对于大多数系统来说,你可能会放弃它.未来的架构在很大程度上是不可预测的,我可以很容易地想象你的计划打破了下一个Big New Thing.

有些事情需要考虑:

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

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

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