代码之家  ›  专栏  ›  技术社区  ›  Ignas Limanauskas

将枚举类型转换为void*是否错误/危险?

  •  4
  • Ignas Limanauskas  · 技术社区  · 14 年前

    整个代码是用ANSIC编写的,应该保持这样。 我有一个回调定义如下:

    typedef enum {
        Event_One,
        Event_Two,
        Event_State
    } EventEnum;
    
    typedef void (*callback)(EventEnum event, void* data);
    

    回调接收者解释 data 取决于 event 值。这是组件之间的合同。有时它是指向结构的指针,有时它可能是字符串,其他情况可能是其他数据。我正在定义一个附加的 事件 建立一个新的“合同” 数据 是枚举。就像这样:

    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 反之亦然?这种方式有什么可能?对可测试性和可维护性有什么想法吗?

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

    4 回复  |  直到 11 年前
        1
  •  5
  •   Alok Singhal    14 年前

    只有指向对象(即,不是函数)的指针才能转换为 void * 然后回来。不能将非指针转换为 无效* 然后回来。因此,将您的呼叫更改为:

    infoCallback(Event_State, &someState);
    

    你的职责是:

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

    根据标准(6.3.2.3):

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

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

    所以,您要做的是定义实现。如果您的实现将其定义为可以转换 int 指向指针,然后返回,代码就可以工作了。 一般来说 ,它不是便携式的。有关详细信息,请参阅 this thread 在comp.lang.c.

    C99另外定义了类型 intptr_t uintptr_t ,它是整型的,并且保证将 空洞* 给他们和回来。

        2
  •  0
  •   user261840    14 年前

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

        3
  •  0
  •   caf    14 年前

    alok绝对正确,整数和指针之间的转换是实现定义的,因此完全不可移植。例如,如果 void * 整数值的值总是产生0(在不提供 intptr_t uintptr_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) {
    
        4
  •  0
  •   BenMorel Sonaten    11 年前

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

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