Como declarar, inicializar e usar ponteiros em C - A constante NULL

Agora que já vimos os conceitos teóricos sobre memória, blocos de memória, endereçamento e do uso da função sizeof(), vamos de fato usar os ponteiros.

Nesse tutorial de C de nossa apostila vamos ensinar como declarar ponteiros, fazê-los apontarem para alguma variável ou vetor, e manipulá-los.


Como declarar ponteiros em C

Para declarar um ponteiro, ou apontador, em C basta colocarmos um asterisco - * - antes do nome desse ponteiro.

Sintaxe:
tipo *nome_do_ponteiro;

Por exemplo:
int *ponteiro_pra_inteiro;
float *ponteiro_pra_float;
char *ponteiro_pra_char;

Na verdade, esse asterisco pode ser encostado no tipo ou entre o tipo e o nome.

Aqui, se você estiver com os conceitos de ponteiro na cabeça, pode surgir uma pergunta.
“Se os ponteiros são tipos que armazenam endereço, e endereço são apenas números, por quê ter que declarar ponteiros com os tipos (int, float, char etc) ?”

A resposta é dada no artigo passado, em que falamos sobre o tamanho que as variáveis ocupam em memória.
Vimos que as variáveis ocupam posições vizinhas e contíguas (em seqüência) de memória (exceto, claro, o tipo char, que ocua só 1 byte, ou seja, só um bloco).

Vamos pegar o exemplo da variável inteira. Em minha máquina, ela ocupa 4 bytes.
Ou seja, 4 blocos de memória, cada bloco com um endereço.
Mas o ponteiro armazena apenas um endereço de memória, e não 4.
Então, o ponteiro irá sempre armazenar o endereço do primeiro bloco, do primeiro byte.

E os outros? Ué, se o C sabe quantos bytes cada variável ocupa, que elas são blocos vizinhos de memória e o ponteiro sabe o endereço do primeiro bloco, ele vai saber dos outros também!

É por isso que precisamos dizer o tipo de variável, antes de declarar o ponteiro.
Se for um ponteiro de inteiro, estamos dizendo: “Ponteiro, guarde esse endereço e os próximos 3, pois o inteiro tem 4 bloco”.
Se for um double: “Ponteiro, armazene o primeiro endereço, e saiba que os próximos 7 blocos são dessa mesma variável.”

Ponteiros e Vetores em C

Já explicamos sobre a relação dos ponteiros com os diversos tipos de blocos de memória, de cada variável.
E a relação dos ponteiros com os vetores, que possuem diversas variáveis?

Pois bem, eles têm (ponteiros e vetores) possuem uma relação especial.
Quando declaramos um vetor, estamos declarando um conjunto de variáveis também contíguas, e cada uma dessas variáveis ocupam vários bytes (ou só 1 byte, se for char). Então, um vetor é um conjunto maior ainda de bytes, de blocos de memória.

Como você sabe, quando apontamos um ponteiro para uma variável, esse ponteiro armazena o endereço do primeiro byte, do menor endereço, da variável.
A relação com vetores é análoga: o nome do vetor é, na verdade, o endereço do primeiro elemento desse vetor.

Ou seja, se declararmos um vetor de nome vetor, não importando o número de elementos, se imprimirmos o nome vetor dentro de um printf, veremos o endereço da primeira variável daquele vetor.
Podemos ver um vetor como um ponteiro.

Isso explica o fato de que quando passamos um vetor para uma função, essa função altera de fato o valor do vetor. Isso ocorre pois não estamos passando uma cópia do vetor (como acontece com as variáveis).
Isso ocorre porque quando passamos o nome do vetor, estamos passando um ponteiro pra função.
Ou seja, estamos passando um endereço, onde a função vai atuar.
E endereço de memória é o mesmo, dentro ou fora de uma função.

Rode o seguinte exemplo para se certificar do que foi ensinado aqui.



Exemplo de código em C

Crie um programa que mostre que o nome de um vetor é, na verdade, um ponteiro para a primeira posição que o vetor ocupa na memória. Ou seja, um vetor sempre aponta para o elemento 0.


#include <stdio.h>
// Curso C Progressivo: www.cprogessivo.net
// O melhor curso de C! Online e gratuito !
// Artigos, apostilas, tutoriais e vídeo-aulas sobre
// a linguagem de programação C !

