【问题标题】:Safely cast void* to int安全地将 void* 转换为 int
【发布时间】:2014-11-07 10:58:07
【问题描述】:

如果编译应用程序以产生 x32 图像,则根据架构整数类型可能是 16 位宽、32 位宽或超过 2 字节的任何值。 void* 的大小将为 4(在 x32 上始终为 4???)。这意味着将int 传递给void* 很好,但如果事实证明在给定架构上void*int 宽(标准只保证至少2 个字节),则面对

C 标准 n1124 § 6.3.2.3 指针

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

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

void* 转换为int 可能会在以下sn-p 中产生未定义的行为。

typedef enum tagENUM
{
    WSO_1,
    WSO_2,
    //...
    WSO_COUNT
} ENUM;

/* I cannot change handler signature because this is callback. I have to cast void*
 * to ENUM however inside */
void handler( int i, int j, void *user_data)
{
    ENUM mOperation;
    mOperation = (ENUM)reinterpret_cast<int>(user_data);
}

// somewhere
handler( 1, 2, (void*)WSO_1);  // UB? We can imagine that someone passes to handler
                               // (void*)WSO_131072 which don't fit into 16 bits
                               // So is there a place opened for UB?

如果这是正确的,那么鼻恶魔的可能性就打开了 - 那么我该如何安全地编写这个演员表?我可以使用intptr_t 来确保结果合适吗?

void handler( int i, int j, void *user_data)
{
    ENUM mOperation;

uintptr_t p_mOperation = reinterpret_cast<uintptr_t>( user_data);

if ( p_mOperation > WSO_COUNT ) {
    send_error(conn, 500, http_500_error, "Error: %s", strerror(ERRNO));
    return;
}

mOperation = static_cast<ENUM_WS_OPERATION>( p_mOperation);  // now safe?

【问题讨论】:

  • void* 参数旨在直接传递数据。只需传递您的整数 address 而不是值,就可以了。你正在尝试的东西永远不会正常工作,这是 x86 世界采用 64 位速度缓慢的主要原因。
  • 我知道我可以,但我的问题是关于这种情况 - 有没有 UB 的地方?我必须假设有人可以通过 void* 背后的任何东西 - 我如何防止 UB?
  • 引用的部分清楚地表明它是定义的实现
  • "void* 的大小将始终为 4?" - 不,并非总是如此。仅在具有 32 位内存地址空间的平台上。
  • 嗯,在这种情况下,答案是肯定的。但是请注意,C++ 标准不保证 int 的大小为 4(我个人使用的平台只有 2)。

标签: c++ casting int undefined-behavior void-pointers


【解决方案1】:

是的,它可能导致未定义的行为。如果您改用intptr_t,则没有未定义的行为。

但是,通常您可以重写代码,使指针指向预期的整数,而不是被强制转换为它。

在您的第二个示例中,您有一个混搭。您想使用intptr_t 或指向intvoid *。不是intptr_t *uintptr_t *

我首选的解决方案是user_data 始终指向数据;并且指向的数据的类型由被调用的处理程序或其他参数确定。

【讨论】:

  • 对不起,我不清楚。您必须使用intptr_tuintptr_t 作为handler 的参数类型。你仍然可以让 UB 做你现在所做的事情(因为 uintptr_t 可能比 void * 的合法值范围更广)
  • 我无法更改处理程序签名,因为这是回调。我必须将 void* 转换为 ENUM 但是在里面。此外,如果 uintptr_t 更宽,也可以。 void* 会适合它
  • 您投射到void * 的内容可能不会进入void *。如果您不能更改函数,那么正确的做法是实际传递一个指针;不要进行 reinterpret_cast。
【解决方案2】:

这一点:

mOperation = (ENUM)reinterpret_cast<int>(user_data);

表明你已经失去了对正在做的事情的控制。你正在做双重演员,这总是表明正在发生坏事(好像 reinterpret_cast 还没有表明这一点)。

目前尚不清楚您要做什么。当然 (void*) 是不适合传递非数据值的强制转换。它用于传递指向任意数据缓冲区的指针,希望另一端的东西可以从数据中计算出它需要做什么。

所以这个:

handler( 1, 2, (void*)WSO_1); 

完全是错误的。

但是,鉴于 WSO_1 实际上是 enum,而且我不知道在任何平台上 void * 实际上小于枚举,你应该可以这样做

mOperation = reinterpret_cast<ENUM>(user_data);

当客户将枚举转换为 void * 时,他已经进入了可疑区域,并且该转换不会使情况变得更糟。

【讨论】:

  • 不是我! ; p 我只是事态的无辜受害者,必须以某种方式应对。我知道这很丑!你也错在“鉴于 WSO_1 实际上是一个枚举,而且我不知道任何平台 void * 实际上小于枚举,你应该没问题” - 这实际上是我的问题 void* 可以大于 ENUM
  • @AB_ 虽然从问题来看,你可以控制handler
  • 我无法更改它的签名
  • 签名不需要双重转换。
  • 这不是我的代码,我知道这是错误的。它必须是 void*,我必须将其转换为 ENUM。如何安全地编写它,所以我可以检查 void* 参数是否真的适合 ENUM
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多