Как проверить, можно ли безопасно привести указатель void* к чему-то другому?


Допустим, у меня есть эта функция, которая является частью некоторого инструментария gui:

typedef struct _My_Struct My_Struct;
/* struct ... */

void paint_handler( void* data )
{
   if ( IS_MY_STRUCT(data) ) /* <-- can I do something like this? */
   {
      My_Struct* str = (My_Struct*) data;
   }
}

/* in main() */
My_Struct s;
signal_connect( SIGNAL_PAINT, &paint_handler, (void*) &s ); /* sent s as a void* */

Поскольку paint_handler также будет вызываться главным циклом GUI toolkit с другими аргументами, я не всегда могу быть уверен, что параметр, который я получаю, всегда будет указателем на s.

Могу ли я сделать что-то вроде IS_MY_STRUCT в функции paint_handler, чтобы проверить, что параметр, который я получаю, можно безопасно вернуть в My_Struct* ?

6 4

6 ответов:

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

К сожалению, нет функции для проверки того, что указатель был до того, как он появился в этом контексте (void).

Единственное решение, которое я могу придумать, это если вы поместите int _struct_id в качестве первого члена всех ваших структур. Этот id-член можно безопасно проверить независимо от типа, но это не сработает, если вы передадите указатели, которые не реализуют этот член (или int, char,... указатели).

Лучшее, что вы можете сделать, - это посмотреть, на что указывают данные, чтобы увидеть, есть ли у них признаки того, что вы хотите, хотя А) это не будет близко к гарантии и Б) может быть опасно, поскольку вы не знаете, насколько велика вещь, на которую указывают данные. Я полагаю, что это не более опасно, чем просто бросать его и использовать, но (как было предложено) было бы лучше изменить дизайн.

На самом деле нет в c. void указатели не имеют типа и должны быть приведены только тогда, когда вы действительно знаете, на что они указывают.

Возможно, вместо этого вам следует пересмотреть свой дизайн; перепишите свой код так, чтобы не было необходимости в проверке. Это та же причина, по которой google запрещает RTTI в своем Руководстве по стилю .

Если вы создаете используемый тип, вы можете включить в него некоторую идентифицирующую информацию, которая поможет вам исключить некоторые пустые указатели как не относящиеся к типу, который вы ищете. Хотя у вас есть шанс, что какая-то случайная область памяти будет содержать те же данные или сигнатуру, что и то, что вы ищете, по крайней мере, вы будете знать, когда что-то не было тем типом, который вы искали.

Этот подход потребует, чтобы структура была инициализируется таким образом, что члены сигнатуры, используемые для определения недопустимости области памяти, инициализируются значением сигнатуры.

Пример:

typedef struct {
    ULONG  ulSignature1;
    //  .. data elements that you want to have
    ULONG  ulSignature2;
} MySignedStruct;
#define MYSIGNEDSTRUCT_01  0x1F2E3D4C
#define MYSIGNEDSTRUCT_02  0xF1E2D3C4

#define IS_MY_STRUCT(sAdr)  ( (((MySignedStruct *)sAdr)->ulSignature1 == MYSIGNEDSTRUCT_01  ) && (((MySignedStruct *)sAdr)->ulSignature1 == MYSIGNEDSTRUCT_02))
Это своего рода грубый подход, однако он может помочь. Естественно, использование макроса типа IS_MY_STRUCT(), где аргумент используется дважды, может быть проблематичным, если аргумент имеет побочный эффект, поэтому вам придется быть осторожным с чем-то вроде IS_MY_STRUCT(xStruct++), где xStruct является указателем на MySignedStruct.

Я знаю, что этому вопросу 3 года, но вот я иду., Как насчет использования простого глобального перечисления, чтобы отличить, откуда вызывается функция? затем вы можете переключаться между тем, к какому типу привести указатель void.