Jak sprawdzić, czy wskaźnik void (void *) jest jednym z dwóch typów danych?

10

Piszę funkcję, w której chciałbym zaakceptować 2 types parametrów.

  • A string(char *)
  • structureGdzie będzie n liczbę elementów.

Aby to osiągnąć, myślę o użyciu prostego void *typu parametru. Ale nie wiem, jak w bezpieczny sposób sprawdzić, czy parametr jest jednego rodzaju, czy drugiego.

Lokalny Gospodarz
źródło
10
Nie możesz! Przynajmniej będziesz musiał dodać drugi parametr do funkcji, który wskazuje na co void*wskazuje.
Adrian Mole
4
... a jeśli trzeba dodać drugi parametr w każdym razie, można równie dobrze napisać dwie odrębne funkcje func_stri func_structdotyczące kontroli typu w czasie kompilacji.
M Oehm,
Tak, dlatego zastanawiałem się, czy to możliwe tylko w jednej funkcji
localhost
1
Nie możesz w bezpieczny i przenośny sposób. Jeśli masz dość odwagi, możesz spróbować użyć heurystyki, aby zgadnąć, czy pierwsze bajty pamięci wyglądają tak, jak można się spodziewać po postaciach, ale nie nazwałbym tego bezpiecznym .
Serge Ballesta,
Jeśli potrzebujesz tylko wspólnej nazwy dla funkcji string i struct, możesz użyć _Genericmakra. Możesz także tworzyć typy samoidentyfikujące się, na przykład ze znakowanymi związkami , co oznaczałoby, że nie możesz przekazać nieprzetworzonego char *ciągu. To chyba więcej kłopotów niż jest warte.
M Oehm,

Odpowiedzi:

12

Tłumaczenie void*to:
„Drogi kompilatorze, to jest wskaźnik, i nie ma dla ciebie dodatkowych informacji na ten temat”.

Zwykle kompilator wie lepiej niż ty (programista), ponieważ z informacji, które otrzymał wcześniej, wciąż pamięta i być może zapomniałeś.
Ale w tym szczególnym przypadku wiesz lepiej lub musisz wiedzieć lepiej. We wszystkich przypadkach void*informacje są dostępne inaczej, ale tylko dla programisty, który „akurat wie”. Programiści muszą więc przekazywać informacje kompilatorowi - lub lepiej działającemu programowi, ponieważ jedyną zaletą void*jest to, że informacje mogą się zmieniać w czasie wykonywania.
Zwykle odbywa się to poprzez przekazanie informacji za pomocą dodatkowych parametrów do funkcji, czasami przez kontekst, tzn. Program „zdarza się wiedzieć” (np. Dla każdego możliwego typu istnieje osobna funkcja, która z wywoływanych funkcji implikuje typ).

Tak więc ostatecznie void*nie zawiera informacji o typie.
Wielu programistów źle to rozumie jako „Nie muszę znać informacji o typie”.
Ale prawdą jest odwrotność: użycie programisty void* zwiększa odpowiedzialność programisty za śledzenie informacji o typie i przekazywanie go odpowiednio programowi / kompilatorowi.

Yunnosch
źródło
Ponadto kompilator faktycznie wie, jaki jest typ wskazanych danych. Więc jeśli wskoczysz do jakiejś void*funkcji, rzucisz na niewłaściwy typ, a następnie cofniesz odwołanie do danych ... wtedy wywoływane są wszystkie rodzaje niezdefiniowanych zachowań.
Lundin,
5

void*są trochę przestarzałe w przypadku programowania ogólnego, nie ma obecnie wielu sytuacji, w których powinieneś ich używać. Są niebezpieczne, ponieważ prowadzą do nieistniejącego typu bezpieczeństwa. I, jak zauważyłeś, tracisz także informacje o typie, co oznacza, że ​​będziesz musiał przeciągać trochę nieporęcznych enumrazem z void*.

Zamiast tego powinieneś użyć C11, _Genericktóry może sprawdzać typy w czasie kompilacji i zwiększać bezpieczeństwo typów. Przykład:

#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); 
}

Pamiętaj, aby dostarczyć kwalifikowane ( const) wersje wszystkich typów, które chcesz obsługiwać.


Jeśli chcesz poprawić błędy kompilatora, gdy program wywołujący przejdzie nieprawidłowy typ, możesz dodać statyczne potwierdzenie:

#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) 

Jeśli spróbujesz czegoś takiego int x; func(x);, otrzymasz komunikat kompilatora "x: incorrect type".

Lundin
źródło