将指针转换为整数

时间:2020-10-31 21:47:18

标签: c pointers

在我之前问过的问题中已经提到,将指针转换为整数类型不是一个好习惯。有什么例子说明为什么这不是一个好主意?如下所示如何处理-为什么会被认为是不良做法?

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

5 个答案:

答案 0 :(得分:2)

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

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

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

第6款则相反:

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

每当我看到“实现定义的”或“未定义的行为”时,该代码通常将不可移植。如果您喜欢编写良好的代码,请不要使用此类构造。但是,如果您知道自己在做什么,并且测试了自己的期望,就可以摆脱它。

顺便说一句,两个指针未指向指向同一数组(或恰好超过数组的末尾)的区别也是不确定的行为。


编辑:

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

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

intptr_t

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

uintptr_t

这些类型是可选的。

最后一句话很重要。

答案 1 :(得分:1)

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

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

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

[C2018,第6.3.2.3/6段]

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

以下内容如何处理-为什么会被认为是不良做法?

那是见仁见智。

但是,尽管代码片段符合(甚至严格符合)标准,但可以对这种孤立的片段进行评估,但它所打印的消息不一定涉及所涉及地址之间的关系。确实,除了(非)相等关系之外,世界的C模型甚至不支持无关对象地址之间的关系的概念。

答案 2 :(得分:1)

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

最大的缺点是您的代码比需要的要难理解。

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

如果这还不足以解决问题,那么我提出以下缺点:如果您搞砸了(并且比大多数其他事情更容易搞砸了),您的代码将变得不确定,有时以一种您暂时不会注意到的方式。这在Windows软件中发生了很多事情,以至于64位可执行文件的默认地址空间仍然是2GB。 (他们最近对SDK进行了相对更改,以设置LARGEADDRESSAWARE标志,但二进制映像默认值仍为no。)

答案 3 :(得分:0)

指针可能会转换为整数,因为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•< em> b + o 。但是,这些整数没有连续编号地址。当使用基 b 和偏移量 o 来访问内存时,形成的实际硬件地址可能是64• b + o

一个效果是 b +1, o b o +64是不同的相同存储位置的地址。另一个效果是,减去转换指针所产生的两个整数不一定会给您它们之间的距离。 b +1, o b o 之间的距离为64字节,但减去65536• 65536•中的> b + o •( b +1)+ o 得出65536。

答案 4 :(得分:0)

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

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

实际上,我认为使用“整数指针”不是一个大问题,因为跨不同段的指针算术可能也不起作用。

相关问题