¿Cómo verificar si un puntero vacío (vacío *) es uno de los dos tipos de datos?

10

Estoy escribiendo una función donde me gustaría aceptar 2 types de parámetros.

  • A string(char *)
  • A structuredonde habrá n número de elementos.

Y para lograr esto, estoy pensando en usar un tipo simple void *como parámetro. Pero no sé cómo verificar si el parámetro es de un tipo u otro, de forma segura.

localhost
fuente
10
No puedes! Como mínimo, deberá agregar un segundo parámetro a la función, que indica a qué void*apuntan.
Adrian Mole
44
... y si usted tiene que añadir un segundo parámetro de todos modos, usted podría escribir así dos funciones separadas func_stre func_structy recibir cheques de tipo en tiempo de compilación.
M Oehm
Sí, es por eso que estaba pensando si era posible solo en una función
localhost
1
No puede hacerlo de forma segura y portátil. Si eres lo suficientemente valiente, puedes intentar usar la heurística para tratar de adivinar si los primeros bytes de memoria se parecen a lo que podrías esperar para los caracteres, pero no lo llamaría seguro .
Serge Ballesta
Si solo desea un nombre común para las funciones de cadena y estructura, puede usar una _Genericmacro. También podría crear tipos autoidentificables, por ejemplo, con uniones etiquetadas , lo que significaría que no puede pasar una char *cadena sin procesar . Todo eso es probablemente más problemas de lo que vale.
M Oehm

Respuestas:

12

La traducción de void*es
"Estimado compilador, este es un puntero, y no hay información adicional para usted sobre esto".

Por lo general, el compilador sabe mejor que usted (el programador), debido a la información que obtuvo antes y que aún recuerda y que quizás haya olvidado.
Pero en este caso especial, usted sabe mejor o necesita saber mejor. En todos los casos, void*la información está disponible de otra manera, pero solo para el programador, que "sabe". El programador debe proporcionar la información al compilador, o mejor al programa en ejecución, porque la única ventaja que void*tiene es que la información puede cambiar durante el tiempo de ejecución.
Por lo general, esto se realiza al proporcionar la información a través de parámetros adicionales a las funciones, a veces a través del contexto, es decir, el programa "sabe" (por ejemplo, para cada tipo posible hay una función separada, la función que se llame implica el tipo).

Entonces, al final void*no contiene la información de tipo.
Muchos programadores malinterpretan esto como "No necesito saber la información de tipo".
Pero lo contrario es cierto, el uso de void* aumenta la responsabilidad del programador para realizar un seguimiento de la información de tipo y proporcionarla de manera adecuada al programa / compilador.

Yunnosch
fuente
Además, el compilador realmente sabe cuál es el tipo de datos señalados. Entonces, si salta a alguna void*función, convierte al tipo incorrecto, luego des-referencia los datos ... entonces se invoca todo tipo de comportamiento indefinido.
Lundin
5

void*están en desuso para la programación genérica, no hay muchas situaciones en las que deba usarlas hoy en día. Son peligrosos porque conducen a una seguridad de tipo inexistente. Y como notó, también pierde la información de tipo, lo que significa que tendría que arrastrar algo engorroso enumjunto con el void*.

En su lugar, debe usar C11, _Genericque puede verificar los tipos en tiempo de compilación y agregar seguridad de tipo. Ejemplo:

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

Recuerde proporcionar constversiones calificadas ( ) de todos los tipos que desea admitir.


Si desea mejores errores de compilación cuando la persona que llama pasa el tipo incorrecto, puede agregar una afirmación estática:

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

Si intenta algo así int x; func(x);, recibirá el mensaje del compilador "x: incorrect type".

Lundin
fuente