【问题标题】:Casting pointer to an integer将指针转换为整数
【发布时间】:2021-02-13 23:58:25
【问题描述】:

在我之前问过的问题中已经提到过,将指针转换为整数类型并不是一个好习惯。有哪些例子说明这不是一个好主意?像下面这样的东西呢?为什么会被认为是不好的做法?

short first_local_int   = 44;
int second_local_int    = 92;

printf(
        "The difference between the two memory addresses (in bytes) is: %lu", 
         (unsigned long) &second_local_int - (unsigned long) &first_local_int
);

两个内存地址之间的实际差异(以字节为单位)为:2

【问题讨论】:

  • 问题是unsigned long 的大小可能与指针的大小不同。
  • 在将指针解释为数字时应该使用intptr_t。 stackoverflow.com/questions/6326338/…
  • 在我的平台上 unsigned long 是 32 位,但指针的大小是 64 位。
  • C 允许将指针转换为整数(通过强制转换)。但它明确拒绝为结果整数值定义任何特定意义。
  • 对不指向同一个对象(或过去一个元素)的指针进行算术运算也是未定义的行为。

标签: c pointers


【解决方案1】:

标准 C11(作为我手头的示例)在第 6.3.2.3 章“指针”第 5 段中说:

整数可以转换为任何指针类型。除非前面指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示。

提到的异常是关于值 0,它产生一个空指针。

第 6 段是另一种方式:

任何指针类型都可以转换为整数类型。除非前面指定,结果是实现定义的。如果结果不能以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。

每当我看到“实现定义的”或“未定义的行为”时,代码通常是不可移植的。如果您更喜欢编写好的代码,请避免使用此类结构。但是,如果您知道自己在做什么,并且如果您测试您的期望,您可能会侥幸成功。

顺便说一句,两个未指向同一个数组(或正好超过数组末尾)的指针的差异也是未定义的行为。


编辑:

同一标准的第 7.20.1.4 章“能够保存对象指针的整数类型”说:

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

intptr_t

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

uintptr_t

这些类型是可选的。

最后一句话很重要。

