简体   繁体   English

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

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

In the GLib documentation, there is a chapter on type conversion macros.GLib文档中,有一章是关于类型转换宏的。 In the discussion on converting an int to a void* pointer it says (emphasis mine):在将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;

(source: GLib Reference Manual for GLib 2.39.92, chapter Type Conversion Macros ). (来源:GLib 2.39.92 的 GLib 参考手册,章节类型转换宏)。

Why is that cast to long necessary?为什么需要转换为long

Should any required widening of the int not happen automatically as part of the cast to a pointer?任何需要的int扩展是否应该不会作为指针转换的一部分自动发生?

As according to the C99: 6.3.2.3 quote:根据C99: 6.3.2.3引用:

5 An integer may be converted to any pointer type. 5 整数可以转换为任何指针类型。 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)除了前面指定的之外,结果是实现定义的,可能没有正确对齐,可能没有指向引用类型的实体,并且可能是陷阱表示。56)

6 Any pointer type may be converted to an integer type. 6 任何指针类型都可以转换为整数类型。 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.结果不必在任何整数类型的值范围内。

According to the documentation at the link you mentioned:根据您提到的链接中的文档:

Pointers are always at least 32 bits in size (on all platforms GLib intends to support).指针的大小始终至少为 32 位(在 GLib 打算支持的所有平台上)。 Thus you can store at least 32-bit integer values in a pointer value.因此,您可以在指针值中存储至少 32 位整数值。

And further more long is guaranteed to be atleast 32-bits .并且保证long时间至少为 32-bits

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 位整数。

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 i and p on all conforming c implementations.在所有符合 c 的实现上都会导致ip相同值。
The example is poorly chosen, because 42 is guaranteed to be representable by int and long (C11 draft standard n157: 5.2.4.2.1 Sizes of integer types ).该示例选择不当,因为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 int can, which practically means sizeof(int) <= sizeof(void*) (theoretically: padding bits, yadda, yadda, doesn't actually matter).这将往返int值如果void*可以表示int可以表示的每个值,这实际上意味着sizeof(int) <= sizeof(void*) (理论上:填充位,yadda,yadda,实际上并不重要) . For other integer types, same problem, same actual rule ( sizeof(integer_type) <= sizeof(void*) ).对于其他整数类型,同样的问题,同样的实际规则( 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't possibly work, right?哇,这是不能工作的可能,对吧? (actually, it might ). (实际上,它可能)。 In order to round-trip a pointer (which software has done unnecessarily for a long time), you also have 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 accidentally roundtripping through int .从历史上看,很多软件是由猴子编写的,它们假设指针可以通过int来回传输,这可能是因为 K&R c 的隐式int -"feature" 以及很多人忘记#include <stdlib.h>然后转换malloc()的结果malloc()到指针类型,从而意外地通过int往返。 On the machines the code was developed for sizeof(int) == sizeof(void*) , so this worked.在机器上,代码是为sizeof(int) == sizeof(void*) ,所以这是有效的。 When the switch to 64-bit machines, with 64-bit addresses (pointers) happened, a lot of software expected two mutually exclusive things:当切换到具有 64 位地址(指针)的 64 位机器时,很多软件都期望两个互斥的东西:

1) int is a 32-bit 2's complement integer (typically also expecting signed overflow to wrap around) 1) int是一个 32 位 2 的补码整数(通常也期望有符号溢出环绕)
2) sizeof(int) == sizeof(void*) 2) sizeof(int) == sizeof(void*)

