Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to verify if a void pointer (void *) is one of two data types?

Tags:

c

types

gcc

clang

I am writing a function where i would like to accept 2 types of parameters.

  • A string (char *)
  • A structure where there will be n number of elements.

And to achieve this i am thinking of using a simple void * as parameter type. But i do not know how to verify if the parameter is one type or the other, safely.

like image 384
localhost Avatar asked Oct 08 '19 05:10

localhost


2 Answers

The translation of void* is
"Dear compiler, this is a pointer, an there is no additional information for you on this.".

Usually the compiler knows better than you (the programmer), because of information he got earlier and still remembers and you might have forgotten about.
But in this special case, you know better or need to know better. In all cases of void* the information is available otherwise, but only to the programmer, who "happens to know". The programmer therefor has to provide the information to the compiler - or better to the running program, because the one advantage a void* has is that the information can change during runtime.
Usually that is done by giving the information via additional parameters to functions, sometimes via context, i.e. the program "happens to know" (e.g. for each possible type there is a separate function, whichever function gets called implies the type).

So in the end void* does not contain the type info.
Many programmers misunderstand this as "I don't need to know the type info".
But the opposite is true, the use of void* increases the responsibility of the programmer to keep track of the type info and provide it appropriatly to the program/compiler.

like image 73
Yunnosch Avatar answered Sep 19 '22 19:09

Yunnosch


void* are kind of deprecated for generic programming, there aren't many situations where you should use them nowadays. They are dangerous because they lead to non-existent type safety. And as you noted, you also lose the type information, meaning you'd have to drag around some cumbersome enum along with the void*.

Instead you should use C11 _Generic which can check types at compile-time and add type safety. Example:

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

Remember to provide qualified (const) versions of all types you wish to support.


If you want better compiler errors when the caller passes the wrong type, you could add a static 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) 

If you try something like int x; func(x); you'll get the compiler message "x: incorrect type".

like image 42
Lundin Avatar answered Sep 20 '22 19:09

Lundin