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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25381610/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 11:16:45  来源:igfitidea点击:

Cast int to pointer - why cast to long first? (as in p = (void*) 42; )

cpointerscastingintlong-integer

提问by sleske

In the GLibdocumentation, there is a chapter on type conversion macros. In the discussion on converting an intto a void*pointer it says (emphasis mine):

GLib文档中,有一章是关于类型转换宏的。在将 an 转换intvoid*指针的讨论中,它说(强调我的):

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;

天真地,您可能会尝试这样做,但这是不正确的:

gpointer p;
int i;
p = (void*) 42;
i = (int) p;

同样,那个例子是不正确的,不要复制它。问题是在某些系统上你需要这样做:

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;

(source: GLib Reference Manual for GLib 2.39.92, chapter Type Conversion Macros).

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

Why is that cast to longnecessary?

为什么需要强制转换long

Should any required widening of the intnot happen automatically as part of the cast to a pointer?

int作为对指针的强制转换的一部分,是否应该自动发生任何需要的扩展?

采纳答案by EOF

The glib documentation is wrong, both for their (freely chosen) example, and in general.

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

gpointer p;
int i;
p = (void*) 42;
i = (int) p;

and

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;

will both lead to identical values of iand pon all conforming c implementations.
The example is poorly chosen, because 42is guaranteed to be representable by intand long(C11 draft standard n157: 5.2.4.2.1 Sizes of integer types ).

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

A more illustrative (and testable) example would be

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

int f(int x)
{
  void *p = (void*) x;
  int r = (int)p;
  return r;
}

This will round-trip the int-value iff void*can represent every value that intcan, which practicallymeans sizeof(int) <= sizeof(void*)(theoretically: padding bits, yadda, yadda, doesn't actually matter). For other integer types, same problem, same actualrule (sizeof(integer_type) <= sizeof(void*)).

这将往返int-value iffvoid*可以表示每个可以表示的值int,这实际上意味着sizeof(int) <= sizeof(void*)(理论上:填充位,yadda,yadda,实际上并不重要)。对于其他整数类型,同样的问题,同样的实际规则 ( sizeof(integer_type) <= sizeof(void*))。

Conversely, the real problem, properly illustrated:

相反,真正的问题,正确说明:

void *p(void *x)
{
  char c = (char)x;
  void *r = (void*)c;
  return r;
}

Wow, that can'tpossibly work, right? (actually, it might). In order to round-trip a pointer(which software has done unnecessarily for a long time), you alsohave to ensure that the integer type you round-trip through can unambiguously represent every possible value of the pointer type.

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

Historically, much software was written by monkeys that assumed that pointers could round-trip through int, possibly because of K&R c's implicit int-"feature" and lots of people forgetting to #include <stdlib.h>and then casting the result of malloc()to a pointer type, thus accidentallyroundtripping through int. On the machines the code was developed for sizeof(int) == sizeof(void*), so this worked. When the switch to 64-bit machines, with 64-bit addresses (pointers) happened, a lot of software expected two mutually exclusive things:

从历史上看,许多软件是由猴子编写的,它们假设指针可以往返int,可能是因为 K&R c 的隐式int“功能”,并且很多人忘记将#include <stdlib.h>的结果malloc()转换为指针类型,从而意外地往返通过int。在机器上,代码是为 开发的sizeof(int) == sizeof(void*),所以这是有效的。当切换到具有 64 位地址(指针)的 64 位机器时,很多软件都期望两个互斥的东西:

1) intis a 32-bit 2's complement integer (typically also expecting signed overflow to wrap around)
2) sizeof(int) == sizeof(void*)

1) int是一个 32 位 2 的补码整数(通常也期望有符号溢出环绕)
2) sizeof(int) == sizeof(void*)

Some systems (coughWindows cough) also assumed sizeof(long) == sizeof(int), most others had 64-bit long.

