将int转换为指针 - 为什么要先转换为long?(如在p =(void*)42;)

sle*_*ske 12 c int pointers casting long-integer

GLib文档中,有一章关于类型转换宏.在关于转换int*void指针的讨论中,它说(强调我的):

天真的,你可以试试这个,但这是不正确的:

gpointer p;
int i;
p = (void*) 42;
i = (int) p;
Run Code Online (Sandbox Code Playgroud)

同样,该示例不正确,请勿复制.问题是在某些系统上你需要这样做:

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
Run Code Online (Sandbox Code Playgroud)

(来源:GLib参考手册GLib 2.39.92,章节类型转换宏).

为什么演员long必要?

是否需要加宽int不作为指针演员的一部分自动发生?

ask*_*ish 10

根据C99: 6.3.2.3报价:

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

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

根据您提到的链接中的文档:

指针的大小始终至少为32位(在GLib打算支持的所有平台上).因此,您可以在指针值中存储至少32位整数值.

而且更多的long是保证至少32位.

所以,代码

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
Run Code Online (Sandbox Code Playgroud)

正如GLib所宣传的那样,它更安全,更便携,并且仅适用于高达32位的整数.

  • 这个答案似乎并没有解释为什么代码“更安全,更可移植”,只是断言它是 (2认同)

EOF*_*EOF 9

glib 文档是错误的,无论是对于他们(自由选择的)示例还是一般而言。

gpointer p;
int i;
p = (void*) 42;
i = (int) p;
Run Code Online (Sandbox Code Playgroud)

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
Run Code Online (Sandbox Code Playgroud)

将导致所有符合的 c 实现的i和 的相同值p
该示例选择不当,因为42保证可以用intlong(C11 草案标准 n157: 5.2.4.2.1 Sizes of integer types )表示。

一个更具说明性(和可测试)的例子是

int f(int x)
{
  void *p = (void*) x;
  int r = (int)p;
  return r;
}
Run Code Online (Sandbox Code Playgroud)

这将往返int-value iffvoid*可以表示每个可以表示的值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;
}
Run Code Online (Sandbox Code Playgroud)

哇,这不能可能的工作,对不对?(实际上,它可能)。为了往返指针(软件已经做了很长时间没有必要),您必须确保您往返的整数类型可以明确表示指针类型的每个可能值。

从历史上看,许多软件是由猴子编写的,它们假设指针可以往返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;
}
Run Code Online (Sandbox Code Playgroud)

当然,在 Windows 上除外。从好的方面来说,对于大多数非 Windows(和非 16 位)系统来说sizeof(long) == sizeof(void*)是正确的,因此往返是双向的

所以:

  • 这个例子是错误的
  • 选择保证往返的类型不保证往返

当然,c标准在/ (C11草案标准n1570:7.20.1.4 Integer types能够保持对象指针)中一个(自然符合标准的)解决方案,它被指定为保证 指针->整数类型->指针 循环-trip(虽然不是相反)。intptr_tuintptr_t

  • 转换的结果是实现定义的,实现可以定义“(int)42”与“(long)42”有不同的结果 (2认同)

Doc*_*sha 7

我认为这是因为这种转换是依赖于实现的。最好uintptr_t用于此目的,因为它在特定实现中是指针类型的大小。

  • `size_t` 是一种无符号整数类型,用于保存对象的最大大小,而不是指针的大小。`uintptr_t` 是一种无符号整数类型,用于保存指针的表示,它在标准中的存在本身表明 `size_t` 并非如此。 (4认同)

oxu*_*xuf 7

据我了解,代码(void*)(long)42“更好”,(void*)42因为它消除了以下警告gcc

\n\n
cast to pointer from integer of different size [-Wint-to-pointer-cast]\n
Run Code Online (Sandbox Code Playgroud)\n\n

void*在和long具有相同大小但不同的环境中int环境中。根据C99,\xc2\xa76.4.4.1\xc2\xb65:

\n\n
\n

整数常量的类型是可以表示其值的相应列表中的第一个。

\n
\n\n

因此,42被解释为int,如果将此常量直接分配给 a void*(when sizeof(void*)!=sizeof(int)),则会弹出上述警告,但每个人都希望干净的编译。这就是Glib 文档指出的问题(问题?):它发生在某些系统上。

\n\n
\n\n

那么,两个问题:

\n\n
    \n
  1. 将整数分配给相同大小的指针
  2. \n
  3. 将整数分配给不同大小的指针
  4. \n
\n\n

对我来说奇怪的是,尽管这两种情况在 C 标准和 gcc 实现说明中具有相同的地位(请参阅gcc 实现说明),gcc但仅显示 2 的警告。

\n\n

另一方面,很明显,转换为long并不总是解决方案(sizeof(void*)==sizeof(long)大多数情况下,在现代 ABI 上),对于64 位体系结构一般int情况,根据、longlong long的大小,有许多可能的组合。这就是为什么 glib 开发人员尝试为指针找到匹配的整数类型并为构建系统进行相应的分配。稍后,这些变量在这里用于以正确的方式生成这些转换宏(另请参阅基本 glib 类型)。最终,这些宏首先将一个整数转换为另一个大小相同的整数类型void* glib_gpi_castglib_gpui_castmasonmasonvoid*(这种转换符合标准,没有警告)。

\n\n

