便携式标记指针

有没有一种可移植的方法来实现C / C ++中的标记指针,就像一些在平台和编译器中工作的文档化宏? 或者当你标记你的指针时,你处于危险之中? 如果存在这样的辅助函数/宏,它们是任何标准的一部分还是仅作为开源库提供?

对于那些不知道标记指针但感兴趣的人来说,这是一种在普通指针中存储一些额外数据的方法,因为在大多数架构中,指针中的一些位总是0或1,所以你保留你的标志/类型/提示这些额外的位,并在您想要使用指针取消引用某些实际值之前删除它们。

const int gc_flag = 1; const int flag_mask = 7; // aka 0b00000000000111, because on some theoretical CPU under some arbitrary OS compiled with some random compiler and using some particular malloc last three bits are always zero in pointers. struct value { void *data; }; struct value val; val.data = &data | gc_flag; int data = *(int*)(val.data & flag_mask); 

https://en.wikipedia.org/wiki/Pointer_tagging

您可以通过保证对象与1 << N倍数对齐来获取地址的最低N位以供个人使用。 这可以通过不同的方式独立于平台实现(对于基于堆栈的对象的alignasaligned_storage或对于动态对象的std::aligned_alloc ),具体取决于您想要实现的目标:

 struct Data { ... }; alignas(1 << 4) Data d; // 4-bits, 16-byte alignment assert(reinterpret_cast(&d) % 16 == 0); // dynamic (preferably with a unique_ptr or alike) void* ptr = std::aligned_alloc(1 << 4, sizeof(Data)); auto obj = new (ptr) Data; ... obj->~Data(); std::free(ptr); 

你通过丢弃大量内存来付出代价,随着所需的位数逐渐增长。 此外,如果您计划连续分配许多此类对象,则对于相对较小的arrays,此类arrays将不适合处理器的高速缓存行,这可能会大大降低程序的速度。 因此,该解决方案不是按比例的

如果您确定要传递的地址始终使用某些位,则可以使用uintptr_t作为传输类型。 这是一个整数类型,以预期的方式映射到指针(并且将无法存在于不提供此类可能映射的模糊平台上)。

没有任何标准宏,但你可以轻松自己滚动。 代码(没有宏)可能看起来像:

 void T_func(uintptr_t t) { uint8_t tag = (t & 7); T *ptr = (T *)(t & ~(uintptr_t)7); // ... } int main() { T *ptr = new T; assert( ((uintptr_t)ptr % 8) == 0 ); T_func( (uintptr_t)ptr + 3 ); } 

这可能会破坏涉及跟踪指针使用的编译器优化。

好吧,GCC至少可以计算位字段的大小,因此您可以跨平台获得可移植性(我没有可用于测试的MSVC)。 您可以使用intptr_t指针和标记打包到intptr_t ,并且intptr_t保证能够保存指针。

 #include  #include  #include  #include  #include  struct tagged_ptr { intptr_t ptr : (sizeof(intptr_t)*CHAR_BIT-3); intptr_t tag : 3; }; int main(int argc, char *argv[]) { struct tagged_ptr p; p.tag = 3; p.ptr = (intptr_t)argv[0]; printf("sizeof(p): %zu <---WTF MinGW!\n", sizeof p); printf("sizeof(p): %lu\n", (unsigned long int)sizeof p); printf("sizeof(void *): %u\n", (unsigned int)sizeof (void *)); printf("argv[0]: %p\n", argv[0]); printf("p.tag: %" PRIxPTR "\n", p.tag); printf("p.ptr: %" PRIxPTR "\n", p.ptr); printf("(void *)*(intptr_t*)&p: %p\n", (void *)*(intptr_t *)&p); } 

得到:

 $ ./tag.exe sizeof(p): zu <---WTF MinGW! sizeof(p): 8 sizeof(void *): 8 argv[0]: 00000000007613B0 p.tag: 3 p.ptr: 7613b0 (void *)*(intptr_t*)&p: 60000000007613B0 

我已将标记放在顶部,但更改结构的顺序会将其置于底部。 然后将要存储的指针右移3,将实现OP的用例。 可能会使访问宏变得更容易。

我也有点像结构,因为你不能意外取消引用它,就好像它是一个普通的指针。