一些系统(Windows)也假定sizeof(long) == sizeof(int),大多数其他系统有 64 位long

Consequently, on most systems, changing the round-tripping intermediate integer type to longfixed the (unnecessarily broken) code:

因此,在大多数系统上,更改往返中间整数类型以long修复(不必要的损坏)代码:

void *p(void *x)
{
  long l = (long)x;
  void *r = (void*)l;
  return r;
}

exceptof course, on Windows. On the plus side, for most non-Windows (and non 16-bit) systems sizeof(long) == sizeof(void*)is true, so the round-trip works both ways.

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

So:

所以:

  • the example is wrong
  • the type chosen to guarantee round-trip doesn't guarantee round-trip
  • 这个例子是错误的
  • 选择保证往返的类型不保证往返

Of course, the c standard hasa (naturally standard-conforming) solution in intptr_t/uintptr_t(C11 draft standard n1570: 7.20.1.4 Integer types capable of holding object pointers), which are specifiedto guarantee the
pointer -> integer type -> pointer
round-trip (though notthe reverse).

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

回答by askmish

As according to the C99: 6.3.2.3quote:

根据C99: 6.3.2.3报价:

5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.56)

6 Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

5 整数可以转换为任何指针类型。除了前面指定的之外,结果是实现定义的,可能没有正确对齐,可能没有指向引用类型的实体,并且可能是陷阱表示。56)

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

According to the documentation at the linkyou mentioned:

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

Pointers are always at least 32 bits in size (on all platforms GLib intends to support). Thus you can store at least 32-bit integer values in a pointer value.

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

And further more longis guaranteed to be atleast 32-bits.

此外long,保证至少为 32 位

So,the code

所以,代码

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;

is safer,more portable and well defined for upto 32-bit integers only, as advertised by GLib.

正如 GLib 所宣传的那样,它更安全、更便携,并且仅适用于最多 32 位整数。

回答by DoctorMoisha

I think it is because this conversion is implementation-dependendent. It is better to use uintptr_tfor this purpose, because it is of the size of pointer type in particular implementation.

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

回答by oxuf

As I understand it, the code (void*)(long)42is "better" than (void*)42because it gets rid of this warning for gcc:

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

cast to pointer from integer of different size [-Wint-to-pointer-cast]

on environments where void*and longhave the same size, but different from int. According to C99, §6.4.4.1 ?5:

void*long具有相同大小但不同于int. 根据 C99,§6.4.4.1 ?5:

The type of an integer constant is the first of the corresponding list in which its value can be represented.

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

Thus, 42is interpreted as int, had this constant be assigned directly to a void*(when sizeof(void*)!=sizeof(int)), the above warning would pop up, but everyone wants clean compilations. This isthe problem (issue?) the Glib doc is pointing to: it happens on somesystems.

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



So, two issues:

所以,两个问题:

  1. Assign integer to pointer of same size
  2. Assign integer to pointer of different size
  1. 将整数分配给相同大小的指针
  2. 将整数分配给不同大小的指针

Curiously enough for me is that, even though both cases have the same status on the C standard and in the gcc implementation notes (see gcc implementation notes), gcconly shows the warning for 2.

对我来说奇怪的是,即使这两种情况在 C 标准和 gcc 实现说明(参见gcc 实现说明)中具有相同的状态,也gcc只显示 2 的警告。

On the other hand, it is clear that casting to longis not always the solution (still, on modern ABIs sizeof(void*)==sizeof(long)most of the times), there are many possible combinations depending on the size of int,long,long longand void*, for 64bits architecturesand in general. That is why glib developers try to find the matching integer type for pointers and assignglib_gpi_castand glib_gpui_castaccordingly for the masonbuild system. Later, these masonvariables are used in hereto generate those conversion macrosthe right way (see also thisfor basic glib types). Eventually, those macros first cast an integer to another integer type of the same size as void*(such conversion conforms to the standard, no warnings) for the target architecture.

