关于在 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;

apb 的值都为 5p 未指向有效地址。尝试取消引用 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。 ab 不保证具有相同的值。 @DavidBrown 当然,这是未定义的行为。在我的机器上,这种行为是如此。 @FiddlingBits :更正:它是implementation defined,正如我之前所说的。正如您所描述的,它可以在许多(如果不是大多数)平台上运行,但不能保证。【参考方案2】:

你不能这样做 *(int*)local_sum 因为 local_sum 不是 int* 转换为 void*local_sumint 转换为 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?

在 C++ 中将指针转换为 volatile void**

void Test::printxy(void)' : 不能在 const 类中将 'this' 指针从 'const Test' 转换为 'Test &'

在c ++ 11中将std :: string转换为char * [重复]

在 C 和 C++ 中将 char 转换为 int

在本机 C++ 中将变量从 C# 编组为 void*,并在本机程序内更改 Managed/C# 中的变量值