在GLib文档中,有一章介绍了类型转换宏。
在关于将int
转换为*void
指针的讨论中,它说(强调我的):
天真地,你可以试试这个,但它不正确:
gpointer p; int i; p = (void*) 42; i = (int) p;
同样,这个例子不正确,不要复制它。 问题是 在某些系统上,您需要执行此操作:
gpointer p; int i; p = (void*) (long) 42; i = (int) (long) p;
(来源:GLib参考手册GLI 2.39.92,章节Type Conversion Macros)。
为什么需要转换为long
?
是否需要加宽int
不会自动作为指针投射的一部分发生?
答案 0 :(得分:10)
根据C99: 6.3.2.3
引用:
5整数可以转换为任何指针类型。除了 以前指定的,结果是实现定义的,可能不是 正确对齐,可能不指向引用的实体 类型,可能是一个陷阱表示.56)
6任何指针类型都可以转换为整数类型。除了 之前指定的,结果是实现定义的。如果 结果不能用整数类型表示,行为是 未定义。结果不必在任何值的范围内 整数类型。
根据你提到的link的文件:
指针的大小始终至少为32位(在所有平台上都是GLib 打算支持)。因此,您可以存储至少32位整数值 在指针值。
还有更多long
is guaranteed to be atleast 32-bits。
所以,代码
gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
更安全,更便携,并且仅适用于高达32位的整数,正如GLib所宣传的那样。
答案 1 :(得分:6)
对于glib文档(无论是自由选择的示例)还是一般而言,都是错误的。
gpointer p;
int i;
p = (void*) 42;
i = (int) p;
和
gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
在所有符合c的实现中都将导致i
和p
的值相同。
该示例选择不当,因为保证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
值,如果void*
可以代表int
可以代表的每个值,实际上表示sizeof(int) <= sizeof(void*)
(理论上:填充位yadda,yadda实际上并不重要)。对于其他整数类型,相同的问题,相同的 actual 规则(sizeof(integer_type) <= sizeof(void*)
)。
相反,正确说明了实际问题:
void *p(void *x)
{
char c = (char)x;
void *r = (void*)c;
return r;
}
哇,那 可能行不通,对吧? (实际上,它可能)。 为了使 pointer (该软件长时间不必要地执行)往返操作,您还 必须确保往返的整数类型可以明确表示指针类型的每个可能值。
从历史上看,很多猴子写的软件都认为指针可以往返于int
,这可能是由于K&R c的隐式int
-“功能”以及许多人忘记了{{1 }},然后将#include <stdlib.h>
的结果强制转换为指针类型,从而偶然地通过malloc()
往返。在机器上,代码是为int
开发的,因此可以正常工作。当切换到具有64位地址(指针)的64位计算机时,很多软件都期望两个互斥的东西:
1)sizeof(int) == sizeof(void*)
是32位2的补码整数(通常还期望带符号的溢出会环绕)
2)int
某些系统(咳嗽 Windows 咳嗽)也假定为sizeof(int) == sizeof(void*)
,大多数其他系统具有64位sizeof(long) == sizeof(int)
。
因此,在大多数系统上,将往返中间整数类型更改为long
可以修复(不必要中断的)代码:
long
当然,在Windows上, 除了。从好的方面来说,对于大多数非Windows(和非16位)系统,void *p(void *x)
{
long l = (long)x;
void *r = (void*)l;
return r;
}
是正确的,因此往返双向都是有效的。
所以:
当然,c标准在sizeof(long) == sizeof(void*)
/ intptr_t
中有一个(自然符合标准的)解决方案(C11标准草案n1570:7.20.1.4整数类型能够包含对象指针),它们是指定的,用于保证
指针->整数类型->指针
往返(尽管不是相反)。
答案 2 :(得分:4)
如Askmish的answer中所述,从整数类型到指针的转换是实现定义的(例如,参见N1570 6.3.2.3 Pointers {{3} } §5和脚注§6)。
从指针到整数的转换也是实现定义的,并且如果结果不能以整数类型表示,则行为为 undefined 。
如今,在大多数通用体系结构中,sizeof(int)
小于sizeof(void *)
,因此即使是这些行
int n = 42;
void *p = (void *)n;
使用clang或gcc进行编译时会生成警告(例如,67)
warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
自C99起,标头<stdint.h>
引入了一些可选的固定大小类型。 here在这里尤其要使用一对:
以下类型指定一个带符号的整数类型,其属性是可以将任何有效的指向void的指针转换为该类型,然后再转换回指向void的指针,结果将与原始指针进行比较:
intptr_t
以下类型指定一个无符号整数类型,其属性是可以将任何有效的指向void的指针转换为该类型,然后再转换回指向void的指针,结果将与原始指针进行比较:
uintptr_t
这些类型是可选的。
因此,尽管long
可能比int
更好,但为了避免未定义的行为,最可移植(但仍由实现定义)的方法是使用其中一种类型(1)< / sup>。
Gcc的文档n1570 7.20.1.4 Integer types capable of holding object pointers。
4.7数组和指针
将指针转换为整数(反之亦然)的结果(C90 6.3.4,C99和C11 6.3.2.3)。
如果指针表示形式大于整数类型,则从指针到整数的转换将丢弃最高有效位;如果指针表示形式小于整数类型,则将符号扩展(2),否则这些位不变。
如果指针表示形式小于整数类型,则从整数到指针的转换将丢弃最高有效位;如果指针表示形式大于整数类型,则将根据整数类型的有符号性扩展,否则这些位不变
当从指针转换为整数并再次返回时,结果指针必须引用与原始指针相同的对象,否则行为未定义。也就是说,不能使用整数算术来避免C99和C11 6.5.6 / 8中禁止的指针算术的未定义行为。
[...]
(2)未来版本的GCC可能会零扩展,或使用目标定义的ptr_extend模式。不要依赖符号扩展名。
其他specifies how the conversion takes place
int
和intptr_t
之间的转换)
将具有整数类型的值转换为
_Bool
以外的其他整数类型时,如果该值可以用新类型表示,则该值不变。否则,如果新类型是无符号的,则通过重复添加或减去比新类型可表示的最大值多一个值来转换该值,直到该值在新类型的范围内为止。
否则,将对新类型进行签名,并且不能在其中表示值;结果是实现定义的,还是引发实现定义的信号。
因此,如果我们从一个int
值开始并且实现提供了一个intptr_t
类型的和 sizeof(int) <= sizeof(intptr_t)
或INTPTR_MIN <= n && n <= INTPTR_MAX
,我们可以放心地将其转换为intptr_t
,然后将其转换回。
可以将intptr_t
转换为void *
,然后再转换回相同的(1) (2) {{1} }值。
对于intptr_t
和int
之间的直接转换,通常不适用,即使在提供的示例中,值(42)很小也不会引起未定义的行为
我个人在链接的GLib文档(重点是我的)中发现了那些类型转换宏给出的理由值得商bat
许多时候,GLib,GTK +和其他库允许您以空指针的形式将“用户数据”传递给回调。 您有时会希望传递一个整数而不是一个指针。您可以分配一个整数[...]但这很不方便,而且以后不得不释放内存也很烦人。
指针的大小始终至少为32位(在GLib打算支持的所有平台上)。因此,您可以在指针值中至少存储32位整数值。
我会让读者决定他们的方法是否比简单的方法更有意义
void *
(1)我想在n1570 6.3.1.3 Signed and unsigned integers的Steve Jessop中引用有关这些类型的段落
以此来表达它的意思。它没有说明尺寸。
#include <stdio.h> void f(void *ptr) { int n = *(int *)ptr; // ^ Yes, here you may "pay" the indirection printf("%d\n", n); } int main(void) { int n = 42; f((void *)&n); }
的大小可能与uintptr_t
相同。可能更大。可以想象,它可能会更小,尽管这样的C ++实现方式是错误的。例如,在某个假设平台上,void*
是32位,但仅使用24位虚拟地址空间,您可以拥有一个满足要求的24位void*
。我不知道为什么实现会这样做,但是标准允许这样做。
(2)实际上,该标准明确提到了uintptr_t
转换,要求这些指针进行相等比较。在void* -> intptr_t/uintptr_t -> void*
中,两个整数值比较相等并不是强制性的。只是在脚注67中提到:“用于将指针转换为整数或将整数转换为指针的映射功能旨在与执行环境的寻址结构一致。”
答案 3 :(得分:4)
据我了解,代码(void*)(long)42
比(void*)42
更好,因为它摆脱了gcc
的警告:
cast to pointer from integer of different size [-Wint-to-pointer-cast]
在void*
和long
具有相同大小但与int
不同的环境中。根据C99,第6.4.4.1节¶5:
整数常量的类型是相应列表中可以表示其值的列表中的第一个。
因此,42
被解释为int
,如果将此常量直接分配给void*
(当sizeof(void*)!=sizeof(int)
时),则会弹出上述警告,但是每个人想要干净的汇编。这个 是Glib文档所指向的问题(问题?):它发生在某些系统上。
所以,有两个问题:
对我来说很有趣的是,即使这两种情况在C标准和gcc实施说明(请参阅gcc implementation notes)中都具有相同的状态,gcc
仅显示2的警告。 / p>
另一方面,很明显,强制转换为long
并非总是解决方案(仍然,在大多数现代ABI sizeof(void*)==sizeof(long)
上,取决于大小,存在许多可能的组合) 64bits architectures和general中int
,long
,long long
和void*
的值。这就是为什么glib开发人员尝试为指针找到匹配的整数类型,并为assign构建系统找到相应的mason
glib_gpi_cast
和glib_gpui_cast
的原因。稍后,这些mason
变量在here中用于以正确的方式生成那些转换宏(有关基本glib类型,另请参见this)。最终,这些宏首先将整数转换为与目标体系结构void*
具有相同大小的另一整数类型(这种转换符合标准,没有警告)。
这种消除警告的解决方案可以说是一个糟糕的设计,现在可以通过intptr_t
和uintptr_t
来解决,但出于历史原因,它可能存在:intptr_t
和uintptr_t
可用since C99,Glib于1998年开始开发earlier,因此他们找到了解决同一问题的解决方案。似乎有some tries对其进行了更改:
GLib取决于有效C99工具链的各个部分,因此现在该 尽可能使用C99整数类型,而不要进行配置时间 像1997年这样的发现。
但是没有成功,看来它从未进入主分支。
简而言之,正如我所看到的,原始问题已从为什么这段代码更好变为为什么这个警告不好(而使其静音的好主意?)。后者已经在其他地方得到解答,但是this也可以提供帮助:
从指针转换为整数,反之亦然,导致代码不可移植,并且可能会创建指向无效内存位置的意外指针。
但是,正如我在上文所述,此规则似乎不符合上述第1个警告的警告条件。也许其他人可以阐明这个话题。
我对这种行为背后的原理的猜测是,gcc决定在原始值以某种方式更改(即使细微)时也发出警告。正如gcc doc所说(强调我的意思):
如果指针表示形式小于整数类型,则从整数到指针的转换将丢弃最高有效位,如果指针表示形式大于整数类型,则根据整数类型的有符号性扩展,否则位保持不变。
因此,如果大小匹配,则位数不变(不扩展,不截断,不填充零)并且不引发警告。
此外,[u]intptr_t
is just a typedef
是适当的限定整数:将[u]intptr_t
分配给void*
时,发出警告是没有道理的,因为这确实是其目的。如果该规则适用于[u]intptr_t
,则必须适用于typedef
个整数类型。
答案 4 :(得分:1)
我认为这是因为此转换是依赖于实现的。为此目的最好使用uintptr_t
,因为它在特定实现中具有指针类型的大小。