Как проверить, является ли указатель void (void *) одним из двух типов данных?

10

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

  • A string(символ *)
  • А structureгде будет n элементов.

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

локальный
источник
10
Ты не можешь! По крайней мере, вам нужно будет добавить второй параметр в функцию, который указывает на что void*указывает.
Адриан Моул
4
... и если у вас есть , чтобы добавить второй параметр в любом случае, вы можете просто написать, а две отдельные функции func_strи func_structи получить проверки типов во время компиляции.
M Oehm
Да, именно поэтому я подумал, возможно ли это только в одной функции
localhost
1
Вы не можете безопасно и портативно. Если вы достаточно смелы, вы можете попытаться использовать эвристику, чтобы попытаться угадать, будут ли первые байты памяти выглядеть так, как вы могли ожидать от символов, но я бы не назвал это безопасным .
Серж
Если вам просто нужно общее имя для строковых и структурных функций, вы можете использовать _Genericмакрос. Вы также можете создавать самоидентифицируемые типы, например, с помеченными объединениями , что будет означать, что вы не можете передать необработанную char *строку. Все это, вероятно, больше проблем, чем оно того стоит.
M Oehm

Ответы:

12

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

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

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

Yunnosch
источник
Кроме того, компилятор фактически знает, на какой тип данных он указывает. Так что, если вы перейдете к какой-либо void*функции, приведете к неправильному типу, а затем отмените ссылку на данные ... тогда будет вызван любой способ неопределенного поведения.
Лундин
5

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) версии всех типов, которые вы хотите поддерживать.


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

#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".

Лундин
источник