【问题标题】:Is it safe to cast an int to void pointer and back to int again?将 int 强制转换为 void 指针并再次返回 int 是否安全?
【发布时间】:2011-04-03 20:12:40
【问题描述】:

在 C 和/或 C++ 中:将 int 强制转换为 void 指针并再次返回 int 是否安全?

基于问题“C++: Is it safe to cast pointer to int and later back to pointer again?”。

【问题讨论】:

  • 我希望人们会选择一种语言来提出这样的问题。您不会看到有人问:“如何在 Java 或 C++ 中检查 null 的引用?”为什么人们坚持要问这些关于 C 和 C++ 的问题?他们都使用 { 所以必须相同?
  • 嗯....在这种情况下,C 和 C++ 在我所使用的实现中是相同的,但我自己的烦恼都是一样的。
  • 我不认为它们是一样的。你需要考虑C中的指针大小吗?在 C++ 中,您有各种不同类型的指针,它们的大小可以不同,而 void* 大到可以容纳任何指针。因此,将 int 转换为 void* 与将 int 转换为 int* 或其他任何内容相比,不太可能丢失信息。我不记得在 C 中是这种情况,但我承认自从我还是新手以来就没有使用过它。
  • @Noah Roberts:你错了。在 C++ 中,您可以拥有不保证适合 void* 的指向成员的指针。这忽略了标准无法保证的实际新自定义指针类型。
  • 是的,我说错了。在 C++ 中,任何 T* 都可以放入 void* 中,而并非所有 T1* 都可以放入 T2* 中,反之亦然。我不记得 C 有这个。

标签: c++ c pointers


【解决方案1】:

不一定。取决于指针的大小。为什么要这样做?

【讨论】:

  • 我看到的一个原因 - API 允许您向它传递一个 void* 通用值,它会在稍后传递给您。很高兴能够灵活地使用 int 而不是指针。更好的 API 将使用保证处理两者的类型,但这在旧代码中并不常见。
  • @Mark:在过去可能是这样,但现在uintptr_t 确实更适合这样的功能。如果平台没有uintptr_t(我不知道),那么可能根本不存在这种统一类型,无论如何都应该非常小心。
【解决方案2】:

没有。 void 指针在大小方面与任何其他指针没有什么不同。因此,它将遇到与其他指针类型完全相同类型的问题。

【讨论】:

  • 其实是(在 C++ 中)。它保证足够大以存储任何其他指针。从 T* 到 void* 并返回到 T* 的任何转换都保证会产生与您最初转换的相同的 T*。
  • @Noah,看看链接的问题。 OP 询问将int 转换为void* 并再次返回是否安全。我是说void* 并不特殊,它与将int 转换为T* 的问题完全相同。
  • 不是我的错,你没有说出你的意思。
  • 我想我明白你的意思,但我对“空指针在大小方面与任何其他指针没有什么不同”持异议。 void*特别的。
【解决方案3】:

它的实现就像上一个问题一样定义,原因相同。它不太可能导致行为不端,但仍由实现定义。

【讨论】:

  • 这是一个微妙不同的问题——这个问题问 int->void*->int,而另一个问题问 void*->int->void*。
  • 我注意到了不同之处。谢谢。
【解决方案4】:

可能在大多数现代普通机器中。

但是,我敢打赌,有些编译器或配置(例如,使用 32 位整数运算的 16 位寻址机器)并非如此。

不过,uintptr_t 可以保证两者都包含,因此如果您愿意,请使用该类型。

【讨论】:

  • 仅适用于 C99 和 C++,但不适用于 ANSI C ( C89 )。
【解决方案5】:

没有。在某些情况下,它似乎适用于特定的编译器和设置,然后两年后您花费数周时间调试某些更改,并且转换不再按预期工作。

如果您只是以不需要这种行为的方式设计代码(最好的情况是完全避免使用这种转换,最坏的情况是使用char[]),那么您就不必担心隐蔽的错误未来。

【讨论】:

    【解决方案6】:

    你为什么要这样做?

    回复 C,我对 C++ 的了解不够:不,行为未定义为将 int 转换为 void*。首先,您应该始终使用uintptr_t,如果您有这样的事情。使用int 是一种滥用行为。

    然后,如果您的uintptr_t 不是来自有效地址,C 不保证任何事情。它只能保证反过来。不要这样做。

    编辑:这里是 C99 标准的相关部分。如您所见,所有警报都会响起...

    整数可以转换为任意 指针类型。除了以前 指定,结果是 实现定义的,可能不是 正确对齐,可能不指向 引用类型的实体,以及 可能是陷阱表示

    最后一个特别尴尬,因为这意味着这样获得的指针值不能再使用,直到被覆盖:

    某些对象表示需要 不代表对象的值 类型。如果一个对象的存储值 有这样的表示并被阅读 通过一个左值表达式不 有字符类型,行为是 不明确的。 ...这样的表示是 称为陷阱表示

    【讨论】:

    • 所说的只是“一个 int 可以转换为指针类型,但不要试图取消引用它”。它没有说明以后将其转换回整数的安全性。 (如上所述,在大多数现代平台上可能是安全的,但您仍应使用 uintptr_t。)
    • @Joe:是的。特别是它说结果可能是陷阱表示,或者换句话说,该类型的值无效。尝试访问包含陷阱表示的变量(任何类型)是未定义的行为。我也会编辑这个。
    • 至于“为什么要这样做?”:例如,如果您使用 pthread_create() 启动一个线程并且想要/需要只将一个 int 参数传递给该线程,那么它很诱人将该 int 转换为 void* 指针并通过 arg 参数将其传递给 start_routine。但遗憾的是,虽然它可以在最常见的架构中工作,但并不安全。
    • 我不是专家,但标准文本说“......并且由 LVALUE”表达式读取。那么这实际上意味着什么?如果我们有一些 void* p,然后执行 (int)p,那么这是一个 RVALUE,因此陷阱表示子句不适用?
    【解决方案7】:

    这里是一个例子,当将整数转换为指针时,将指针转换为整数可能不会产生相同的指针。

    给定一个具有 24 位地址并使用两个 16 位量来描述位置的架构。让一个量为 SEGMENT,另一个为 OFFSET。位置由符号 SEGMENT:OFFSET 指定。

    实际的 24 位(物理)地址计算方式为:

    address = segment * 16 + offset.
    

    使用这种表示法,可以有多个 SEGMENT:OFFSET 对描述相同的物理地址。

    转换为整数时,使用 32 位(无符号)量(以简化处理器中的内部计算)。问题是如何将物理地址转换为与创建物理地址相同的 SEGMENT::OFFSET。

    将整数转换为指针的通用公式是:

    offset = address & 0xFFFF; // Mask off high order bits, keep lower 16.
    segment = address >> 16;   // Shift address right 16 bits, zero fill.
    

    虽然这个新段的物理地址和偏移量与原来的 SEGMENT:OFFSET 的物理地址相同,但不能保证段和偏移量相同。

    为了优化代码,有些处理器指令在段中使用相对寻址。当 SEGMENT 值由于从物理地址的转换而改变时,这些指令可能会变得混乱。

    在这种情况下,可以将指针转换为整数。但是,强烈反对从整数转换为指针。运行时可能会出现难以调试的错误。

    额外问题:你能说出实际的架构吗?

    【讨论】:

      【解决方案8】:

      如果你的整数范围很小,你总是可以这样做:

      static const char dummy[MAXVAL];
      

      然后使用dummy+i 作为将i 编码为指针的一种方式。这是 100% 便携的。如果您只关心 99% 的便携性,您可以使用 (char *)0 + i

      【讨论】:

        猜你喜欢
        • 2011-04-03
        • 2019-12-29
        • 2013-11-18
        • 1970-01-01
        • 2011-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多