以*可能*在尽可能多的系统上工作的方式读取低指针位

Read low pointer bit in way that could *probably* work on as many systems as possible

似乎指针的低位为 0 或多或少是相当可移植的 (显然可移植并不意味着 "standard",但人们可以摆脱它并且可以在某些情况下使用它有一些优势,希望可以通过编译开关禁用).

想弄的项目用过,运气差倒数第二位:

How portable is using the low bit of a pointer as a flag?

但是假设一个人不想只插入一点数据或不插入已知类型的指针。你希望你可以做的是使用那个低位为 0 来允许指针类型做 "double-duty" 作为终止符。

因此您的项目看起来像这样:

struct Item {
    uintptr_t flags; // low bit zero means "not an item"
    type1 field1;
    type2 field2;
    ...
};

然后你想要这样一种情况,其中一些物品容器看起来像这样:

[(flags field1 field2...) (flags field1 field2...) some-pointer stuff stuff...]

因此,您可以使用 "sunk-cost"(假设数据结构中的一些内部管理指针用于其他目的)为您终止。


更新: 更清楚地说明情况:这是控制代码库和结构的地方。所以像这样使用的结构中的任何指针都可以声明为联合类型,例如:

union Maybe_Terminator_Pointer {
    uintptr_t flags;
    type1* pointer1;
    type2* pointer2;
    ...
};

...然后使用它,如果有帮助的话。排除 char*s 没问题,因为它们当然不算数。


所以这里的一个额外的类型双关问题是:用于进行终止测试的指针是一个 Item*,并且进行检查的例程不知道哪种类型的指针 some-pointer 具体来说。

我想知道什么——如果有的话——最好的赌注是能够移植和编译这样的技巧。这包括将指针转换为联合、#ifdef'ing 机器的字节顺序以及从带有位的字节中获取 char* 等。无论 more 可能起作用,如果有没有人有经验或者猜想。

想象一下,为了您的案例,削减大量数据是值得的。如果人们在编译时发现这个技巧在某处不起作用,那么你有备用方案......#ifdef 可以使用全尺寸项目作为终止符并浪费额外的 space。所以想知道是否有任何提示可以使这个明显违反标准的技巧更有可能在更多系统上工作。

(Self-answering 提供更多信息并让人们发现我的替代方案的任何潜在问题。)

So wondering if there are any tips on to make this obviously-standards-violating trick have a better chance of working on more systems.


提示一(根据评论)如果你能找到另一种方法,请不要这样做。

例如,"you"提到这个布局:

[(flags field1 field2...) (flags field1 field2...) some-pointer stuff stuff...]

但是在“stuff stuff”中有什么东西不是指针——也许是已知为偶数的无聊旧整数——你可以在哪里做同样的把戏?如果是这样,为什么不像这样重新排序:

[(flags field1 field2...) (flags field1 field2...) even-uintptr_t stuff...]

这样,当您从 Item 或非 Item 中读取 flags 时,它将是相同的类型。如果你环顾四周,而不是像指针一样不透明的东西,你可能会发现当前代码中明显的 non-opaque 数字总是偶数......例如,很多字节计数测量聚合是你可能的事情保证有 % 2 = 0.


提示二适用于上述备选方案——也许也有助于提高 standards-breaking-pointer-version 的几率。确保在写入值时通过 "aliasing" 指针,并且不要直接通过 .->.

写入字段

编译器不需要确保字段上两个不同结构之间的内存一致性,只是因为它们是相同的类型。假设 struct Auintptr_t field_a 开头,struct Buintptr_t field_b 开头,您将指向两者的指针放在同一地址。如果您这样做 some_a->field_a = value;,那么从该地址的 some_b->field_b 指针读回可能不会看到该更新,因为编译器不希望您通过 A 指针写入 B 字段。

因此通过指针进行写入。像 uintptr_t *alias = &some_a->field_a;*alias = value 之类的东西将强制与任何整数的连续读取 (!) 保持一致。 (对这种属性指针is why restrict exists的性能后果表示不满。如果这个技巧要起作用,它只能通过利用指针的non-restrict行为来实现。 )

(!) - 我认为您只需通过指针进行写入,而不是读取,但也许有人可以提供见解。