在另一方面,很显然,铸造long并不总是解决方案(当然,在现代的ABIsizeof(void*)==sizeof(long)大部分的时间),还有取决于大小许多可能的组合intlonglong longvoid*,对64位架构通用。这就是为什么 glib 开发人员试图为指针找到匹配的整数类型并为构建系统分配相应的类型。稍后,这些变量在此处用于以正确的方式生成这些转换宏(另请参阅glib_gpi_castglib_gpui_castmasonmason对于基本的 glib 类型)。最终,这些宏首先将一个整数转换为另一个void*与目标架构大小相同的整数类型(这种转换符合标准,没有警告)。

This solution to get rid of that warning is arguably a bad design that is nowadys solved by intptr_tand uintptr_t, but it is posible it is there for historical reasons: intptr_tand uintptr_tare available since C99and Glib started its development earlierin 1998, so they found their own solution to the same problem. It seems that there were some triesto change it:

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

GLib depends on various parts of a valid C99 toolchain, so it's time to use C99 integer types wherever possible, instead of doing configure-time discovery like it's 1997.

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

no success however, it seems it never got in the main branch.

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



In short, as I see it, the original question has changed from why this code is betterto why this warning is bad(and is it a good idea to silence it?). The later has been answered somewhere else, but thiscould also help:

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

Converting from pointer to integer or vice versa results in code that is not portable and may create unexpected pointers to invalid memory locations.

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

But, as I said above, this rule doesn't seem to qualify for a warning for issue number 1 above. Maybe someone else could shed some light on this topic.

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

My guess for the rationale behind this behaviour is that gcc decided to throw a warning whenever the original value is changed in some way, even if subtle. As gcc docsays (emphasis mine):

我对这种行为背后的基本原理的猜测是 gcc 决定在原始值以某种方式改变时发出警告,即使是微妙的。正如gcc doc所说(强调我的):

A cast from integer to pointer discards most-significant bits if the pointer representation is smaller than the integer type, extends according to the signedness of the integer type if the pointer representation is larger than the integer type, otherwise the bits are unchanged.

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

So, if sizes match there is no change on the bits(no extension, no truncation, no filling with zeros) and no warning is thrown.

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

Also, [u]intptr_tis just a typedefof the appropriate qualified integer: it is not justifiable to throw a warning when assigning [u]intptr_tto void*since it is indeed its purpose. If the rule applies to [u]intptr_t, it has to apply to typedefed integer types.

此外,[u]intptr_t只是typedef适当限定整数的 a :在分配[u]intptr_t给时发出警告是没有道理的,void*因为它确实是它的目的。如果规则适用于[u]intptr_t,则它必须适用于typedefed 整数类型。

回答by Bob__

As explained in Askmish's answer, the conversion from an integer type to a pointer is implementation defined(see e.g. N1570 6.3.2.3 Pointers§5§6and the footnote 67).

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

The conversion from a pointer to an integer is implementation defined too and if the result cannot be represented in the integer type, the behavior is undefined.

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

On most general purpose architectures, nowadays, sizeof(int)is less than sizeof(void *), so that even those lines

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

int n = 42;
void *p = (void *)n;

When compiled with clang or gcc would generate a warning (see e.g. here)

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

warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]

Since C99, the header <stdint.h>introduces some optional fixed-sized types. A couple, in particular, should be used here n1570 7.20.1.4 Integer types capable of holding object pointers:

从 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.

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

intptr_t  

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

uintptr_t  

这些类型是可选的。

So, while a longmay be better than int, to avoid undefined behaviour the most portable (but still implementation defined) way is to use one of those types(1).

因此,虽然 along可能比 更好int,但为了避免未定义的行为,最可移植(但仍是实现定义)的方法是使用其中一种类型(1)

Gcc's documentation specifies how the conversion takes place.

Gcc 的文档指定了转换是如何发生的

4.7 Arrays and Pointers

