在GLib文档中,有一章是关于类型转换宏的。
在关于将 int
转换为 void*
指针的讨论中,它说(强调我的):
Naively, you might try this, but it's incorrect:
gpointer p; int i; p = (void*) 42; i = (int) p;
Again, that example was not correct, don't copy it. The problem is that on some systems you need to do this:
gpointer p; int i; p = (void*) (long) 42; i = (int) (long) p;
(来源:GLib 2.39.92 的 GLib 引用手册,第 Type Conversion Macros 章)。
为什么必须强制转换为 long
?
作为指针转换的一部分,int
的任何必需的扩展是否应该自动发生?
最佳答案
glib 文档是错误的,无论是对于他们(自由选择的)示例还是一般情况。
gpointer p;
int i;
p = (void*) 42;
i = (int) p;
和
gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
都将导致相同的值 i
和 p
在所有符合标准的 C 实现上。
该示例选择不当,因为 42
保证由 int
表示和 long
(C11 标准草案 n157:5.2.4.2.1 整数类型的大小)。
一个更具说明性(和可测试性)的例子是
int f(int x)
{
void *p = (void*) x;
int r = (int)p;
return r;
}
这将往返 int
-值 iff void*
可以表示 int
的每一个值可以,这实际上意味着sizeof(int) <= sizeof(void*)
(理论上:填充位,yadda,yadda,实际上并不重要)。对于其他整数类型,同样的问题,同样的实际规则(sizeof(integer_type) <= sizeof(void*)
)。
相反,真正的问题,适当说明:
void *p(void *x)
{
char c = (char)x;
void *r = (void*)c;
return r;
}
哇,那不可能能行,对吧? (实际上,它可能)。 为了往返一个指针(软件已经不必要地做了很长时间),你还必须确保你往返的整数类型可以明确表示指针类型的每个可能值。
从历史上看,许多软件都是由猴子编写的,它们假设指针可以通过 int
往返,可能是因为 K&R c 的隐式 int
-“功能”和很多人忘记了#include <stdlib.h>
然后转换 malloc()
的结果到一个指针类型,因此不小心通过int
往返.在机器上,代码是为 sizeof(int) == sizeof(void*)
开发的,所以这有效。当切换到具有 64 位地址(指针)的 64 位机器时,许多软件期望两个相互排斥的事情:
1) int
是一个 32 位 2 的补码整数(通常还期望有符号溢出环绕)
2) sizeof(int) == sizeof(void*)
一些系统(咳咳 Windows 咳咳)也假设sizeof(long) == sizeof(int)
, 大多数其他人有 64 位 long
.
因此,在大多数系统上,将往返中间整数类型更改为 long
修复了(不必要地损坏的)代码:
void *p(void *x)
{
long l = (long)x;
void *r = (void*)l;
return r;
}
除了当然,在 Windows 上。从好的方面来说,对于大多数非 Windows(和非 16 位)系统 sizeof(long) == sizeof(void*)
是真的,所以往返工作双向。
所以:
- 示例错误
- 选择保证往返的类型并不保证往返
当然,c 标准在intptr_t
中有一个(自然符合标准的)解决方案|/uintptr_t
(C11 标准草案 n1570:7.20.1.4 能够保存对象指针的整数类型)指定以保证
指针 -> 整型 -> 指针
往返(虽然不是相反)。
关于将 int 转换为指针 - 为什么要先转换为 long? (如 p = (void*) 42; ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25381610/