Wie überprüfe ich, ob ein void-Zeiger (void *) einer von zwei Datentypen ist?

10

Ich schreibe eine Funktion, in der ich 2 types Parameter akzeptieren möchte .

  • A string(char *)
  • A structurewo es n Elemente gibt.

Und um dies zu erreichen, denke ich darüber nach, einen einfachen void *Parametertyp zu verwenden. Aber ich weiß nicht, wie ich sicher überprüfen soll, ob der Parameter vom einen oder anderen Typ ist.

localhost
quelle
10
Das kannst du nicht! Zumindest müssen Sie der Funktion einen zweiten Parameter hinzufügen, der angibt, worauf die void*Punkte zeigen.
Adrian Mole
4
... und wenn Sie trotzdem einen zweiten Parameter hinzuzufügen haben, könnten Sie genauso gut zwei separate Funktionen schreiben func_strund func_structund Typprüfungen bei der Kompilierung bekommen.
M Oehm
Ja, deshalb habe ich mir überlegt, ob es nur in einer Funktion möglich ist
localhost
1
Sie können nicht auf sichere und tragbare Weise. Wenn Sie mutig genug sind, können Sie versuchen, mithilfe von Heuristiken zu erraten, ob die ersten Speicherbytes so aussehen, wie Sie es von Charakteren erwarten können, aber ich würde das nicht als sicher bezeichnen .
Serge Ballesta
Wenn Sie nur einen gemeinsamen Namen für die Zeichenfolgen- und Strukturfunktionen wünschen, können Sie ein _GenericMakro verwenden. Sie können auch selbstidentifizierende Typen erstellen, z. B. mit markierten Gewerkschaften . Dies bedeutet, dass Sie keine rohe char *Zeichenfolge übergeben können. All das ist wahrscheinlich mehr Ärger als es wert ist.
M Oehm

Antworten:

12

Die Übersetzung von void*lautet
"Lieber Compiler, dies ist ein Zeiger, und es gibt keine zusätzlichen Informationen für Sie dazu.".

Normalerweise weiß der Compiler es besser als Sie (der Programmierer), aufgrund von Informationen, die er früher erhalten hat und an die er sich noch erinnert und die Sie möglicherweise vergessen haben.
Aber in diesem speziellen Fall wissen Sie es besser oder müssen es besser wissen. In allen Fällen sind void*die Informationen ansonsten verfügbar, jedoch nur für den Programmierer, der "zufällig weiß". Der Programmierer muss daher die Informationen dem Compiler - oder besser dem laufenden Programm - zur Verfügung stellen, da der einzige Vorteil darin void*besteht, dass sich die Informationen zur Laufzeit ändern können.
Normalerweise erfolgt dies durch Übergabe der Informationen über zusätzliche Parameter an Funktionen, manchmal über den Kontext, dh das Programm "weiß es zufällig" (z. B. gibt es für jeden möglichen Typ eine separate Funktion, je nachdem, welche Funktion aufgerufen wird, impliziert dies den Typ).

Enthält also am Ende void*nicht die Typinfo.
Viele Programmierer verstehen dies falsch als "Ich muss die Typinformationen nicht kennen".
Das Gegenteil ist der Fall. Die Verwendung von void* erhöht die Verantwortung des Programmierers, die Typinformationen zu verfolgen und sie dem Programm / Compiler angemessen zur Verfügung zu stellen.

Yunnosch
quelle
Darüber hinaus weiß der Compiler tatsächlich, um welche Art von Daten es sich handelt. Wenn Sie also in eine void*Funktion springen , in den falschen Typ umwandeln und dann die Daten de-referenzieren ... wird jede Art von undefiniertem Verhalten aufgerufen.
Lundin
5

void*sind für die generische Programmierung etwas veraltet, es gibt nicht viele Situationen, in denen Sie sie heutzutage verwenden sollten. Sie sind gefährlich, weil sie zu nicht vorhandener Sicherheit führen. Und wie Sie bemerkt haben, verlieren Sie auch die Typinformationen, was bedeutet, dass Sie einige umständliche enumzusammen mit dem ziehen müssen void*.

Stattdessen sollten Sie C11 _Genericverwenden, mit dem Typen zur Kompilierungszeit überprüft und die Typensicherheit erhöht werden kann. Beispiel:

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

Denken Sie daran, qualifizierte ( const) Versionen aller Typen bereitzustellen, die Sie unterstützen möchten.


Wenn Sie bessere Compilerfehler wünschen, wenn der Aufrufer den falschen Typ übergibt, können Sie eine statische Zusicherung hinzufügen:

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

Wenn Sie so etwas versuchen, erhalten int x; func(x);Sie die Compilermeldung "x: incorrect type".

Lundin
quelle