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

时间:2011-10-19 14:30:55

标签: c pthreads posix c99

这个问题几乎与我发现的其他一些问题重复,但这个问题特别涉及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。例如this manpage就有这样一个例子(参见完整代码的链接):

//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的并行编程简介)中看到过类似的例子。考虑到它似乎是一个人们应该比我更了解这些东西的常用例子,我想知道我是不是错了,这实际上是一个安全和便携的事情。

5 个答案:

答案 0 :(得分:13)

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

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

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

<stdint.h>的POSIX说明intptr_tuintptr_t必须至少 16 位,这与C标准规定的要求相同。由于void*可以转换为intptr_t而不会丢失信息,因此这意味着void*可能只有16位。将其与int至少为32位的POSIX要求(以及long至少为32位的POSIX和C要求)相结合,并且void*可能只是'n'大到足以保持intlong值而不会丢失信息。

pthread_create()的POSIX描述与此并不矛盾。它只是说argvoid*的{​​{1}}第四个参数)被传递给pthread_create()。据推测,意图是start_routine() 指向 arg可以使用的某些数据。 POSIX没有显示start_routine()

用法的示例

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

答案 1 :(得分:6)

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

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

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

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

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

答案 2 :(得分:3)

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

答案 3 :(得分:2)

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

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

static const char dummy_base[65535];

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

答案 4 :(得分:1)

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

  

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

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