这种摆脱该警告的解决方案可以说是一个糟糕的设计,现在已由 和 解决intptr_t,但uintptr_t它的存在可能是出于历史原因:自 1998 年 C99和 Glib 开始开发以来就可用,因此他们找到了自己的解决方案解决同样的问题。似乎有过一些尝试intptr_tuintptr_t过改变它:

\n\n
\n

GLib 依赖于有效 C99 工具链的各个部分,因此是时候尽可能使用 C99 整数类型,而不是像 1997 年那样进行配置时发现。

\n
\n\n

但是没有成功,它似乎从未进入主分支。

\n\n
\n\n

简而言之,正如我所看到的,最初的问题已经从为什么这个代码更好变成了为什么这个警告不好(并且让它静音是一个好主意吗?)。后者已在其他地方得到回答,但这可能有帮助:

\n\n
\n

从指针转换为整数(反之亦然)会导致代码不可移植,并且可能会创建指向无效内存位置的意外指针。

\n
\n\n

但是,正如我上面所说,这条规则似乎不符合上述第一个问题的警告条件。也许其他人可以对这个话题有所启发。

\n\n

我对这种行为背后的基本原理的猜测是,只要原始值以某种方式发生变化,即使是微妙的变化,gcc 也会决定发出警告。正如gcc 文档所说(强调我的):

\n\n
\n

如果指针表示形式小于整数类型,从整数到指针的转换将丢弃最高有效位;如果指针表示形式大于整数类型,则根据整数类型的符号进行扩展,否则这些位保持不变

\n
\n\n

因此,如果大小匹配,则位不会发生变化(没有扩展、没有截断、没有用零填充)并且不会引发警告。

\n\n

另外,[u]intptr_t 只是一个typedef[u]intptr_t适当的合格整数:在分配时抛出警告是没有道理的,void*因为它确实是它的目的。如果规则适用于[u]intptr_t,则它必须适用于typedefed 整数类型。

\n


Bob*_*b__ 5

正如在Askmish回答中所解释的,从整数类型到指针的转换是由实现定义的(参见例如N1570 6.3.2.3 指针 §5 §6和脚注67)。

从指针到整数的转换也是实现定义的,如果结果不能用整数类型表示,则行为是undefined

现在,在大多数通用架构上,sizeof(int)都小于sizeof(void *),因此即使是那些行

int n = 42;
void *p = (void *)n;
Run Code Online (Sandbox Code Playgroud)

当用 clang 或 gcc 编译时会产生警告(参见例如这里

warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
Run Code Online (Sandbox Code Playgroud)

从 C99 开始,头文件<stdint.h>引入了一些可选的固定大小类型。一对夫妇,特别是,应该在这里使用n1570 7.20.1.4 能够保存对象指针的整数类型

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

intptr_t  
Run Code Online (Sandbox Code Playgroud)

以下类型指定了一个无符号整数类型,其属性是任何有效的指向 void 的指针都可以转换为这种类型,然后转换回指向 void 的指针,并且结果将与原始指针相等:

uintptr_t  
Run Code Online (Sandbox Code Playgroud)

这些类型是可选的。

因此,虽然 along可能比 更好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符号和无符号的整数

  1. 将整数类型的值转换为 以外的其他整数类型时_Bool,如果该值可以用新类型表示,则不变。

  2. 否则,如果新类型是无符号的,则通过重复加或减一个新类型可以表示的最大值来转换该值,直到该值在新类型的范围内。

  3. 否则,新类型是有符号的,值不能在其中表示;要么结果是实现定义的,要么引发实现定义的信号。


所以,如果我们从一个int值开始,并且实现提供了一个and orintptr_t类型,我们可以安全地将它转换为 an然后再将它转换回来。 sizeof(int) <= sizeof(intptr_t)INTPTR_MIN <= n && n <= INTPTR_MAXintptr_t

intptr_t可以转换为 avoid *然后转换回相同的(1) (2) intptr_t值。

对于 anint和 a之间的直接转换,通常情况下并不相同void *,即使在提供的示例中,值 (42) 小到不会导致未定义的行为。


我个人认为链接的 GLib 文档中为这些类型转换宏给出的原因很有争议(重点是我的)

很多时候,GLib、GTK+ 和其他库允许您以空指针的形式将“用户数据”传递给回调。有时你想传递一个整数而不是一个指针。您可以分配一个整数 [...] 但这很不方便,而且以后必须释放内存很烦人。

指针的大小始终至少为 32 位(在 GLib 打算支持的所有平台上)。因此,您可以在指针值中存储至少 32 位整数值。

我会让读者决定他们的方法是否比简单的方法更有意义

#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);
}
Run Code Online (Sandbox Code Playgroud)

(1) 我想引用Steve Jessop对这些类型 的回答中的一段话

把这当作它所说的意思。它没有说明大小。 可能与. 它可能更大。可以想象它会更小,尽管这样的 C++ 实现方法有悖常理。例如,在一些假设的平台上是 32 位,但只使用了 24 位的虚拟地址空间,你可以有一个 24 位满足要求。我不知道为什么实现会这样做,但标准允许这样做。
uintptr_tvoid*void*uintptr_t

(2) 实际上,标准明确提到了void* -> intptr_t/uintptr_t -> void*转换,要求那些指针比较相等。它没有明确规定在intptr_t -> void* -> intptr_t两个整数值比较相等的情况下。它只是在脚注67中提到“将指针转换为整数或将整数转换为指针的映射函数旨在与执行环境的寻址结构保持一致。”。