【问题标题】:Does casting pointers to integers define a total order on pointers?将指针转换为整数是否定义了指针的总顺序?
【发布时间】:2015-08-18 04:09:52
【问题描述】:

(与my previous question相关)

在 QT 中,QMap documentation 表示:

QMap 的键类型必须提供operator<() 指定总顺序

但是,在qmap.h 中,他们似乎使用类似于std::less 的东西来比较指针:

/*
    QMap uses qMapLessThanKey() to compare keys. The default
    implementation uses operator<(). For pointer types,
    qMapLessThanKey() casts the pointers to integers before it
    compares them, because operator<() is undefined on pointers
    that come from different memory blocks. (In practice, this
    is only a problem when running a program such as
    BoundsChecker.)
*/

template <class Key> inline bool qMapLessThanKey(const Key &key1, const Key &key2)
{
    return key1 < key2;
}

template <class Ptr> inline bool qMapLessThanKey(const Ptr *key1, const Ptr *key2)
{
    Q_STATIC_ASSERT(sizeof(quintptr) == sizeof(const Ptr *));
    return quintptr(key1) < quintptr(key2);
}

他们只是将指针转换为quintptrs(这是uintptr_t 的QT 版本,即能够storing a pointer 的无符号整数)并比较结果。

以下类型指定了一个无符号整数类型,其属性是任何指向 void 的有效指针都可以转换为该类型,然后转换回指向 void 的指针,结果将与原始指针进行比较:uintptr_t

你认为qMapLessThanKey() 在指针上的这种实现好吗?

当然,整数类型有一个全序。但我认为这还不足以断定这个操作定义了指针的总顺序。

我认为只有当 p1 == p2 暗示 quintptr(p1) == quintptr(p2) 时它才是正确的,AFAIK 没有指定它。

作为这种情况的反例,想象一个使用 40 位指针的目标;它可以将指针转换为quintptr,将 40 个最低位设置为指针地址,并使 24 个最高位保持不变(随机)。这足以尊重quintptr 和指针之间的可转换性,但这并没有定义指针的总顺序。

你怎么看?

【问题讨论】:

  • 好问题。不过,我想你自己已经回答过了:从指针到整数的转换每次都会产生不同的值(想象to_int(void * p) { return to_int32(p) + rand() &lt;&lt; 40; }
  • 理论上没问题,但有人知道这样的平台吗?
  • 谈到您的示例,如果指针是 40 位但无符号长整数是 64 位,那么断言将触发。
  • @NathanOliver 好点:)
  • @marom 编译器在 x86 上用于实模式和保护模式 segmented memory model。在这种情况下,您完全有标准的限制。

标签: c++ qt pointers undefined-behavior partial-ordering


【解决方案1】:

我认为你不能假设指针有一个总顺序。标准为指向 int 转换的指针提供的保证相当有限:

5.2.10/4:指针可以显式转换为任何大到足以容纳它的整数类型。映射函数为 实现定义。

5.2.10/5:整数类型或枚举类型的值可以显式转换为指针。转换为整数的指针 足够大小 (...) 并返回相同的指针类型将具有 其原值;指针和整数之间的映射是 否则由实现定义。

从实用的角度来看,大​​多数主流编译器都会将指针按位转换为整数,你就会得到一个全序。

理论问题:

但这不能保证。它可能不适用于过去的平台 (x86 real and protected mode)、奇异的平台(嵌入式系统?),以及 - 谁知道 - 在某些未来的平台 (?) 上。

以8086的segmented memory为例:真实地址由段(例如数据段的DS寄存器,堆栈段的SS......)和offest的组合给出:

Segment:   XXXX YYYY YYYY YYYY 0000    16 bits shifted by 4 bits
Offset:    0000 ZZZZ ZZZZ ZZZZ ZZZZ    16 bits not sifted
           ------------------------
Address:   AAAA AAAA AAAA AAAA AAAA    20 bits address                      

现在假设编译器将指针转换为 int,只需进行地址数学运算并将 20 位放入整数中:您的安全并有一个总顺序。

但另一种同样有效的方法是将段存储在高 16 位上,将偏移量存储在低 16 位上。事实上,这种方式将显着促进/加速将指针值加载到 cpu 寄存器中。

这种方法符合标准 c++ 要求,但每个地址可以由 16 个不同的指针表示:您的总订单丢失了!!

**订单有替代品吗? **

可以想象使用指针算法。对同一数组中元素的指针算法有严格的限制:

5.7/6:当两个指向同一个数组对象的元素的指针相减时,结果是两者下标之差 数组元素。

并且下标是有序的。

数组最多可以有size_t 个元素。因此,天真地,如果sizeof(pointer) &lt;= sizof(size_t) 可以假设采用任意引用指针并进行一些指针算术应该导致总顺序。

不幸的是,这里的标准也是非常谨慎的:

5.7.7:对于加法或减法,如果表达式 P 或 Q 具有“指向 cv T 的指针”类型,其中 T 不同于 cv-unqualified 数组元素类型,行为未定义。

所以指针算术也不能解决任意指针的问题。再次回到分段内存模型,有助于理解:数组最多可以有 65535 个字节以完全适合一个段。但是不同的数组可以使用不同的段,因此指针算术对于总顺序也不可靠。

结论

标准中有一个关于指针和内部值映射的微妙说明:

对于知道地址的人来说,这并不奇怪 底层机器的结构。

这意味着必须可以确定总订单。但请记住,它将是不可移植的。

【讨论】:

    【解决方案2】:

    标准保证将指针转换为uintptr_t 将产生一些无符号类型的值,如果转换为原始指针类型,将产生原始指针。它还要求任何指针都可以分解为unsigned char 值的序列,并且使用这样的unsigned char 值序列来构造指针将产生原始值。然而,这两种保证都不会禁止实现在指针类型中包含填充位,也不会保证要求填充位以任何一致的方式表现。

    如果代码避免存储指针,而是将每个从malloc 返回的指针转换为uintptr_t,然后根据需要将这些值转换回指针,那么生成的uintptr_t 值将形成一个排名。排名可能与创建对象的顺序没有任何关系,也与它们在内存中的排列无关,但它会是一个排名。但是,如果任何指针多次转换为 uintptr_t,则结果值可能会完全独立地进行排名。

    【讨论】:

      猜你喜欢
      • 2020-05-25
      • 1970-01-01
      • 1970-01-01
      • 2021-02-13
      • 1970-01-01
      • 2019-08-21
      • 1970-01-01
      相关资源
      最近更新 更多