Это неправильно / опасно, чтобы тип перечислялся как недействительный *? - PullRequest
4 голосов
/ 25 февраля 2010

Весь код написан на ANSI C, и так и должно быть. У меня есть обратный вызов, определенный так:

typedef enum {
    Event_One,
    Event_Two,
    Event_State
} EventEnum;

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

Получатель обратного вызова интерпретирует data в зависимости от значения event. Это контракт между компонентами. Иногда это указатель на структуру, иногда это может быть строка, в других случаях это могут быть другие данные. Я определяю дополнительный 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 и наоборот? Каковы возможные ошибки на этом пути? Есть мысли по поводу тестируемости и ремонтопригодности?

РЕДАКТИРОВАТЬ: Код компилируется, ссылки и работает нормально сейчас. Я хочу знать, почему этого не следует делать и есть ли реальные причины, по которым код должен быть изменен.

Ответы [ 4 ]

5 голосов
/ 25 февраля 2010

Только указатели на объекты (т.е. не функции) могут быть преобразованы в void * и обратно. Вы не можете конвертировать не указатель в void * и обратно. Итак, измените ваш звонок на:

infoCallback(Event_State, &someState);

И ваша функция:

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

Из стандарта (6.3.2.3):

Целое число может быть преобразовано в любой тип указателя. За исключением случаев, указанных ранее, результат определяется реализацией, может быть неправильно выровнен, может не указывать на объект ссылочного типа и может быть представлением прерывания.

Любой тип указателя может быть преобразован в целочисленный тип. За исключением случаев, указанных ранее, результат определяется реализацией. Если результат не может быть представлен в целочисленном типе, поведение не определено. Результат не обязательно должен находиться в диапазоне значений любого целочисленного типа.

Итак, то, что вы делаете, определяется реализацией. Если ваша реализация определяет, что все в порядке с int для преобразования в указатель и обратно, тогда код будет работать. Как правило, , он не переносимый. Подробнее см. в этой теме на comp.lang.c.

C99 дополнительно определяет типы intptr_t и uintptr_t, которые являются целочисленными типами, и гарантированно конвертирует void * в них и обратно.

0 голосов
/ 25 февраля 2010

Хотя компилятор позволяет это, и в некоторых случаях это может быть хорошо, вам нужно быть очень осторожным с временем жизни объектов.

Основная проблема, которую я вижу, состоит в том, что, если функция processEvent не находится в той же цепочке вызовов, что и infoCallback, исходная переменная someState может выйти из области видимости. Поэтому позже, когда вы ссылаетесь на infoCallback, void * будет неопределенным.

0 голосов
/ 25 февраля 2010

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) {
0 голосов
/ 25 февраля 2010

То, что вы делаете, определяется реализацией. Нет никаких гарантий, что перечисление будет иметь идентичное значение после преобразования в точку-пустоту, а затем преобразовано обратно. Если вам действительно нужно быть уверенным, вы должны использовать intptr_t или uintptr_t вместо enum. Они гарантированно имеют одинаковое значение после приведения к указателю на void и обратно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...