Приведение указателя функции или параметров при использовании интерфейса - PullRequest
0 голосов
/ 20 июня 2019

Я определил интерфейс:

typedef DataExchg_Status_T (*DataExchg_Iface_ReceiveDataClbk_T)(
        void * p_self, uint8_t const * p_data, size_t size);

typedef struct DataExchg_Iface_Tag {
    DataExchg_Status_T (*SendData)(void * p_self, uint8_t const * p_data, size_t size);
    void (*RegisterReceiveDataClbk)(void * p_self,
                                    DataExchg_Iface_ReceiveDataClbk_T receive_data_clbk,
                                    void * p_receiver_instance);
    void * pSelf;
} DataExchg_Iface_T;

, и я использую его в Object_A.

typedef struct Object_A_Tag {
     DataExchg_Iface_T * pIface;
} Object_A_T;

В Object_B модуль


У меня есть реализация этого интерфейса:

typedef struct Object_B_Tag {
     int value;
     ...
} Object_B_T;

DataExchg_Status_T SendData(Object_B_T * p_self, uint8_t const * p_data, size_t size) {
    ...
}

void RegisterReceiveDataClbk(Object_B_T * p_self,
                             DataExchg_Iface_ReceiveDataClbk_T receive_data_clbk,
                             void * p_receiver_instance) {
    ...
}

Все в порядке, пока я не хочу назначить вышеуказанные функциик интерфейсу в Object_A:

Object_B object_b = {...};

Object_A object_a = {
    .pIface = &(DataExchg_Iface_T){
        .SendData = SendData,
        .RegisterReceiveDataClbk = RegisterReceiveDataClbk,
        .pSelf = &object_b
    }
};

Проблема в том, что я получаю предупреждение о несовместимом указателе, потому что один из параметров: void * p_self не равен Object_B_T * p_self в реализации интерфейса.

Есть несколько возможных решений этой проблемы:

1.Приведение к указателю на интерфейсную функцию:

SendData = (DataExchg_Status_T(*)(void *, DataExchg_Iface_ReceiveDataClbk_T, void *)SendData

Это наиболее удобное решение, но существуют убедительные доказательства того, что это решение может привести к неопределенному поведению: Приведение указателя функции к другому типу

2.Реализуйте интерфейс точно так, как объявлено, и приведите параметр в теле функции:

Это самое безопасное решение, но не самое удобное.

DataExchg_Status_T SendData(void * p_self, uint8_t const * p_data, size_t size) {
    Object_B_T pSelf = p_self;
}

3.Приведение к указателю на интерфейсную функцию при инициализации интерфейса (решение 1) и приведение интерфейсной функции к реализованному типу каждый раз, когда я его использую:

Насколько мне известно, это решение не должно приводить к неопределенному поведению.

object_b.pIface->(DataExchg_Status_T(*)(Object_B *, DataExchg_Iface_ReceiveDataClbk_T, void *)SendData(...)

И, наконец, вопросы:

1) Может ли решение 1 действительно привести к неопределенному поведению в моем случае?

2) Есть лиЛюбое решение, где я могу иметь общее объявление интерфейса с указателем экземпляра Void и конкретную реализацию с указателем экземпляра Specyfic?(аналогично решению 1)

1 Ответ

0 голосов
/ 20 июня 2019

Ваш второй вариант, изменение определения функции в соответствии с типом указателя и преобразование void * в правильный тип, это правильный и предпочтительный способ реализации обратного вызова, такого как этот.

В качестве примера рассмотрим справочную страницу для qsort. Функция объявлена ​​так:

   void qsort(void *base, size_t nmemb, size_t size,
              int (*compar)(const void *, const void *));

И это дает пример функции обратного вызова:

static int
cmpstringp(const void *p1, const void *p2)
{
    /* The actual arguments to this function are "pointers to
       pointers to char", but strcmp(3) arguments are "pointers
       to char", hence the following cast plus dereference */

   return strcmp(* (char * const *) p1, * (char * const *) p2);
}

Как видите, в этом примере void * приводится к правильному типу, чтобы использовать его. Это обычная идиома для функций обратного вызова языка C.

...