关于在 C 中将 `void *` 转换为 `int` 的问题
Posted
技术标签:
【中文标题】关于在 C 中将 `void *` 转换为 `int` 的问题【英文标题】:Question about converting `void *` to `int` in C 【发布时间】:2019-04-19 19:25:22 【问题描述】:我正在尝试再次选择我的 C 技能。我想对不同线程中的序列求和,每个线程将返回一个序列的一部分总和的指针。但是,当我尝试将void*
类型值local_sum
转换为int
时,出现了问题。
我尝试使用sum += *(int*)local_sum;
进行转换,发生了段错误,我得到了Process finished with exit code 11
。
我发现如果我使用sum += (int)local_sum;
,就可以了。但我无法说服自己:local_sum
不应该是void *
吗?为什么可以用(int)local_sum
转换成int
?
非常感谢你能回答这个问题。
对每个进程的返回值求和的部分在这里:
int sum = 0;
for (int i = 0; i < NUM_THREADS; i ++)
void * local_sum;
pthread_join(count_threads[i], (&local_sum));
sum += (int)local_sum;
线程的功能在这里:
void * count_thr(void *arg)
int terminal = ARRAY_SIZE / NUM_THREADS;
int sum = 0;
for (int i = 0; i < terminal; i ++)
sum += *((int*)arg + i);
return (void*)sum;
【问题讨论】:
在pthreads的void*
中传递int
,而不是指向一个,一直是一种肮脏的黑客行为。就像所有肮脏的黑客一样,它们最终会崩溃。整数到指针的转换具有实现定义的行为(至少使用 uintptr_t)。解决方案不是使用脏黑客,而是传递一个指向 malloc:ed 数据的指针。与创建和关闭线程的成本相比,动态分配的开销执行时间算不了什么。
参考c89: Convert an int to void* and back和链接问题Is it safe to cast an int to void pointer and back to int again?
【参考方案1】:
通过设置void *
地址,您可以return
获取int sum
的值。在这种情况下,地址无效。但是,如果您牢记这一点并通过将void *
转换为int
来获得sum
的值,它将起作用。
void *
有时以这种方式用于return
值(例如int
)或某物的地址(例如struct
)。
为了说明这一点:
int a = 5;
void *p = (void *)a;
int b = (int)p;
a
、p
和 b
的值都为 5
。 p
未指向有效地址。尝试取消引用 p
会导致未定义的行为:
b = *(int *)p; // Undefined Behavior!
考虑以下程序:
#include <limits.h>
#include <stdio.h>
int main(void)
int a, b;
void *p;
a = 5;
p = (void *)a;
b = (int)p;
printf("%d %p %d\n", a, p, b);
a = INT_MAX;
p = (void *)a + 1;
b = (int)p;
printf("%d %p %d\n", a, p, b);
return 0;
编译时,我收到以下警告:
$ gcc main.c -o main.exe main.c:在函数“main”中: main.c:9:9:警告:从不同大小的整数转换为指针 [-Wint-to-pointer-cast] p = (void *)a; ^ main.c:10:9:警告:从指针转换为不同大小的整数 [-Wpointer-to-int-cast] b = (int)p; ...发出警告是因为,正如@Gerhardh 所指出的,sizeof(int)
和sizeof(void *)
可能不同。如果void *
的值超过int
可以容纳的最大值,您可能会遭受数据丢失。
输出
$ ./main.exe 5 0x5 5 2147483647 0x80000000 -2147483648【讨论】:
" 在我们的例子中,这种行为是需要的。"真的吗?这不仅是关于转换的警告,而且是关于不同大小的。这不是我们想要的部分。你可能有 4 字节整数和 8 字节指针。 注意这个行为是实现定义的 a、p 和 b 的值都为 5。a
和 b
不保证具有相同的值。
@DavidBrown 当然,这是未定义的行为。在我的机器上,这种行为是如此。
@FiddlingBits :更正:它是implementation defined,正如我之前所说的。正如您所描述的,它可以在许多(如果不是大多数)平台上运行,但不能保证。【参考方案2】:
你不能这样做 *(int*)local_sum
因为 local_sum
不是 int*
转换为 void*
。 local_sum
是 int
转换为 void*
。它是一个重新解释为地址的数字,但仅用于传输目的,因为pthread_exit
只允许您返回void*
,而不是int
,并且因为标准明确允许实现定义的转换(6.3.2.3p5,@ 987654322@) 在整数和数字之间,只要值合适(如果不合适,则 UB)。如果你返回,例如0x42
,那么地址0x42
处不太可能有任何东西,所以你应该忘记取消引用它,而是应该尽快将它转换回整数,或者使用(int)local_sum;
,或者使用(int)local_sum;
可能更好(int)(intptr_t)local_sum;
(虽然intptr_t
不保证存在)或(也许是最好的)(int)(intmax_t)local_sum;
,以避免在 LP64 平台上转换为不同大小的整数时可能出现的编译器警告。
【讨论】:
【参考方案3】:一个安全且可移植的解决方案可能是使用联合:
union void_cast
void* ptr;
int value;
;
然后,例如,您可以安全地重新解释 void*
指针:
int VOID_TO_INT(void* ptr)
union void_cast u;
u.ptr = ptr;
return u.value;
void* INT_TO_VOID(int value)
union void_cast u;
u.value = value;
return u.ptr;
所以你的代码可以改成:
sum += VOID_TO_INT(local_sum);
【讨论】:
无论字节顺序如何,转换都应该有效。在一个(非常理论上的)大端机器上,整数是指针宽度的一半,联合方法将无法提取值,而强制转换则不应该。以上是关于关于在 C 中将 `void *` 转换为 `int` 的问题的主要内容,如果未能解决你的问题,请参考以下文章
如何在目标 c 中将 NSString 转换为 NSDictionary?
void Test::printxy(void)' : 不能在 const 类中将 'this' 指针从 'const Test' 转换为 'Test &'