O que significa void * e como usá-lo?

147

Hoje, quando eu estava lendo o código de outras pessoas, vi algo como void *func(void* i);: o que isso void*significa aqui para o nome da função e para o tipo de variável, respectivamente?

Além disso, quando precisamos usar esse tipo de ponteiro e como usá-lo?

OneZero
fonte
2
Que livro C você está usando? Você está pedindo a maior parte de um capítulo inteiro.
Cnicutar
1
Ter um olhar para stackoverflow.com/questions/692564/...
WDan
Pegue uma sugestão de malloce calloc. A página de manual continua dizendo: "... retorne um ponteiro para a memória alocada, adequadamente alinhada para qualquer tipo de dados interno".
Autômato

Respostas:

175

Um ponteiro para voidé um tipo de ponteiro "genérico". A void *pode ser convertido para qualquer outro tipo de ponteiro sem uma conversão explícita. Você não pode desreferenciar um void *ponteiro ou fazer aritmética com ele; você deve convertê-lo em um ponteiro para um tipo de dados completo primeiro.

void *é frequentemente usado em locais onde você precisa trabalhar com diferentes tipos de ponteiros no mesmo código. Um exemplo comumente citado é a função de biblioteca qsort:

void qsort(void *base, size_t nmemb, size_t size, 
           int (*compar)(const void *, const void *));

baseé o endereço de uma matriz, nmembé o número de elementos na matriz, sizeé o tamanho de cada elemento e comparé um ponteiro para uma função que compara dois elementos da matriz. É assim chamado:

int iArr[10];
double dArr[30];
long lArr[50];
...
qsort(iArr, sizeof iArr/sizeof iArr[0], sizeof iArr[0], compareInt);
qsort(dArr, sizeof dArr/sizeof dArr[0], sizeof dArr[0], compareDouble);
qsort(lArr, sizeof lArr/sizeof lArr[0], sizeof lArr[0], compareLong);

As expressões de matriz iArr, dArre lArrsão convertidos implicitamente a partir de tipos de matriz para tipos de ponteiro na chamada de função, e cada um é implicitamente convertido de "ponteiro a int/ double/ long" a "ponteiro de void".

As funções de comparação seriam parecidas com:

int compareInt(const void *lhs, const void *rhs)
{
  const int *x = lhs;  // convert void * to int * by assignment
  const int *y = rhs;

  if (*x > *y) return 1;
  if (*x == *y) return 0;
  return -1;
}

Ao aceitar void *, qsortpode trabalhar com matrizes de qualquer tipo.

A desvantagem do uso void *é que você joga a segurança de tipos pela janela e entra no tráfego que se aproxima. Não há nada para protegê-lo de usar a rotina de comparação errada:

qsort(dArr, sizeof dArr/sizeof dArr[0], sizeof dArr[0], compareInt);

compareIntespera que seus argumentos apontem para ints, mas na verdade está trabalhando com doubles. Não há como detectar esse problema no momento da compilação; você acabará com uma matriz distorcida.

John Bode
fonte
5
Na verdade, não é garantido que um void*possa ser convertido em um ponteiro de função. Mas para ponteiros de dados o que você disse é válido.
Vatine 12/07/16
Antes que os ponteiros nulos estivessem disponíveis, "char *" era usado. Mas o vazio é melhor, pois na verdade não pode ser usado para alterar nada diretamente.
user50619
22

Usar um vazio * significa que a função pode receber um ponteiro que não precisa ser de um tipo específico. Por exemplo, nas funções de soquete, você tem

send(void * pData, int nLength)

isso significa que você pode chamá-lo de várias maneiras, por exemplo

char * data = "blah";
send(data, strlen(data));

POINT p;
p.x = 1;
p.y = 2;
send(&p, sizeof(POINT));
TheSteve
fonte
Então isso é muito parecido com genéricos em outros idiomas, mas sem verificação de tipo, certo?
Winger Sendon
3
Suponho que seria semelhante, no entanto, como não há verificação de tipo, cometer um erro pode causar resultados muito estranhos ou causar a falha total do programa.
TheSteve 28/08
7

C é notável a esse respeito. Pode-se dizer que o voidnada void*é tudo (pode ser tudo).

É tão pequeno *que faz a diferença.

René apontou isso. A void *é um ponteiro para algum local. O que há como "interpretar" é deixado para o usuário.

