Как проверить, является ли указатель void (void *) одним из двух типов данных? - PullRequest
9 голосов
/ 08 октября 2019

Я пишу функцию, в которой я хотел бы принять 2 type s параметров.

  • A string (char *)
  • A structure, гдетам будет n элементов.

И чтобы достичь этого, я думаю об использовании простого void * в качестве типа параметра. Но я не знаю, как безопасно проверить, является ли параметр того или иного типа.

Ответы [ 2 ]

9 голосов
/ 08 октября 2019

Перевод void* -
«Уважаемый компилятор, это указатель, и для вас нет дополнительной информации по этому вопросу.».

Обычно компилятор знает лучше вас (программист), из-за информации, которую он получил раньше и до сих пор помнит, и вы, возможно, забыли о ней.
Но в этом особом случае вы знаете лучше или вам нужно знать лучше. Во всех случаях void* информация доступна иным образом, но только для программиста, который "случайно знает". Для этого программист должен предоставить информацию компилятору - или, что лучше, запущенной программе, потому что одно преимущество void* состоит в том, что информация может изменяться во время выполнения.
Обычно это делается путем передачи информации через дополнительныепараметры для функций, иногда через контекст, то есть программа «случайно узнает» (например, для каждого возможного типа существует отдельная функция, в зависимости от того, какая функция вызывается, подразумевает тип).

Итак, в конце void*не содержит информацию о типе.
Многие программисты неправильно понимают это как «Мне не нужно знать информацию о типе».
Но верно и обратное, использование void* увеличивает обязанность программиста отслеживать информацию о типе и предоставлять ее соответствующим образом программе / компилятору.

3 голосов
/ 08 октября 2019

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

Вместо этого вам следует использовать C11 _Generic, который может проверять типыво время компиляции и добавить безопасность типов. Пример:

#include <stdio.h>

typedef struct
{
  int n;
} s_t; // some struct

void func_str (const char* str)
{
  printf("Doing string stuff: %s\n", str);
}

void func_s (const s_t* s)
{
  printf("Doing struct stuff: %d\n", s->n);
}

#define func(x) _Generic((x),              \
  char*: func_str, const char*: func_str,  \
  s_t*:  func_s,   const s_t*:  func_s)(x) \


int main()
{
  char str[] = "I'm a string";
  s_t s = { .n = 123 };

  func(str);
  func(&s); 
}

Не забудьте предоставить квалифицированные (const) версии всех типов, которые вы хотите поддерживать.


Если вы хотите, чтобы ошибки компилятора были лучше, когда вызывающий передает неверный кодтипа, вы можете добавить статический assert:

#define type_check(x) _Static_assert(_Generic((x), \
  char*:   1,  const char*: 1,  \
  s_t*:    1,  const s_t*:  1,  \
  default: 0), #x": incorrect type.")

#define func(x) do{ type_check(x); _Generic((x),     \
  char*: func_str, const char*: func_str,            \
  s_t*:  func_s,   const s_t*:  func_s)(x); }while(0) 

Если вы попробуете что-то вроде int x; func(x);, вы получите сообщение компилятора "x: incorrect type".

...