O ponteiro do tipo mais discriminado que existe. Será uma solução? Será uma inutilidade? Leia esta série de dois artigos e decida se ele é a opção que lhe falta ou não.
Inicialmente quero dizer que o artigo trata sobre ponteiros void
sobre a linguagem C e demonstra como podemos utilizá-lo. Assim, é
primordial um conhecimento anterior da linguagem C, principalmente
sobre ponteiros e tipos de dados. Espero que todos gostem do artigo.
Qualquer dúvida estou sempre a disposição.
O tipo void
Antes de falarmos de ponteiros, falaremos sobre o tipo void. Essa é
uma excelente forma de começar o artigo, pois faz lembrarmos o que
ele deve armazenar. Assim, sinta-se a vontade para pular essa parte.
Já que não temos como saber os conhecimentos prévios de uma pessoa.
Além disso, é essa explicação sobre tipos que nos ajudará a entender
o comportamento do ponteiro e a termos idéias quando houverem problemas.
O tipo void é uma das exceções das regras de tipo. Todos os tipos básicos
que existem servem para armazenar alguma espécie de dado definido num
tamanho. Exemplos que posso citar são:
char com 1 byte;
int com 4 bytes;
float com 4 byte;s
double com 8 byte.
s
É desnecessário dizer que alguns desses tamanhos podem variar conforme
for. O tipo void possui tamanho igual a 1 byte, mas não serve para armazenar
dados de qualquer tipo. Assim, o "void" é uma excessão a regra de tipos
básicos, já que ele muitas vezes nem é considerado um tipo. Mas, podemos
dizer que void é o tipo que armazena dados vazios. Assim, ele não seria uma
excessão. Outra coisa é que ele é normalmente achado em códigos aparecendo
como abaixo:
Assim, o tipo void é usado quando queremos, ou melhor, quando não queremos
dados armazenados nele. Esse é um conceito fácil de se entender, pois em
funções que não retornam nada devemos colocar void. Além disso, podemos
colocar void para explicitar que não há parâmetros como no caso acima.
Ponteiros void
Vamos começar a falar sobre ponteiros void. A primeira pergunta que nos
vem a cabeça quando falamos de um ponteiro com esse tipo é como podemos
utilizá-los e o primeiro pensamento é algo próximo disso:
#include <stdio.h>
void main( void )
{
int x = 5;
void *y = &x;
*y = 9;
}
Para quem leu o que falei sobre tipos void, sabe que quem tentar fazer isso
terá como retorno um erro. Mas ele está logicamente correto, já que podemos
atribuir o valor "9" ao conteúdo de y.
Um apontador do tipo void aponta para um tipo vazio do tamanho igual a um
byte. Mas, mesmo tendo o mesmo tamanho de um char, a conversão de tipos não
é apropriada a nenhum tipo existente (seja básico ou não). Isto é, o ponteiro
void somente aponta para void, pois é esse o tipo do seu ponteiro.
Assim, uma forma simples de demonstrar como o "void *" pode ser útil para
armazenar endereços é esta:
#include <stdio.h>
void main( void )
{
int x = 5;
void *y = &x;
int *z;
z = y;
*z = 9;
}
Assim, chegamos a seguinte conclusão:
Um ponteiro só pode apontar para o seu tipo.
Não podemos armazenar nada em tipo void.
Só pode armazenar endereços de memória em void*.
Dentre as três regras, as duas primeiras já deveríamos estar acostumados.
A "nova" - a última regra - é o que garante generabilidade para o tipo void.
[1] Comentário enviado por ymc em 18/05/2004 - 09:39h
Ótimo artigo. Não tinha visto nada sobre o uso de ponteiros para void.
Só não entendi muito bem sua utilidade. O ponteiro de void pode armazenar
qualquer tipo de ponteiro sem dar erro?
[4] Comentário enviado por ron_lima em 20/05/2004 - 07:17h
O ponteiro void é interessante quando se deseja programar funções que operem sobre dados genéricos. A biblioteca Standard C utiliza-se largamente dos ponteiros para void. Por exemplo, a função memcpy tem o seguinte protótipo:
Observe que o casting para os ponteiros internos da função são necessários. Caso contrário, não seria possível a operação de deslocamento dos ponteiros. Esta implementação sugerida da função memcpy (que pode ser achada em stdlib.h) opera sobre qualquer tipo de dado contíguo em memória. Óbvio que é uma função que deve ser utilizada com cuidado, pois não existe qualquer checagem de tipo. Se você fornecer uma origem de dados de tipo maior que o destino com toda certeza irá provocar uma bela invasão de memória no seu programa. Se tiver sorte, a invasão levará o programa a cair com falha de segmentação (sinal 11). Na pior das hipóteses outra variável no stack irá "acolchoar" a invasão e você nunca vai poder pegar este problema...
[5] Comentário enviado por jllucca em 20/05/2004 - 16:15h
To fazendo o artigo dois e tava falando disso disso... o cast é importante, porque num "void" não podemos ter nada. Mas, ai no caso em que ele falou do cast não é lá tão importante não porque em teoria void* e char* são genericos. Mas, para evitar confusões do compilador(e retirar warnings) é sempre bom castar.
[6] Comentário enviado por ron_lima em 23/05/2004 - 11:40h
Com a padronização da linguagem pelo comitê ANSI-X3j11, os ponteiros char * deixaram de ser considerados como ponteiros genéricos e a orientação é que utilize-se ponteiros para void. Sem dúvida, a utilização de ponteiros para char pode ser encarada como genérica mas já é um conceito considerado antigo.
No meu comentário, quando eu disse que o cast era importante eu me referia aos casts dentro do for, pois sem eles a operação de incremento de ponteiros gera um erro de compilação. O compilador precisa saber qual o tipo básico do ponteiro para que possa incrementar ou decrementar adequadamente os ponteiros com a utilização dos operadores unários incremento (++) ou decremento (--).
[7] Comentário enviado por jllucca em 23/05/2004 - 19:02h
Bem, tenho conhecimento do conceito ser sendo antigo... Mas, isso funciona e no minimo gera um ou dois warnings... tem um problema muito mais grave no seu programa que é não inicializar "i" que nem comentei. Alem disso, se não estou enganado foi C++ que propos essa mudança em C.
[8] Comentário enviado por ron_lima em 23/05/2004 - 19:51h
De fato comi uma bola no programinha. Pressa é sempre inimiga da perfeição. Quando à mudança dos ponteiros genéricos para o tipo void realmente foi contemplado pelo comitê X3J11 do ANSI em seu documento X3.159.1989 que descreve todo o padrão da linguagem. Na definição original da linguagem, realmente os ponteiros para char eram considerados ponteiros genéricos. Conforme este trecho do livro de Brian Kernighan e Dennis Ritchie, os inventores da linguagem: "A principal mudança no ANSI C é tornar explícitas as regras sobre como os apontadores podem ser manipulados, efetivamente afirmando o que os bons programadores já praticam e bons compiladores já forçam. Além disso, o tipo void * (apontador para void) substitui o char * como tipo apropriado para um apontador genérico".
O documento a que se refere, que padroniza o C++ nada tem a ver com isso, mesmo por que o C++ foi padronizado por outro comitê, o X3J16, que publicou o documento final de padrão da linguagem C++ somente em 1998.
Por favor, não estou dizendo que seu artigo está errado. Em verdade é um bom artigo e tentei apenas contribuir com alguns comentários.
[9] Comentário enviado por jllucca em 23/05/2004 - 23:32h
Eu compreendo, mas mesmo assim tenho que tentar defender meu ponto de vista não acha?
Sobre o C++ quis dizer que ele foi o primeiro a ter a implementação void* e depois o conselho do ANSI C fez estas alterações pois ainda era char* considerado generico... Não sei bem se isso é verdade... história não é meu forte hehehe ^^
[10] Comentário enviado por engos em 23/06/2004 - 16:26h
Acho que o pessoal já falou alguma coisa, como esse é o primeiro artigo e você deixou claro que vai ter um segundo, vou esperar um pouco para entrar em detalhes.
Apoio sua iniciativa de fazer uma matéria sobre o void, uma vez que o mesmo não é muito utilizado quando não se sabe o que ele pode fazer, entretanto é muito utilizado quando se sabe seu potencial, principalmente (no meu caso que uso muito em transmissão de dados) para cast.
[11] Comentário enviado por sax0n_m0f0r em 26/09/2006 - 11:31h
O ponteiro void '*' pode ser atribuído a qualquer tipo de ponteiro.
se não houver memória suficiente para alocar a memória requisitada a função blablabla() retorna um ponteiro nulo; será que delirei ? o_O eueheueheue