【问题标题】:Properly casting a `void*` to an integer in C++在 C++ 中正确地将 `void*` 转换为整数
【发布时间】:2015-08-26 10:27:29
【问题描述】:

我正在处理一些使用外部库的代码,您可以在其中通过 void* 值将值传递给回调。

不幸的是,之前编写此代码的人决定通过将整数强制转换为 void 指针 ((void*)val) 将整数传递给这些回调。

我现在正在清理这个烂摊子,我正在尝试确定将整数转换为/从void* 的“正确”方式。不幸的是,修复 void 指针的 use 有点超出了我在这里可以做的返工的范围。

现在,我正在执行两次强制转换以从/转换为 void 指针:

static_cast<int>(reinterpret_cast<intptr_t>(void_p))

reinterpret_cast<void *>(static_cast<intptr_t>(dat_val))

由于我在 64 位机器上,直接投射 ((int)void_p) 会导致错误:

error: cast from 'void*' to 'int' loses precision [-fpermissive]

最初的实现确实-fpermissive一起工作,但我试图摆脱可维护性和与错误相关的问题,所以我试图“正确”地做到这一点,例如c++ 强制转换。

直接转换为 int (static_cast&lt;int&gt;(void_p)) 失败 (error: invalid static_cast from type 'void*' to type 'int')。我对reinterpret_cast 的理解是,它基本上只是导致编译器将有问题的值的地址视为转换为数据类型而不实际发出任何机器代码,因此将int 直接转换为void*这是个坏主意,因为void*int 大(分别为4/8 字节)。

认为使用intptr_t 是正确的中间值,因为它保证足够大以包含void* 的整数值,一旦我有一个整数值,我就可以在不引起编译器抱怨的情况下截断它。

鉴于我不得不通过 void 指针推送数据,这是正确的,甚至是理智的方法吗?

【问题讨论】:

  • 使用 unsigned long long intuint64_t,使其适合 64 位长度的指针。
  • 所以,您是通过void* 来回往返int,反之亦然,对吧?
  • 那么使用intptr_t 真的解决了关于演员阵容错误的问题吗?事实上,它是正确使用的类型。
  • @Deduplicator - 是的。以int 开头,以int 结尾。它必须通过一个只接受 void* 的库,尽管它不会改变它们(它是一个带有 C++ 包装器的 C 库,这就是它喜欢 void* 的原因)。
  • @πάνταῥεῖ - 是的,它适用于intptr_t,只是我不确定我是否从正确的角度解决了这个问题。 this question 似乎并不暗示 intptr_t 在这里是一个很好的方法(否则它有什么用?),但我发现的大多数其他解决方案都涉及大量返工,我真的很不舒服考虑到现有代码库的大小。

标签: c++ pointers casting


【解决方案1】:

根据您的问题,我假设您调用某个库中的一个函数,并将其传递给 void*,然后在稍后的某个时间点,它调用您的一个函数,并将其传递给相同的 void*

基本上有两种可能的方法来做到这一点;第一种是通过显式转换,正如您在当前代码中所展示的那样。

Deduplicator 提到的另一个效率稍低,但允许您保持对数据的控制,并可能在调用库函数和调用回调函数之间进行修改。这可以通过类似的代码来实现:

void callbackFunction(void* dataPtr){
    int data = *(int*)dataPtr;
    /* DO SOMETHING WITH data */
    delete dataPtr;
}
void callLibraryFunction(int dataToPass){
    int* ptrToPass = new int(dataToPass);
    libraryFunction(ptrToPass,callbackFunction);
}

您应该使用哪一个取决于您需要对数据执行什么操作,以及修改数据的能力在未来是否有用。

【讨论】:

    【解决方案2】:

    我认为在这里使用intptr_t 是正确的中间值,因为它保证足够大以包含void* 的整数值,并且一旦我有一个整数值,我就可以截断它而不会导致编译器抱怨。

    是的,出于您提到的原因,这是正确的中间类型。到目前为止,如果您的实现不提供它,您可能会遇到更多的问题,而不仅仅是缺少 typedef。

    鉴于我不得不通过 void 指针推送数据,这是正确的,甚至是理智的方法吗?

    是的,考虑到限制,这很正常。
    您可能会考虑检查值是否合适,而不是在调试模式下从 void* 解包时简单地截断它,或者甚至对该整数的所有进一步处理都使用 intptr 而不是 int 以避免截断。

    您还可以考虑通过该参数将指针推送到实际的int 而不是int 本身。但请注意,这种方法效率较低,并且会让您面临终身问题。

    【讨论】:

    • 这些值基本上是代码中的整数常量,用于在回调中选择进一步的函数调用。它们是在编译时定义的,在任何情况下都不会超过 10 个。至于为什么(显然有点疯狂)原作者不只是为每个选项注册不同的回调(甚至定义了函数!)我不知道。
    【解决方案3】:

    “鉴于我不得不通过 void 指针推送数据,这是正确的,甚至是理智的方法吗?”

    嗯,关于 正确sane 值得商榷,尤其是如果您是代码的作者,在界面中使用 void*

    我认为在这里使用intptr_t 是正确的中间值,因为它保证足够大以包含 void* 的整数值,并且一旦我有一个整数值,我就可以截断它而不会导致编译器抱怨.

    是的,这是与 reinterpret_cast&lt;intptr_t&gt; 一起使用的正确类型,但您需要确保已传入 intptr_t 指针类型,并且地址有效且不会超出范围。


    在与 API 交互时,偶然发现这个问题并不少见,这些 API 提供 callbacks,允许您传入 user-data ,这将由他们透明地处理,并且永远不会被触及,除了你的入口点1

    因此,由客户端代码确定应该如何安全重新解释 void*


    1)这种情况的典型例子是pthread_create()函数

    【讨论】:

      【解决方案4】:

      你别无选择,只能在这里使用静态和重新解释演员表。转换为 int 会导致精度损失,这绝不是理想的。最好避免显式转换,因为转换的内容迟早会发生变化,届时不会出现编译器警告。但在这种情况下,你别无选择,这是可以理解的。还是你?

      您可以将您这边的回调定义更改为 intptr_t 或 long int 而不是 void*,然后它应该可以工作并且您不必进行任何类型转换...

      【讨论】:

        猜你喜欢
        • 2010-12-14
        • 1970-01-01
        • 1970-01-01
        • 2021-06-21
        • 2020-10-24
        • 2020-01-04
        • 1970-01-01
        • 2011-09-28
        • 2012-03-21
        相关资源
        最近更新 更多