int main(void)
{ int teste[10]; printf("Imprimindo o vetor 'teste': %d\n", teste); printf("Imprimindo o endereço do primeiro elemento: %d\n", &teste[0]);
    return 0;
}

Ou seja, para declararmos um ponteiro ptr para um vetor vet[ ], fazemos:
ptr = vet;

Pois o nome do vetor é um ponteiro (que não muda) para o primeiro elemento.
Então poderíamos fazer assim também:
ptr = &vet[0];

Como inicializar um ponteiro em C – A constante NULL

Já vimos como declarar um ponteiro, então é hora de fazer com que eles cumpram sua missão.
Vamos fazer os ponteiros apontarem.

Lembra que ensinamos como checar o endereço de uma variável ou vetor apenas usando o símbolo & antes da variável?
Agora vamos fazer isso novamente, mas é para pegar esse endereço e armazenar em um ponteiro.

Por exemplo, se quisermos armazenar o endereço do inteiro ‘numero’ no ponteiro ‘numeroPtr’, fazemos:

int numero = 5;
int *numeroPtr = &numero;

Pronto, agora nosso ponteiro está apontando para a variável numero, pois o ponteiro guardou o endereço do inteiro na sua posição de memória.
Muito cuidado! Ponteiros armazenam endereços, e não valores. Ou seja, se fizer:
int *numeroPtr = numero;

Estará comentendo um erro!

É sempre bom inicializarmos os ponteiros, pois senão eles podem vir com lixo e você se esquecer, posteriormente, de inicializar. Então, quando for usar, pensará que está usando o ponteiro de modo correto, mas estará usando o ponteiro com ele apontando para um lixo (endereço qualquer de memória).

Uma boa prática é apontar os ponteiros para a primeira posição de memória, que é conhecida como NULL. Sempre que terminar de usar um ponteiro, coloque ele pra apontar para a posição NULL. Para fazer isso, faça:
tipo *nome_do_ponteiro = NULL;


Exemplo de código: Como usar ponteiros
Crie um programa em C que declara um inteiro e uma variável do tipo double. Em seguida, crie dois ponteiros apontando para essas variáveis e mostre o endereço de memória das variáveis, e mostre o endereço de memória que cada ponteiro armazenou.
Por fim, coloque esses ponteiros para a primeira posição (NULL), de memória.

Para saber o endereço de uma variável dentro do printf, colocamos o %d e depois ‘&nome_variavel’.
Para saber que endereço um ponteiro armazena no printf, também colocamos o %d entre as aspas, e fora colocamos apenas o nome do ponteiro.

Veja como ficou nosso código sobre como fazer esse programa em C:

#include <stdio.h>
// Curso C Progressivo: www.cprogessivo.net
// O melhor curso de C! Online e gratuito !
// Artigos, apostilas, tutoriais e vídeo-aulas sobre
// a linguagem de programação C !
 
int main(void)
{ int inteiro; int *inteiro_ptr = &inteiro; double double1; double *double_ptr = &double1; printf("Endereco da variariavel 'inteiro': %d\n", &inteiro); printf("Endereco armazenado no ponteiro 'inteiro_ptr': %d\n\n", inteiro_ptr); printf("Endereco da variariavel 'double1': %d\n", &double1); printf("Endereco armazenado no ponteiro 'double_ptr': %d\n\n", double_ptr); printf("Apos o uso dos ponteiros, vamos aponta-los para NULL\n\n"); inteiro_ptr = NULL; double_ptr = NULL; printf("Endereco armazenado no ponteiro 'inteiro_ptr': %d\n", inteiro_ptr); printf("Endereco armazenado no ponteiro 'double_ptr': %d\n", double_ptr);

    return 0; 
}