【讨论】:

    【解决方案2】:

    有哪些例子说明这不是一个好主意?

    这不是一个好主意,因为尽管允许指向整数转换的指针,但其结果的意义在很大程度上并未由标准指定。具体来说,

    任何指针类型都可以转换为整数类型。除了作为 之前指定的,结果是实现定义的。如果 结果不能以整数类型表示,行为 未定义。结果不必在任何值的范围内 整数类型。

    [C2018,第 6.3.2.3/6 段]

    对于任何有用的行为,这是一个非常薄弱的​​规定。在实践中,大多数从指针到整数转换中获得有用行为的程序都是通过利用其 C 实现提供的对该行为的适当定义来实现的,这是可移植性限制。

    类似以下内容的情况如何——为什么这会被认为是不好的做法?

    这将是一个见仁见智的问题。

    然而,尽管代码片段符合——甚至严格符合——标准,但在可以对这样一个孤立片段进行评估的范围内,它打印的消息对于所涉及地址之间的关系并不一定是正确的.事实上,世界的 C 模型甚至不支持不相关对象的地址之间的关系概念,除了(非)相等关系。

    【讨论】:

      【解决方案3】:

      看看异或链表:https://en.wikipedia.org/wiki/XOR_linked_list XOR 链表的一般思想是我们可以将两个指针存储在同一个内存地址中,但是在链表遍历算法中需要一对指针。它确实具有完全相同的代码在任一方向遍历列表的优势。

      最大的缺点是你的代码比它需要的更难理解。

      第二大缺点是调试器无法处理。

      如果这还不够,我提出以下缺点:如果你把它搞砸了(而且它比大多数其他事情更容易搞砸),你的代码就会变得未定义,有时你不会注意到一段时间.这种情况在 Windows 软件中发生过如此之多,以至于 64 位可执行文件的默认地址空间仍然是 2GB。 (他们最近更改了 SDK 以设置 LARGEADDRESSAWARE 标志,但二进制图像默认仍然是 no。)

      【讨论】:

      • 非常简洁,谢谢!想展示一个非常基本的例子来说明如何在 C 中使用它?
      • @samuelbrody1249:见鬼。
      • 多么有趣的想法,异或链表。我每天都学到新东西。 :-D 但是,在阅读了这篇文章之后,我更喜欢 DIFF 链表(减去而不是 XOR)。
      【解决方案4】:

      指针可以转换为整数,因为 C 2018 6.3.2.3 6 说:

      任何指针类型都可以转换为整数类型。除非前面指定,结果是实现定义的。如果结果不能以整数类型表示,则行为未定义……

      此外,注释 69 说:

      将指针转换为整数或将整数转换为指针的映射函数旨在与执行环境的寻址结构保持一致。

      注释不是标准的规范部分,但这告诉我们,对于“常规”C 实现,我们应该期望将指针转换为整数以产生指向事物的硬件内存地址,如果它适合在整数类型中。请注意,某些 C 实现是为特定目的而设计的,因此它们可能是“不规则的”。例如,可以将 C 实现设计为节省空间并使用比硬件支持的更窄的指针。

      用于指针到整数转换的正确整数类型是uintptr_t,它在<stdint.h> 中定义。这是因为 7.20.1.4 1 定义了uintptr_t 能够保存对象指针(的所有信息):

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

              uintptr_t

      在常见的现代硬件中,内存地址是“平面”地址空间中的简单整数。字节从 0 到最大值连续编号。每个内存位置对应一个数字,这个范围内的每个数字对应一个内存位置。 (但是,存在用于指定内存位置的地址这一事实并不意味着该内存位置在特定进程的地址空间中被映射或可访问。)

      旧机器有多种内存地址方案。一些方案涉及基地址和偏移量的组合。在这些机器中,地址有两个或更多部分,例如一个 16 位基数 b 和一个 16 位偏移量 o。当指针转换为整数时,结果将是一个 32 位整数,其中 b 在高位,o 在低位,等于 65536•b + o。但是,这些整数没有连续编号地址。当base b 和offset o 用于访问内存时,实际形成的硬件地址可能是64•b + o em>。

      这样做的一个效果是b+1,o和b,o+64是不同的相同内存位置的地址。另一个效果是减去转换指针产生的两个整数不一定会给你它们之间的距离。 b+1,o和b,o的距离为64字节,减去65536•b + o from 65536•(b+1) + o 得到 65536。

      【讨论】:

      • 谢谢,uintptr_t 是基于 unsigned long 类型还是基于架构/c 实现的大小? (我问是因为对我来说尺寸是 8)。
      • @samuelbrody1249:每个 C 实现都适当地​​定义了它。一种 C 实现可能使用unsigned long,而另一种使用unsigned long long,另一种使用自定义类型。
      【解决方案5】:

      (unsigned long) &second_local_int - (unsigned long) &first_local_int 可能不起作用的地方是旧的 16 位 8086 架构。在 Intel 8086 上,内存总线为 20 位宽,要访问内存,您必须使用两个 16 位寄存器、一个段寄存器和一个偏移寄存器。例如,如果DS 是您的段寄存器,AX 是您的偏移量,则计算 CPU 将执行的实际内存地址 hwaddr = (DS<<4) + AX

      如果您的程序不需要超过 64k,则段寄存器保持固定,并且所有指针都为 16 位。否则你有 32 位指针,16 位用于段,16 位用于偏移。将指针转换为 32 位整数不会给出硬件地址,但会保留指针的位值。

      在实践中,我不认为将“指针作为整数”工作会是一个大问题,因为跨不同段的指针算术可能也不起作用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-16
        • 2012-01-19
        • 2010-11-23
        • 1970-01-01
        相关资源
        最近更新 更多