The result of converting a pointer to an integer or vice versa (C90 6.3.4, C99 and C11 6.3.2.3).

A cast from pointer to integer discards most-significant bits if the pointer representation is larger than the integer type, sign-extends(2)if the pointer representation is smaller than the integer type, otherwise the bits are unchanged.

A cast from integer to pointer discards most-significant bits if the pointer representation is smaller than the integer type, extends according to the signedness of the integer type if the pointer representation is larger than the integer type, otherwise the bits are unchanged.

When casting from pointer to integer and back again, the resulting pointer must reference the same object as the original pointer, otherwise the behavior is undefined. That is, one may not use integer arithmetic to avoid the undefined behavior of pointer arithmetic as proscribed in C99 and C11 6.5.6/8.
[...]
(2) Future versions of GCC may zero-extend, or use a target-defined ptr_extend pattern. Do not rely on sign extension.

4.7 数组和指针

将指针转换为整数或反之的结果 (C90 6.3.4、C99 和 C11 6.3.2.3)

如果指针表示大于整数类型,则从指针到整数的转换会丢弃最高有效位,如果指针表示小于整数类型,则符号扩展(2),否则位不变。

如果指针表示小于整数类型,则从整数到指针的转换会丢弃最高有效位,如果指针表示大于整数类型,则根据整数类型的符号进行扩展,否则位不变。

当从指针转换为整数并再次返回时,结果指针必须引用与原始指针相同的对象,否则行为未定义。也就是说,不能使用整数算术来避免 C99 和 C11 6.5.6/8 中禁止的指针算术的未定义行为。
[...]
(2) GCC 的未来版本可能会零扩展,或使用目标定义的 ptr_extend 模式。不要依赖符号扩展。

Others, well...

其他的,嗯...



The conversions between different integer types (intand intptr_tin this case) are mentioned in n1570 6.3.1.3 Signed and unsigned integers

不同的整数类型之间的转换(int以及intptr_t在这种情况下)在提到n1570 6.3.1.3符号和无符号的整数

  1. When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.

  2. Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

  3. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

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

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

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



So, if we start from an intvalue and the implementation provides an intptr_ttype andsizeof(int) <= sizeof(intptr_t)or INTPTR_MIN <= n && n <= INTPTR_MAX, we can safely convert it to an intptr_tand then convert it back.

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

That intptr_tcan be converted to a void *and then converted back to the same (1)(2)intptr_tvalue.

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

The same doesn't hold in general for a direct conversion between an intand a void *, even if in the example provided, the value (42) is small enough not to cause undefined behaviour.

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



I personally find quite debatable the reasons given for those type conversion macros in the linked GLib documentation (emphasis mine)

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

Many times GLib, GTK+, and other libraries allow you to pass "user data" to a callback, in the form of a void pointer. From time to time you want to pass an integer instead of a pointer. You could allocate an integer [...] But this is inconvenient, and it's annoying to have to free the memory at some later time.

Pointers are always at least 32 bits in size (on all platforms GLib intends to support). Thus you can store at least 32-bit integer values in a pointer value.

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

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

I'll let the reader decide whether their approach makes more sense than a simple

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

#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);
}


(1) I'd like to quote a passage in this Steve Jessop's answerabout those types

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

Take this to mean what it says. It doesn't say anything about size.
uintptr_tmight be the same size as a void*. It might be larger. It could conceivably be smaller, although such a C++ implementation approaches perverse. For example on some hypothetical platform where void*is 32 bits, but only 24 bits of virtual address space are used, you could have a 24-bit uintptr_twhich satisfies the requirement. I don't know why an implementation would do that, but the standard permits it.

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

(2) Actually, the standard explicitly mention the void* -> intptr_t/uintptr_t -> void*conversion, requiring those pointers to compare equal. It doesn't explicitlymandate that in the case intptr_t -> void* -> intptr_tthe two integer values compare equal. It just mention in footnote 67 that "The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.".

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