将整数值转换为void *并在POSIX中再次返回是否总是安全的?

这个问题几乎与我发现的其他一些问题重复,但这个问题特别涉及POSIX,这是我在pthreads中遇到的一个非常常见的例子。 我主要关心当前的事态(即C99和POSIX.1-2008或更高版本),但任何有趣的历史信息当然也很有趣。

问题基本上归结为b是否总是采用与以下代码中的a相同的值:

long int a = /* some valid value */ void *ptr = (void *)a; long int b = (long int)ptr; 

我知道这通常有效,但问题是它是否是一个正确的事情(即,C99和/或POSIX标准是否保证它将起作用)。

说到C99似乎没有,我们有6.3.2.3:

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

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

即使使用intptr_t,标准似乎只能保证任何有效的void *都可以转换为intptr_t并再次返回,但它不能保证任何intptr_t都可以转换为void *并再次返回。

但是,POSIX标准仍然可以允许这样做。

我没有很大的愿望使用void *作为任何变量的存储空间(即使POSIX应该允许它我觉得它很难看),但我觉得我必须要问因为pthreads_create函数的常见例子使用了start_routine的参数是一个整数,它作为void *传入,并在start_routine函数中转换为int或long int。 例如, 此联机帮助页有这样的示例(请参阅完整代码的链接):

 //Last argument casts int to void * pthread_create(&tid[i], NULL, sleeping, (void *)SLEEP_TIME); /* ... */ void * sleeping(void *arg){ //Casting void * back to int int sleep_time = (int)arg; /* ... */ } 

我也在教科书(Peter S. Pacheco的并行编程简介)中看到了类似的例子。 考虑到它似乎是一个常见的例子,人们应该比我更了解这些东西,我想知道我是不是错了,这实际上是一个安全和便携的事情。

正如您所说,C99并不保证任何整数类型都可以转换为void*而不会丢失信息。 它确实对定义的intptr_tuintptr_t提供了类似的保证,但这些类型是可选的。 (保证void*可以转换为{u,}intptr_t并返回而不会丢失信息;对任意整数值都没有这样的保证。)

POSIX似乎也没有做出任何此类保证。

的POSIX描述要求intunsigned int至少为32位。 这超出了C99要求,它们至少为16位。 (实际上,要求是按范围而不是大小,但效果是intunsigned int必须至少为32(在POSIX下)或16(在C99下)位,因为C99需要二进制表示。)

的POSIX描述表明intptr_tuintptr_t必须至少为16位,这与C标准规定的要求相同。 由于void*可以转换为intptr_t而不会丢失信息,这意味着void*可能小到16位。 将它与POSIX要求结合起来,即int至少为32位(以及长度至少为32位的POSIX和C要求),并且void*可能不足以容纳intlong值而没有失去信息。

pthread_create()的POSIX描述与此不矛盾。 它只是说argpthread_create()void* 4th参数)被传递给start_routine() 。 据推测,意图是arg 指向 start_routine()可以使用的一些数据。 POSIX没有显示arg用法的示例。

你可以在这里看到POSIX标准; 您必须创建一个免费帐户才能访问它。

到目前为止,答案中的焦点似乎是指针的宽度,实际上正如@Nico指出的那样(@Quantumboredom也在注释中指出), intptr_t可能比指针宽。 @Kevin的回答暗示了另一个重要问题,但没有完全描述它。

此外,虽然我不确定标准中的确切段落,但Harbison&Steele指出intptr_tuintptr_t也是可选类型,甚至可能不存在于有效的C99实现中。 OpenGroup表示符合XSI标准的系统必须支持两种类型,但这意味着普通POSIX不需要它们(至少在2003版本中)。

这里真正被遗漏的部分是指针不必总是具有与整数的内部表示匹配的简单数字表示。 这一直是如此(自1978年K&R以来),我很确定POSIX也小心不要否定这种可能性。

因此,C99确实要求可以将指针转换为存在类型的intptr_t IFF ,然后再返回指针,使新指针仍然指向内存中与旧指针相同的对象,实际上如果指针具有非整数表示,这意味着存在一种算法,该算法可以将一组特定的整数值转换为有效的指针。 但是,这也意味着INTPTR_MININTPTR_MAX之间的所有整数INTPTR_MIN INTPTR_MAX是有效的指针值, 即使intptr_t (和/或uintptr_t )的宽度与指针的宽度完全相同

因此,标准不能保证任何intptr_tuintptr_t都可以转换为指针并返回到相同的整数值,或者甚至哪个整数值集可以在这种转换中存活,因为它们不可能定义所有可能的规则和算法将整数值转换为指针值。 即使对于所有已知的体系结构,这样做仍然可能妨碍标准适用于尚未发明的新型体系结构。

(u)intptr_t只是保持足够大以容纳指针,但它们也可能“更大”,这就是为什么C99标准只保证(void *) – >(u)intptr_t – >(void *),但在另一种情况下,可能会发生数据丢失(并且被认为是未定义的)。

不确定你的意思是“永远”。 它没有写在标准的任何地方,这是可以的,但没有系统失败。

如果您的整数非常小(例如限制为16位),您可以通过声明:

 static const char dummy_base[65535]; 

然后传递dummy_base+i作为参数并将其恢复为i=(char *)start_arg-dummy_base;

我想你的答案在你引用的文字中:

如果结果无法以整数类型表示,则行为未定义。 结果不必在任何整数类型的值范围内。

所以,不一定。 假设您有一个64位long并在32位计算机上将其转换为void* 。 指针可能是32位,因此要么丢失前32位, INT_MAX返回INT_MAX 。 或者,可能是完全不同的东西(未定义,正如标准所说)。