【问题标题】:Read low pointer bit in way that could *probably* work on as many systems as possible以可以*可能*在尽可能多的系统上工作的方式读取低指针位
【发布时间】:2016-01-10 09:52:01
【问题描述】:

似乎指针的低位为 0 或多或少是可移植的 (可移植显然并不意味着“标准”,但人们可以摆脱它并且可以在某些方面使用它来获得一些优势在某些情况下,希望通过编译开关禁用)

想弄巧成拙的项目都用过,倒数第二位运气不太好:

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

但是假设一个人不想仅仅将一点数据插入到已知类型的指针中。你希望你能做的是使用低位 0 来允许指针类型作为终止符执行“双重任务”。

所以你的物品看起来像这样:

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...]

因此,您将摆脱“沉没成本”(假设数据结构中的一些内部管理指针用于其他目的)为您终止。


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

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

...然后使用它,如果它有帮助的话。排除char*s 很好,因为它们当然不会计算在内。


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

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

想象一下,为您的案例付出努力是值得的,因为它会减少大量数据。如果人们在编译时发现该技巧在某处不起作用,那么您有备用方案......#ifdef 可以使用完整尺寸的项目作为终止符并浪费额外的空间。所以想知道是否有任何技巧可以让这个明显违反标准的技巧更有可能在更多系统上工作。

【问题讨论】:

  • 没有“最不便携”的方式。不需要对齐的架构,或者如果您使用packedstructs,则 LSB 根本不可用。除非你有真正的内存限制,否则额外的代码开销是不值得的。如果你有内存问题,你有一个特定的系统。这仍然远远超出了 C 标准。
  • @Olaf 我不知道你是否特别说这种情况比链接帖子中的可能性要小得多......例如引用 “在“理论”中:据我所知,这是未定义的行为。在“现实”中:它可以在日常 x86/x64 机器上工作,也可能在 ARM 上工作?” 我的问题更多关于“现实”方面;特别是因为如果环境的细节没有解决,这可以(正如我所说的)在编译中被关闭,所以如果写这个(最好赔率,基本上)
  • 我声明了它不起作用的场景,包括 x86 和 ARM。在 8 位架构上,它会严重失败。
  • 不超过widewr架构。但这是一个完全不相关的主题。
  • 允许以这种方式滥用“any 指针类型”的目标是极其不现实的。例如,大多数体系结构对char 指针使用all 指针位。因此,至少,您只能对指向其地址保证低位为 0 且不可移植的数据类型的指针执行此操作。

标签: c pointers c89 termination type-punning


【解决方案1】:

(自我回答以提供更多信息并让人们发现我的替代方案的任何潜在问题。)

所以想知道是否有任何提示可以使这种明显违反标准的技巧更有可能在更多系统上工作。


提示一(根据 cmets)是如果您可能找到其他方法,请不要这样做。

例如,“你”提到了这个布局:

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

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

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

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


提示二适用于上述替代方案 - 并且可能有助于打破标准的指针版本的几率。确保在写入值时通过“别名”指针,不要直接通过.-> 写入字段。

编译器不需要确保字段上的两个不同结构之间的内存一致性,因为它们是相同的类型。假设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。如果这个技巧有效,它只能通过利用指针的非限制行为来实现。)

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

【讨论】:

    猜你喜欢
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 2014-07-11
    • 2011-04-23
    • 1970-01-01
    • 2019-08-22
    • 1970-01-01
    • 2011-09-13
    相关资源
    最近更新 更多