Enviar um café pro programador

Pode me ajudar a transformar cafeína em código?

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:

Brittivaldo disse...

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

Unknown 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.

ze disse...

#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???

Anônimo disse...

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

Anônimo disse...

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

Unknown disse...

"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....

Victor disse...

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

igoros635@gmail.com disse...

Excelente trabalho, está fácil aprender isso!

Rayller disse...
Este comentário foi removido pelo autor.
Rayller disse...

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.

Unknown disse...

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!

joagostini disse...

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?

Unknown disse...

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.

Unknown disse...

mas que belo curso! muito bom!

Anônimo disse...

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