Como verificar se um ponteiro nulo (nulo *) é um dos dois tipos de dados?

10

Estou escrevendo uma função em que gostaria de aceitar 2 types de parâmetros.

  • A string(caractere *)
  • A structureonde haverá n número de elementos.

E para conseguir isso, estou pensando em usar um simples void *como tipo de parâmetro. Mas não sei como verificar se o parâmetro é de um tipo ou de outro, com segurança.

localhost
fonte
10
Você não pode! No mínimo, você precisará adicionar um segundo parâmetro à função, que indica para que os void*pontos apontam.
Adrian Mole
4
... e se você tem que adicionar um segundo parâmetro de qualquer maneira, você poderia muito bem escrever duas funções separadas func_stre func_structe obter verificações de tipo em tempo de compilação.
83319 M Oehm
Sim, é por isso que eu estava pensando se era possível em uma função única
localhost
11
Você não pode de maneira segura e portátil. Se você for corajoso o suficiente, poderá tentar usar heurísticas para tentar adivinhar se os primeiros bytes de memória se parecem com o que você poderia esperar de caracteres, mas eu não consideraria isso seguro .
Serge Ballesta
Se você quiser apenas um nome comum para as funções string e struct, poderá usar uma _Genericmacro. Você também pode criar tipos de identificação automática, por exemplo, com uniões marcadas , o que significa que você não pode passar uma char *sequência bruta . Tudo isso é provavelmente mais problemas do que vale a pena.
M Oehm 08/10/19

Respostas:

12

A tradução de void* é
"Caro compilador, este é um ponteiro, e não há informações adicionais para você sobre isso.".

Normalmente, o compilador conhece melhor que você (o programador), por causa das informações que ele obteve anteriormente e ainda se lembra e você pode ter esquecido.
Mas neste caso especial, você sabe melhor ou precisa saber melhor. Em todos os casos, void*as informações estão disponíveis de outra forma, mas apenas para o programador, que "sabe". O programador deve fornecer as informações ao compilador - ou melhor, ao programa em execução, porque a única vantagem de umvoid* é que as informações podem mudar durante o tempo de execução.
Normalmente, isso é feito fornecendo informações por meio de parâmetros adicionais para funções, às vezes via contexto, isto é, o programa "sabe" (por exemplo, para cada tipo possível, existe uma função separada, a função que for chamada implica o tipo).

Então no final void* , não contém as informações de tipo.
Muitos programadores não entendem isso como "não preciso saber as informações de tipo".
Mas o oposto é verdadeiro, o uso de void* aumenta a responsabilidade do programador de acompanhar as informações de tipo e fornecê-las adequadamente ao programa / compilador.

Yunnosch
fonte
Além disso, o compilador realmente sabe qual é o tipo de dados apontado. Portanto, se você pular para alguma void*função, converter para o tipo errado, desassinar os dados ... todos os tipos de comportamento indefinido serão invocados.
Lundin
5

void*são preteridos para programação genérica, não há muitas situações em que você deve usá-las hoje em dia. Eles são perigosos porque levam à segurança do tipo inexistente. E, como você observou, você também perde as informações de tipo, o que significa que você teria que arrastar algunsenum junto com ovoid* .

Em vez disso, você deve usar C11 _Generic que pode verificar os tipos em tempo de compilação e adicionar segurança ao tipo. Exemplo:

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

Lembre-se de fornecer informações qualificadas (const versões ) de todos os tipos que você deseja oferecer suporte.


Se você deseja erros de compilador melhores quando o chamador passa o tipo errado, você pode adicionar uma declaração 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) 

Se você tentar algo como, int x; func(x);receberá a mensagem do compilador "x: incorrect type".

Lundin
fonte