É a única maneira de ter tipos opacos em C. Exemplos muito importantes podem ser encontrados, por exemplo, em bibliotecas glib ou gerais da estrutura de dados. É tratado muito detalhadamente em "Interfaces e implementações C".

Sugiro que você leia o capítulo completo e tente entender o conceito de um ponteiro para "obtê-lo".

Friedrich
fonte
5
void*

é um 'ponteiro para a memória sem suposições de que tipo está armazenado'. Você pode usar, por exemplo, se deseja passar um argumento para a função e esse argumento pode ser de vários tipos e, na função, você manipulará cada tipo.

René Kolařík
fonte
3

Você pode dar uma olhada neste artigo sobre ponteiros http://www.cplusplus.com/doc/tutorial/pointers/ e ler o capítulo: ponteiros nulos .

Isso também funciona para a linguagem C.

O tipo de ponteiro nulo é um tipo especial de ponteiro. No C ++, void representa a ausência de tipo, portanto, os ponteiros nulos são indicadores que apontam para um valor que não possui tipo (e, portanto, também um comprimento indeterminado e propriedades de desreferência indeterminada).

Isso permite que os ponteiros nulos aponte para qualquer tipo de dado, de um valor inteiro ou flutuante a uma sequência de caracteres. Mas, em troca, eles têm uma grande limitação: os dados apontados por eles não podem ser diretamente desreferenciados (o que é lógico, já que não temos nenhum tipo de referência) e, por esse motivo, sempre teremos que lançar o endereço no ponteiro nulo para algum outro tipo de ponteiro que aponte para um tipo de dados concreto antes de desreferenciá-lo.

AG
fonte
3

Um ponteiro nulo é conhecido como ponteiro genérico. Eu gostaria de explicar com um exemplo de cenário pthread.

A função thread terá o protótipo como

void *(*start_routine)(void*)

Os designers da API pthread consideraram o argumento e retornam os valores da função do encadeamento. Se essas coisas forem tornadas genéricas, podemos digitar cast para void * enquanto enviamos como argumento. da mesma forma, o valor de retorno pode ser recuperado de void * (mas eu nunca usei valores de retorno da função thread).

void *PrintHello(void *threadid)
{
   long tid;

   // ***Arg sent in main is retrieved   ***
   tid = (long)threadid;
   printf("Hello World! It's me, thread #%ld!\n", tid);
   pthread_exit(NULL);
}

int main (int argc, char *argv[])
{
   pthread_t threads[NUM_THREADS];
   int rc;
   long t;
   for(t=0; t<NUM_THREADS; t++){
      //*** t will be type cast to void* and send as argument.
      rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);   
      if (rc){
         printf("ERROR; return code from pthread_create() is %d\n", rc);
         exit(-1);
      }
   }    
   /* Last thing that main() should do */
   pthread_exit(NULL);
}
Jeyaram
fonte
Por que ligar em pthread_exit(NULL);vez de return 0;no final do main?
precisa saber é o seguinte
1
@ Seabass77 Por favor, consulte stackoverflow.com/questions/3559463/…
Jeyaram
1

a void*é um ponteiro, mas o tipo para o qual ele aponta não é especificado. Quando você passa um ponteiro nulo para uma função, você precisa saber qual era o seu tipo para convertê-lo novamente no tipo correto posteriormente na função para usá-lo. Você verá exemplos em pthreadsque funções de uso com exatamente o protótipo em seu exemplo são usadas como a função de thread. Em seguida, você pode usar o void*argumento como um ponteiro para um tipo de dados genérico de sua escolha e, em seguida, convertê-lo novamente nesse tipo para usar na função de encadeamento. Você precisa ter cuidado ao usar ponteiros nulos; a menos que retorne a um ponteiro do seu tipo verdadeiro, você pode acabar com todos os tipos de problemas.

mathematician1975
fonte
1

A norma C11 (n1570) §6.2.2.3 al1 p55 diz:

Um ponteiro para voidpode ser convertido para ou de um ponteiro para qualquer tipo de objeto. Um ponteiro para qualquer tipo de objeto pode ser convertido em um ponteiro para anular e voltar; o resultado deve comparar igual ao ponteiro original.

Você pode usar esse ponteiro genérico para armazenar um ponteiro em qualquer tipo de objeto, mas não pode usar operações aritméticas comuns com ele e não pode deferi-lo.

md5
fonte
0

A função leva um ponteiro para um tipo arbitrário e retorna um desses.

glglgl
fonte