Ponteiros void na linguagem C

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.

[ Hits: 55.074 ]

Por: Ricardo Rodrigues Lucca em 18/05/2004 | Blog: http://aventurasdeumdevop.blogspot.com.br/


Introduçã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:

#include <stdio.h>

void main( void )
{
        int x;

        printf("%d", x);
}

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.

Esperem pela segunda parte!

Qualquer dúvida email-me.

[]'s

   

Páginas do artigo
   1. Introdução
Outros artigos deste autor

Utilizando a função QSort em C

Apreendendo a utilizar o GNU Debugger (parte 2)

Uma pequena análise do Gentoo Linux

Conceitos sobre o X-Window

Como posso recuperar o boot loader?

Leitura recomendada

Criando um sistema operacional com ASM e C++

Introdução à plataforma GNU de desenvolvimento

Criação e uso de um interpretador de script BrainFuck em C++

Escrevendo o caos em C

Substituindo a biblioteca conio.h no Linux usando ncurses curses.h

  
Comentários
[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?

[2] Comentário enviado por jllucca em 18/05/2004 - 12:09h

Sim.

Mas, eh boa pratica realizar cast pro tipo pra evitar problemas e ficar mais claro. Assim:

#include <stdio.h>

void main( void )
{
int x = 5;
void *y = (void *) &x;
int *z;

z = (int *) y;
*z = 9;
}

[3] Comentário enviado por ygorth em 18/05/2004 - 14:59h

Bom artigo,

realmente acresento um conceito que nao conhecia.

[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:

void * memcpy (void * dest, const void * orig, size_t len);

Uma implementação sugerida seria:

void * memcpy (void * dest, const void * orig, size_t len) {
size_t i;
char * o, d;
for (o=(char *)orig, d=(char *)dest; i<len; ++i, ++d, ++o) {
*d = *o;
}
return dest;
}

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

[12] Comentário enviado por f_Candido em 13/10/2007 - 23:37h

Bem interessante. Nunca tinha parado para estudar o void.

[13] Comentário enviado por ryonagana em 25/08/2008 - 20:12h

eu conhecia orem nunca usei

vejo muito disso em acessos de endereços de uma DLL

[14] Comentário enviado por rubensalves em 15/05/2011 - 17:52h

como fazer um programa que conte a data o ano e os dias e contar se o ano foi bissesto

[15] Comentário enviado por paulo_moc em 19/09/2012 - 14:55h

Caramba... muito útil, pode parecer ironico mas não é.
=]


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts