【问题标题】:Is it wrong / dangerous to typecast enum to void*?将枚举类型转换为 void* 是否错误/危险?
【发布时间】:2023-04-03 17:57:02
【问题描述】:

整个代码都是用 ANSI C 编写的,并且应该保持不变。 我有一个这样定义的回调:

typedef enum {
    Event_One,
    Event_Two,
    Event_State
} EventEnum;

typedef void (*callback)(EventEnum event, void* data);

回调接收者根据event 值解释data。这是组件之间的合同。有时它是指向结构的指针,有时它可能是一个字符串,其他情况可能是其他数据。我正在定义一个额外的event 并设置一个新的“合同”,data 是一个枚举。像这样:

typedef enum {
    State_Initial = 0,
    State_Running,
    State_Final
} StateEnum;

然后在代码的某个地方我有一个回调函数,它正在这样做

void ProcessEvent (EventEnum event, void* data)
{
    if (event == Event_State)
    {
         StateEnum state = (StateEnum)data; /* <<<<<<<<<<< */
         switch (state) {
         case State_Initial:
             <...>
             break;
         case State_Running:
             <...>
             break;
         case State_Final:
             <...>
             break;
         }
    }
}

上面的回调是这样调用的:

{
    callback infoCallback = ProcessEvent; /* This is only for example,
                                             done during initialization */
    <...>
    StateEnum someState = State_Running;
    <...>
    infoCallback(Event_State, (void*)someState); /* <<<<<<<<<<<<<<<<<<< */
}

void* 类型转换为StateEnum 有什么根本错误吗?反之亦然?这种方式有哪些可能的陷阱?对可测试性和可维护性有什么想法吗?

编辑: 代码现在可以编译、链接和运行。我想知道为什么不应该这样做,以及是否有任何必须更改代码的真正原因。

【问题讨论】:

  • 我不认为这样做有什么问题。它类似于通过采用 void* 的函数传递 int,而枚举在 C/C++ 中实际上是整数。我能看到的唯一问题是过多地使用 void* 使得代码难以扩展,因为它可以是任何东西(当它只有 10 种可能性时,仍然可以管理,但如果它有 100 种呢?)。
  • @YeenFei:将整数转换为指针是实现定义的,这意味着它不可移植。

标签: c type-conversion


【解决方案1】:

只有指向对象的指针(即不是函数)可以转换为 void * 并返回。您不能将非指针转换为 void * 并返回。所以,把你的电话改成:

infoCallback(Event_State, &someState);

你的功能是:

StateEnum *state = data;
switch (*state)
...

来自标准(6.3.2.3):

整数可以转换为任何指针类型。除非前面指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示。

任何指针类型都可以转换为整数类型。除非前面指定,结果是实现定义的。如果结果不能以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。

所以,您所做的是实现定义的。如果您的实现定义可以将int 转换为指针并返回,那么代码将起作用。 一般来说,它是不可移植的。有关详细信息,请参阅 comp.lang.c 上的this thread

C99 还定义了类型 intptr_tuintptr_t,它们是整数类型,并且保证将 void * 转换为它们并返回。

【讨论】:

  • 这难道不是 POSIX 2003 对dlsym 的技术更正的确切原因吗? dlsym 可用于在运行时动态查找函数指针,但 C99 标准规定将 void *dlsym 的返回值)转换为函数指针是未定义的,因此必须发布技术勘误来解释有效的解决方法。这可以在dlsym 的许多手册页中找到。
  • @dreamlax:你是对的。我刚刚在opengroup.org/onlinepubs/009695399/functions/dlsym.html 上找到了理由。感谢您的信息!
  • @BillyONeal:它可能在您的计算机上运行良好,但不能保证符合标准。尝试省略 (void *)(callback_t) 的演员表,你的编译器会警告你。现在试试int i; void *p = &amp;i; int *pi = p;,它不会警告你。不是没有演员表。
  • int i; void *p = &amp;i; int *pi = p; 无法编译。不兼容的指针类型之间的转换需要 C 样式转换或 reinterpret_cast
  • @BillyONeal:问题标记为 C,而不是 C++。
【解决方案2】:

您所做的是实现定义。不能保证枚举在转换为指向 void 然后再转换回来后将具有相同的值。如果你真的需要确定,你应该使用 intptr_t 或 uintptr_t 而不是枚举。在被强制转换为指向 void 的指针并再次返回后,它们保证具有相同的值。

【讨论】:

  • 这会起作用,但请注意 intptr_tuintptr_t 是可选的 - 符合要求的实现不需要提供它们。
【解决方案3】:

Alok 是绝对正确的,整数和指针之间的转换是实现定义的,因此完全不可移植。这似乎是完全允许的,例如,如果将所有 void * 值转换为整数值始终产生 0(在不提供 intptr_tuintptr_t 的实现上,无论如何)。

一个常见的习惯用法是扩展合约,使得回调将收到一个指向动态分配的枚举的指针,它必须释放它。在调用者中:

StateEnum *someState = malloc(sizeof *someState);
*someState = State_Running;

infoCallback(Event_State, someState);

...在回调中:

void ProcessEvent (EventEnum event, void* data)
{
    if (event == Event_State)
    {
         StateEnum state = *(StateEnum *)data;
         free(data);
         switch (state) {

【讨论】:

  • 这里的问题是 malloc 会带来性能开销,并且是潜在故障(内存不足)的附加点。回调调用者应该进行释放。
  • 如果可能,这绝对是更可取的(在这种情况下,您通常可以将union 类型用于data)。但是,通常情况下,data 是在回调注册时提供的,而回调实际上是在很久以后调用的——在最初注册回调的函数完成之后很久。这真的取决于你在做什么。
【解决方案4】:

尽管编译器允许这样做,并且在某些情况下可能没问题,但您需要非常小心对象的生命周期。

我可以看到的主要问题是,除非 processEvent 函数与 infoCallback 发生在同一调用链中,否则原始的 someState 变量可能会超出范围。所以稍后当你在 infoCallback 中引用 is 时,void* 将是未定义的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-02-21
    • 1970-01-01
    • 2010-12-21
    • 1970-01-01
    • 2012-09-30
    • 1970-01-01
    • 2010-10-11
    相关资源
    最近更新 更多