Some systems ( cough Windows 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 long fixed the (unnecessarily broken) code:因此,在大多数系统上,将往返中间整数类型更改为long固定(不必要地损坏)代码:

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

except of course, on Windows.当然,在 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(和非 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 has a (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 specified to guarantee the当然,c 标准在intptr_t / uintptr_t (C11 草案标准 n1570: 7.20.1.4 Integer types能够保持对象指针)中一个(自然符合标准的)解决方案,它被指定为保证
pointer -> integer type -> pointer指针 -> 整数类型 -> 指针
round-trip (though not the reverse).往返(虽然不是相反)。

I think it is because this conversion is implementation-dependendent.我认为这是因为这种转换是依赖于实现的。 It is better to use uintptr_t for this purpose, because it is of the size of pointer type in particular implementation.为此最好使用uintptr_t ,因为它在特定实现中是指针类型的大小。

As explained in Askmish 's answer , the conversion from an integer type to a pointer is implementation defined (see eg N1570 6.3.2.3 Pointers §5 §6 and 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 eg 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.从 C99 开始,头文件<stdint.h>引入了一些可选的固定大小类型。 A couple, in particular, should be used here n1570 7.20.1.4 Integer types capable of holding object pointers :一对夫妇,特别是,应该在这里使用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:以下类型指定了一个有符号整数类型,其属性是任何指向 void 的有效指针都可以转换为该类型,然后转换回指向 void 的指针,并且结果将与原始指针相等:

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

 uintptr_t

These types are optional.这些类型是可选的。

So, while a long may be better than int , to avoid undefined behaviour the most portable (but still implementation defined) way is to use one of those types (1) .因此,虽然long可能比int更好,但为了避免未定义的行为,最可移植(但仍由实现定义)的方法是使用其中一种类型(1)

Gcc's documentation specifies how the conversion takes place . Gcc 的文档指定了转换是如何发生的

4.7 Arrays and Pointers 4.7 数组和指针

The result of converting a pointer to an integer or vice versa (C90 6.3.4, C99 and C11 6.3.2.3) .将指针转换为整数或反之的结果 (C90 6.3.4、C99 和 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.如果指针表示大于整数类型,则从指针到整数的转换会丢弃最高有效位,如果指针表示小于整数类型,则符号扩展(2) ,否则位不变。

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.也就是说,不能使用整数算术来避免 C99 和 C11 6.5.6/8 中禁止的指针算术的未定义行为。
[...] [...]
(2) Future versions of GCC may zero-extend, or use a target-defined ptr_extend pattern. (2) GCC 的未来版本可能会零扩展,或使用目标定义的 ptr_extend 模式。 Do not rely on sign extension.不要依赖符号扩展。

Others, well...其他的, 嗯...


The conversions between different integer types ( int and intptr_t in this case) are mentioned in n1570 6.3.1.3 Signed and unsigned integers n1570 6.3.1.3 Signed and unsigned integers中提到了不同整数类型(本例中为intintptr_t )之间的转换

  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.当一个整数类型的值被转换为_Bool以外的另一个整数类型时,如果该值可以用新类型表示,则不变。

  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.要么结果是实现定义的,要么引发实现定义的信号。


So, if we start from an int value and the implementation provides an intptr_t type and sizeof(int) <= sizeof(intptr_t) or INTPTR_MIN <= n && n <= INTPTR_MAX , we can safely convert it to an intptr_t and then convert it back.因此,如果我们从一个int值开始,并且实现提供了一个intptr_t类型sizeof(int) <= sizeof(intptr_t)INTPTR_MIN <= n && n <= INTPTR_MAX ,我们可以安全地将其转换为intptr_t然后再转换背部。

That intptr_t can be converted to a void * and then converted back to the same (1) (2) intptr_t value. intptr_t可以转换为void * ,然后转换回相同的(1) (2) intptr_t值。

The same doesn't hold in general for a direct conversion between an int and a void * , even if in the example provided, the value (42) is small enough not to cause undefined behaviour.对于intvoid *之间的直接转换,即使在提供的示例中,值 (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.很多时候,GLib、GTK+ 和其他库允许您以空指针的形式将“用户数据”传递给回调。 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 ).指针的大小始终至少为 32 位(在 GLib 打算支持的所有平台上)。 Thus you can store at least 32-bit integer values in a pointer value.因此,您可以在指针值中存储至少 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 answer about those types (1) 我想引用Steve Jessop对这些类型的回答中的一段话

Take this to mean what it says.把这当作它所说的意思。 It doesn't say anything about size.它没有说明大小。
uintptr_t might be the same size as a void* . uintptr_t可能与void*大小相同。 It might be larger.它可能更大。 It could conceivably be smaller, although such a C++ implementation approaches perverse.可以想象它会更小,尽管这样的 C++ 实现方法有悖常理。 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_t which satisfies the requirement.例如,在void*为 32 位,但仅使用 24 位虚拟地址空间的某些假设平台上,您可以拥有满足要求的 24 位uintptr_t I don't know why an implementation would do that, but the standard permits it.我不知道为什么实现会这样做,但标准允许这样做。

(2) Actually, the standard explicitly mention the void* -> intptr_t/uintptr_t -> void* conversion, requiring those pointers to compare equal. (2) 实际上,标准明确提到了void* -> intptr_t/uintptr_t -> void*转换,要求这些指针比较相等。 It doesn't explicitly mandate that in the case intptr_t -> void* -> intptr_t the two integer values compare equal.它没有明确规定在intptr_t -> void* -> intptr_t的情况下,两个整数值比较相等。 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.".它只是在脚注67中提到“将指针转换为整数或将整数转换为指针的映射函数旨在与执行环境的寻址结构保持一致。”。

As I understand it, the code (void*)(long)42 is "better" than (void*)42 because 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 long have the same size, but different from int .void*long具有相同大小但与int不同的环境中。 According to C99, §6.4.4.1 ¶5:根据 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, 42 is 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.因此, 42被解释为int ,如果将这个常量直接分配给void* (当sizeof(void*)!=sizeof(int) ),上面的警告会弹出,但每个人都想要干净的编译。 This is the problem (issue?) the Glib doc is pointing to: it happens on some systems.就是Glib 文档指出的问题(问题?):它发生在某些系统上。


So, two issues:所以,两个问题:

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

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 ), gcc only shows the warning for 2.令我感到奇怪的是,即使这两种情况在 C 标准和 gcc 实现说明(请参阅gcc 实现说明)中具有相同的状态, gcc只显示 2 的警告。

On the other hand, it is clear that casting to long is 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 long and void* , for 64bits architectures and in general .另一方面,很明显,转换为long并不总是解决方案(仍然,在大多数情况下,在现代 ABI sizeof(void*)==sizeof(long) ),有许多可能的组合取决于大小int , long , long longvoid* ,适用于64 位架构一般情况 That is why glib developers try to find the matching integer type for pointers and assign glib_gpi_cast and glib_gpui_cast accordingly for the mason build system.这就是为什么 glib 开发人员试图为指针找到匹配的整数类型并为mason构建系统相应地分配glib_gpi_castglib_gpui_cast Later, these mason variables are used in here to generate those conversion macros the right way (see also this for basic glib types).稍后,这些mason变量在此处用于以正确的方式生成这些转换宏(对于基本 glib 类型,另请参阅此内容)。 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.最终,这些宏首先将一个整数转换为另一个与void*大小相同的整数类型(这种转换符合标准,没有警告)用于目标架构。

This solution to get rid of that warning is arguably a bad design that is nowadys solved by intptr_t and uintptr_t , but it is posible it is there for historical reasons: intptr_t and uintptr_t are available since C99 and Glib started its development earlier in 1998, so they found their own solution to the same problem.这个摆脱警告的解决方案可以说是一个糟糕的设计,现在已经被intptr_tuintptr_t解决了,但它有可能是由于历史原因而存在的: intptr_tuintptr_t是可用的,因为 C99和 Glib在 1998 年就开始了它的开发,所以他们为同一问题找到了自己的解决方案。 It seems that there were some tries to change it:似乎有一些尝试改变它:

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 better to why this warning is bad (and is it a good idea to silence it? ).简而言之,正如我所看到的,最初的问题已经从为什么这段代码更好变成了为什么这个警告不好(并且让它静音是个好主意吗? )。 The later has been answered somewhere else, but this could 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.但是,正如我上面所说,这条规则似乎不符合上述问题 1 的警告条件。 Maybe someone else could shed some light on this topic.也许其他人可以对这个话题有所了解。

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.我对这种行为背后的基本原理的猜测是 gcc 决定在原始值以某种方式改变时发出警告,即使是微妙的。 As gcc doc says (emphasis mine):正如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_t is just a typedef of the appropriate qualified integer: it is not justifiable to throw a warning when assigning [u]intptr_t to void* since it is indeed its purpose.此外, [u]intptr_t只是适当限定整数typedef :在将[u]intptr_t分配给void*时抛出警告是没有[u]intptr_t ,因为这确实是它的目的。 If the rule applies to [u]intptr_t , it has to apply to typedef ed integer types.如果规则适用于[u]intptr_t ,则它必须适用于typedef ed 整数类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM