【问题标题】:Does going through uintptr_t bring any safety when casting a pointer type to uint64_t?将指针类型转换为 uint64_t 时,通过 uintptr_t 是否会带来任何安全性?
【发布时间】:2022-01-04 02:00:10
【问题描述】:

请注意,从语言律师的角度来看,这纯粹是一个学术问题。这是关于完成转换的理论上最安全的方法。

假设我有一个void*,我需要将它转换为一个 64 位整数。原因是该指针保存了错误指令的地址。我希望将此报告给我的后端以进行记录,并且我使用固定大小的协议 - 所以我正好有 64 位可用于地址。

演员阵容当然是由实现定义的。我知道我的平台(64 位 Windows)允许这种转换,所以实际上只需 reinterpret_cast<uint64_t>(address) 就可以了。

但我想知道:从理论上讲,首先转换为uintptr_t 是否更安全?即:static_cast<uint64_t>(reinterpret_cast<uintptr_t>(address))https://en.cppreference.com/w/cpp/language/reinterpret_cast 说(强调我的):

与 static_cast 不同,但与 const_cast 类似,reinterpret_cast 表达式不会编译为任何 CPU 指令(在整数和指针之间转换或在指针表示取决于其类型的晦涩架构中除外)。 p>

所以,理论上,指针表示并没有被定义为特别的东西;从指针指向uintptr_t理论上可能会执行某种转换,以使指针可表示为整数。之后,我强行提取低 64 位。而直接转换为uint64_t 不会触发上述转换,因此我会得到不同的结果。

我的解释是否正确,或者这两种类型在理论上也没有任何区别?

FWIW,在 32 位系统上,显然扩大到无符号 64 位的转换可以符号扩展,如 this case。但是在 64 位上我不应该有这个问题。

【问题讨论】:

  • DS9K 上的void* 具有 8 位堆标记、56 位段标记、64 位低访问、64 位高访问和 64 位偏移。那些 256 位在 64 位整数中不太适合。它们确实适合 256 位 uintptr_t。 YMMV 基于您正在考虑的平台。
  • "原因是该指针保存了错误指令的地址;" --> 至少在 C 语言中(我认为适用于 C++),void* 对于 对象。指向函数(或函数中的地址)的指针可能需要比void * 更宽的内容。所以开始的前提是错误的——甚至在尝试转换为整数之前。我怀疑是否有任何规范支持指令地址。代码在规范之外的实现定义区域中。最好“将其报告给我的后端以进行记录”将其报告为指针,而不是整数。
  • @chux-ReinstateMonica 有条件地支持 C++ 中的函数指针,并且唯一保证(如果支持)转换为 void* 并返回会产生相同的函数指针值。但我认为,一旦您拥有void*,就应该应用将void* 转换为整数的规则。 eel.is/c++draft/expr.reinterpret.cast#8 当然一般的指令地址是没有定义的。
  • @user17732522 谢谢。如果可能的话,似乎问题应该是直接将函数指针(或指令地址)转换为某个宽整数,而不是通过void *。 “指针可以显式转换为任何大到足以容纳其类型的所有值的整数类型。映射函数是实现定义的。”
  • @chux-ReinstateMonica 在这种情况下,操作系统将指令地址作为void*docs.microsoft.com/en-us/windows/win32/api/winnt/… 提供给您。当然,这是我们进入特定实施领域的地方。

标签: c++ language-lawyer


【解决方案1】:

您对那个(令人震惊的非正式,对于 cppreference)段落的解析过于紧密。它试图理解的只是其他类型转换可能涉及转换操作(float/int 的东西、符号扩展、指针调整),而 reinterpret_cast 具有直接重用位的味道。

如果将指针重新解释为整数并且整数类型不够大,则会出现编译时错误。如果它足够大,你就可以了。 uintptr_t 没有什么神奇之处,除了保证(如果存在)它足够大,如果你重新转换为更小的类型,无论如何你都会失去它。要么 64 位就足够了,在这种情况下,无论哪种类型都可以得到相同的保证,或者不是,无论你做什么,你都会被搞砸。如果您的实现愿意在 reinterpret_cast 中做一些奇怪的事情,这可能会产生与(比如说)bit_cast 不同的结果,那么这两种方法都不会保证也不会阻止。

当然,这并不是说两者可以保证相同。考虑具有 32 位指针的 DS9k 架构,其中指向 uint64_t 的指针的 reinterpret_cast 导致指针位在低字和高字中重复。如果你直接进入 uint64_t,你会得到两个副本,如果你进入 32 位 uintptr_t,则上半部分会得到零。在那种情况下,哪一个是“正确的”将是个人意见的问题。

【讨论】:

  • “如果将指针重新解释为整数并且整数类型不够大,则会丢失信息。”:这样的强制转换将是错误的。
  • 好点 - 当您将指针指向整数时会检查宽度(尽管不是相反)。
  • 我认为还有eel.is/c++draft/expr.reinterpret.cast#5 需要考虑以防万一尝试将整数转换回指针。仅当直接使用相同整数类型的 reinterpret_cast 的值时,才能保证有效。
  • 我明白了,感谢您解决这个问题。所以你最后描述的情况几乎就是stackoverflow.com/questions/42178107/…中发生的事情,不是吗?由于某种原因,实现决定在转换时对指针进行符号扩展。
  • 是的,但更明显的问题。正如@user17732522 提到的,不能保证双重转换作为返回指针的往返正常工作。考虑使用 uint64_t 的高位字实现转换的情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-27
  • 1970-01-01
  • 2011-04-03
  • 2020-12-22
  • 1970-01-01
相关资源
最近更新 更多