如Askmish的答案所解释的,从整数类型到指针的转换是实现定义的(例如参见N1570 6.3.2.3 Pointers§5§6以及脚注67)。
从指针到整数的转换也是实现定义的,如果结果不能表示为整数类型,则行为是未定义的。
在大多数通用架构中,现在的情况是sizeof(int)
小于sizeof(void*)
,因此即使是这些行
int n = 42;
void *p = (void *)n;
当使用clang或gcc编译时,会生成一个警告(例如在这里)
warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
自C99起,头文件
<stdint.h>
引入了一些可选的固定大小类型。特别是有几个类型应该在这里使用
n1570 7.20.1.4 可以容纳对象指针的整数类型:
The following type designates a signed integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
intptr_t
The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
uintptr_t
These types are optional.
所以,虽然
long
可能比
int
更好,但为了避免未定义的行为,最具可移植性(但仍是实现定义的)方法之一是使用这些类型之一
(1)。
Gcc 的文档
指定了转换的方式。
4.7 数组和指针
将指针转换为整数或反之的结果(C90 6.3.4,C99 和 C11 6.3.2.3)。
如果指针表示比整数类型大,则从指针到整数的强制转换会丢弃最高有效位,如果指针表示比整数类型小,则进行符号扩展(2),否则位保持不变。
如果指针表示比整数类型小,则从整数到指针的强制转换会丢弃最高有效位,如果指针表示比整数类型大,则根据整数类型的有符号性进行扩展,否则位保持不变。
当将指针转换为整数然后再转回来时,所得到的指针必须引用与原始指针相同的对象,否则行为是未定义的。也就是说,不能使用整数算术来避免 C99 和 C11 6.5.6/8 中规定的指针算术的未定义行为。
[...]
(2) GCC 的未来版本可能会使用零扩展或目标定义的 ptr_extend 模式。不要依赖符号扩展。
其他人,好的...
在这种情况下,不同整数类型之间的转换(int
和intptr_t
)在n1570 6.3.1.3 Signed and unsigned integers中提到。
将整数类型的值转换为除_Bool
以外的其他整数类型时,如果该值可以由新类型表示,则保持不变。
否则,如果新类型是无符号的,则通过反复添加或减去比新类型中可以表示的最大值多一个的值,直到该值在新类型的范围内。
否则,新类型为有符号且该值无法表示在其中;结果要么是实现定义的,要么会引发实现定义的信号。
因此,如果我们从一个
int
值开始,并且实现提供了一个
intptr_t
类型
并且sizeof(int) <= sizeof(intptr_t)
或
INTPTR_MIN <= n && n <= INTPTR_MAX
,我们可以安全地将其转换为
intptr_t
,然后再转换回来。
那个
intptr_t
可以转换为
void *
,然后再转换回相同的
(1)(2)intptr_t
值。
一般情况下,直接在
int
和
void *
之间进行转换是不可行的,即使在所提供的示例中,该值(42)足够小,不会导致未定义的行为。
我个人认为链接到GLib文档中关于类型转换宏的原因是值得商榷的(重点在于我自己)。
许多时候,GLib、GTK+和其他库允许您以无效指针的形式将“用户数据”传递给回调。有时候,您想要传递一个整数而不是一个指针。您可以分配一个整数[...]但这很不方便,在以后的某个时刻释放内存也很烦人。
指针在所有平台上至少有32位大小(GLib打算支持所有平台)。因此,您可以将至少32位整数值存储在指针值中。
我会让读者决定他们的方法是否比简单的方法更有意义
#include <stdio.h>
void f(void *ptr)
{
int n = *(int *)ptr;
printf("%d\n", n);
}
int main(void)
{
int n = 42;
f((void *)&n);
}
(1)我想引用Steve Jessop的答案中关于这些类型的一段话。
字面意思就是这样,它并没有涉及大小的问题。
uintptr_t
可能与void*
的大小相同。它可能更大。虽然这种C++实现是非常反常的,但它也有可能更小。例如,在某些假设的平台上,void*
为32位,但只使用了24位的虚拟地址空间,你可以拥有一个24位的uintptr_t
来满足要求。我不知道为什么会有这样的实现,但标准允许这样做。
(2) 实际上,标准明确提到了
void* -> intptr_t/uintptr_t -> void*
的转换,并要求这些指针相等。在
intptr_t -> void* -> intptr_t
的情况下,它并没有明确规定这两个整数值相等。它只是在脚注67中提到,“将指针转换为整数或将整数转换为指针的映射函数旨在与执行环境的寻址结构保持一致”。。
long
强制转换将会很好(如果他们完全避免使用此技术,因为有更可靠的替代方法,那就更好了)。 - M.M