15 comentários:

  1. Apostila C Progressivo; É a melhor que existe!
    Em nome de todos os leitores; Meus Parabéns!

    ResponderExcluir
  2. Quem só usou ctrl c + ctrl v ta com 'variariavel' no printf. Além de que provavelmente não aprendeu direito. Aqui to adorando o Curso Obrigado e Parabéns. estudando e praticando muito.

    ResponderExcluir
  3. #include
    // Curso C Progressivo: www.cprogessivo.net
    // O melhor curso de C! Online e gratuito !
    // Artigos, apostilas, tutoriais e vídeo-aulas sobre
    // a linguagem de programação C !

    int main(void)


    {
    int teste[10];

    printf("Imprimindo o vetor 'teste': %d\n", teste);
    printf("Imprimindo o endereço do primeiro elemento: %d\n", &teste[0]);

    return 0;


    }
    na apostila anterior aconteceu a mesma coisa. o programa nao é compilado pq aparece a mensagem: linha 13 aviso: formato '%d' espera argumento do tipo 'int', orem o argumento 2 possui tipo 'int *' [-wformat]....

    pelo que eu entendi o compilador nao esta tratando o numero do endereço como um numero inteiro por isso nao esta aceitando '%d'...
    é isso mesmo???

    ResponderExcluir
  4. André de Souza, sim. %d realmente é para inteiros. Para ponteiros, use %p.

    ResponderExcluir
  5. Ainda nao entendi a importancia de saber os endereços das variaveis

    ResponderExcluir
  6. "Blogger Roberto GMJ disse...
    Quem só usou ctrl c + ctrl v ta com 'variariavel' no printf. Além de que provavelmente não aprendeu direito. Aqui to adorando o Curso Obrigado e Parabéns. estudando e praticando muito."
    Que eu saiba aquilo era um exemplo , não uma questão....

    ResponderExcluir
  7. Excelente explicação, finalmente estou começando a entender ponteiros!
    Muito obrigado equipe do C progressivo!

    ResponderExcluir
  8. Excelente trabalho, está fácil aprender isso!

    ResponderExcluir
  9. Este comentário foi removido pelo autor.

    ResponderExcluir
  10. Não consegui entender claramente a necessidade de apontar para NULL. Se depois eu precisar utilizar o ponteiro novamente, vou ter que declarar ele para o endereço da variável novamente? Se alguem puder esclarecer melhor esse conceito de apontar para NULL, eu agradeço.

    ResponderExcluir
  11. Há duas formas mais utilizadas de se imprimir o endereço de memória. Podemos usar o %d, que foi utilizado no post, e seria o endereço de memória na base 10. Ou também podemos utilizar o %p, que nos retorna o endereço de memória na base hexadecimal. Tenho costume de utilizar %p, mas acredito que no final das contas não faça nenhuma diferença.

    Ótimo post, obrigado por manter o site em funcionamento!

    ResponderExcluir
  12. Olá
    Cheguei aqui porque estava pesquisando o tema da inicialização de um ponteiro. Tive essa dúvida ao tentar repetir os passos de um programador da web que fez um programinha para console e usou ponteiro, mas o fez dessa forma:

    const char *titulo = "MENU PARA DIVIDIR";
    const char *opciones[] = {"Dividir enteros","Dividir flotantes","Regressar"};
    int n = 3

    opcion= menu(titulo, opciones, n);

    Ou seja, ele deu valores para os ponteiros e não endereços. Considerando o texto desta parte da apostila fica a dúvida do que esse programador exatamente fez?

    ResponderExcluir
  13. Ola joagostini.

    Neste caso os valores passados sao enderecos, pois o primeiro recebe uma sequencia de caracteres (um vetor, que automaticamente retorna um endereco inteiro) e o segundo eh um vetor que contem vetores, o que tambem retorna um endereco inteiro.

    ResponderExcluir
  14. Antes, preciso agradecer a vocês do cprogressivo.net pelas publicações que ajudam a todos nós!

    Mas quero informar um equívoco na publicação:
    Onde: "apontar os ponteiros para a primeira posição de memória, que é conhecida como NULL"

    Isso não é verdade!
    NULL, não implica em qualquer significado físico ou lógico de localização na memória.

    Embora NULL seja frequentemente representado como zero, isso não implica que ele realmente aponta para o endereço de memória física zero. Na maioria dos sistemas operacionais modernos, a tentativa de acessar o endereço de memória zero (ou qualquer endereço associado a NULL) causará uma violação de acesso (segmentation fault), porque esse endereço é normalmente protegido ou reservado pelo sistema. O endereço zero na memória é geralmente não utilizável por programas de usuário e é reservado para sinalizar endereços inválidos.

    A conversão de ponteiros nulos para o valor zero é uma convenção

    ResponderExcluir

É quase impossível criar centenas de páginas voltadas para programação C e não cometer algum erro.

- Se notar algum conceito, letra ou trecho de código errado, deixe sua correção

- Se perceber uma maneira melhor ou mais eficiente de fazer algo, deixe sua ideia

- Se algo não ficar claro ou for confuso, nos avise

Aos poucos vamos aumentando e melhorando a qualidade de nosso material, e para isso contamos com sua ajuda.