Enviar um café pro programador

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

Introdução ao uso dos ponteiros - Endereços de memória

Desde o início de nosso curso de C, usamos os mais diversos tipos de dados, como int para números inteiros, float e double para números decimais, char para caracteres etc.

Vamos agora apresentar um novo tipo dado: os ponteiros.

O que são ponteiros em C ?

Não se assuste com o tanto de tipos de variáveis, elas são feitas, em sua maioria, para que os humanos possam entender e trabalhar mais facilmente com os diversos tipos de dados.

Porém, para o computador, não existe praticamente diferença alguma entre as variáveis, para ele é tudo bit, é tudo 1 ou 0.
Um meio bastante usado nos hardwares para administrar esse número gigantesco de 1’s e 0’s, é através do endereçamento.

Cada trecho da memória tem um endereço único. Não existem dois bits, em uma máquina, que tenha o mesmo endereço de memória.
O ato de selecionar, ou alocar, um espaço de memória em C é feito no momento da declaração.

Endereços de memória são um tipo de dado tão importante, ou até mais, que qualquer outro tipo de dado. Quem já trabalhou com eletrônica, em baixo nível, sabe que endereçamento é uma parte essencial de qualquer circuito digital. Nossos computadores possuem diversos dispositivos que são responsáveis somente pelo endereçamento.

E é isso que o um ponteiro é: um tipo de dado que serve para indicar, ou armazenar, um endereço de memória.

Um ponteiro não é um inteiro, é um tipo que armazena o endereço em que o inteiro está alocado.
Um ponteiro não é um float ou double, ponteiro é um tipo de dado que armazena o endereço em que o float ou double está.
Um ponteiro não é um char, ponteiro é um tipo de dado que pode armazenar o endereço em que um caractere está.

É muito importante que você entenda, e se lembre bem disso, pois é muito comum os iniciantes confundirem ponteiros com outros tipos de dados.

Essa confusão é uma dificuldade natural que todas as pessoas têm ao se depararem pela primeira vez com o uso dos ponteiros.
O interessante é que as pessoas não confundem inteiro com um caractere, ou caractere com um número decimal, mas confundem ponteiro com números inteiros.

Isso se deve ao fato dos ponteiros serem um tipo de abstração, criado especialmente para facilitar o trabalho da computação em baixo nível, da computação que mexe diretamente com a memória de seu computador, poder este que pouquíssimas linguagens possuem.


Como saber o endereço de memória de uma variável: &

Sempre que declaramos uma variável e usamos ela, estamos trabalhando com seu valor.

Por exemplo:

numero1 = 1;
numero2 = 2;
letra1 = ‘a’;
letra2 = ‘b’;

O valor da variável ‘numero1’ é 1, o valor da variável ‘numero2’ é 2, o valor da variável ‘letra1’ é ‘a’ e o valor da variável ‘letra2’ é ‘b’.

Fixe bem esse detalhe: esse é o valor que está armazenado na memória, essas variáveis são um conjunto de bits, um conjunto de informações, um valor.

Agora vamos descobrir em qual posição da memória esses valores estão.
Para isso, basta colocarmos o símbolo de E comercial antes da variável: &

Para saber o endereço da variável ‘numero1’, fazemos: &numero1
Para saber o endereço da variável ‘numero2’, fazemos: &numero2
Para saber o endereço da variável ‘letra1’, fazemos: &letra1
Para saber o endereço da variável ‘letra2’, fazemos: &letra2

Para facilitar a visualização do usuário, podemos imaginar a memória como um vetor gigantesco de espaços, e esses espaços são numerados com números inteiros.

Veja bem, embora seja um inteiro, não quer dizer que o valor seja inteiro.
Todos os endereços são números inteiros, mas nem todo o valor armazenado dentro daquele endereço de memória é inteiro.

Vamos fazer um exemplo para entender melhor a diferença entre valor e endereço de uma memória.



Exemplo de código: Vendo o valor e endereço de uma variável

Crie um programa em C que declara dois números inteiros e dois caracteres do tipo char (todos devidamente inicializados).
Em seguida, mostre o VALOR de cada variável, bem como seu ENDEREÇO
Depois, altere os valores das variáveis e mostre novamente o VALOR e ENDEREÇO de cada variável desta.

Após rodar esse exemplo, você verá a clara diferença entre o VALOR e o ENDEREÇO de uma variável na memória de seu computador.
O valor é aquela informação que você inicia, e endereço é um número inteiro ENORME.

O valor é aquela informação que é alterada, já o endereço de uma variável permanece CONSTANTE!

Faz sentido, para você?

#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 numero1=1, numero2=2; char letra1='a', letra2='b'; printf("numero1: \n"); printf("Valor: %d\n", numero1); printf("Endereco na memoria: %d\n\n", &numero1); printf("numero2: \n"); printf("Valor: %d\n", numero2); printf("Endereco na memoria: %d\n\n", &numero2); printf("letra1: \n"); printf("Valor: %c\n", letra1); printf("Endereco na memoria: %d\n\n", &letra1); printf("letra2: \n"); printf("Valor: %c\n", letra2); printf("Endereco na memoria: %d\n\n", &letra2); printf("Alterando os valores...\n\n"); numero1=2112; numero2=666; letra1='A'; letra2='B'; printf("numero1: \n"); printf("Valor: %d\n", numero1); printf("Endereco na memoria: %d\n\n", &numero1); printf("numero2: \n"); printf("Valor: %d\n", numero2); printf("Endereco na memoria: %d\n\n", &numero2); printf("letra1: \n"); printf("Valor: %c\n", letra1); printf("Endereco na memoria: %d\n\n", &letra1); printf("letra2: \n"); printf("Valor: %c\n", letra2); printf("Endereco na memoria: %d\n\n", &letra2); return 0; }
a

14 comentários:

Glauco Pires disse...

Na compilação apresentou esta mensagem aviso: formato ‘%d’ espera argumento do tipo ‘int’, porém o argumento 2 possui tipo ‘int *’ [-Wformat] . Então nas linhas (ex) printf("Endereco na memoria: %p\n\n", &numero1); troquei '%d' por '%p'

Rafael disse...

Muito bom o curso. Estou gostando bastante! Parabéns pela iniciativa e sucesso!

Anônimo disse...

Um sucesso essa pagina!

rimaS disse...

Olá ! Aprimorei o código anterior:

// Ponteiros: incremento / decremento
#include < stdio.h >
#include < string.h >
#include < ctype.h >
#define DIM 8
#define DIM2 9
int a=10,b=23;
int *ptr_x = NULL;
void preenche_com_end_mem(int end_var, char j[DIM]); // protótipo de função
void mostra_pointer(int *ptr_x, char j, int end_ptr);
void tecle_anything(); // protótipo de função
main()
{
while(1)
{
system("cls || clear");
printf("PONTEIRO: variavel que armazena endereco de memoria de outra variavel.\n");
printf("INCREMENTO / DECREMENTO de Ponteiros:\n\n");
printf("Declaracoes de variaveis feitas nesse programa:\n");
printf("int a=%d, b=%d;\n",a,b);
printf("int *ptr_x = NULL;\n\n");
printf("Endereco de memoria da variavel (a) : &a = %d\n", &a);
printf("Endereco de memoria da variavel (b) : &b = %d\n", &b);
printf("Endereco de memoria do ponteiro (ptr_x): &ptr_x = %d\n\n", &ptr_x);
printf("Como o tipo (int) tem %d bytes de tamanho:\n",sizeof(int));
preenche_com_end_mem((int)&a,"(a)");
preenche_com_end_mem((int)&b,"(b)");
preenche_com_end_mem((int)&ptr_x, "(ptr_x)");
printf("\n\n");
tecle_anything();
printf("\rE agora, escrevendo: ptr_x = &a; ... como fica ?\n");
ptr_x = &a;
tecle_anything();
mostra_pointer(ptr_x, 'a',(int) &ptr_x);
tecle_anything();
printf("\rINCREMENTO de Ponteiro: instrucao ptr_x++; para onde apontara ptr_x agora?\n");
ptr_x++;
tecle_anything();
mostra_pointer(ptr_x, 'b',(int) &ptr_x);
tecle_anything();
printf("\rDECREMENTO de Ponteiro: instrucao ptr_x--; para onde apontara ptr_x agora?\n");
ptr_x--;
tecle_anything();
mostra_pointer(ptr_x, 'b',(int) &ptr_x);
system("pause");
}
}
// FUNÇÃO que mostra os endereços de memória usados por uma variável
void preenche_com_end_mem(int end_var, char j[DIM])
{
int v[sizeof(int)], i;
char tmp[DIM2];
v[0]=end_var;
for(i=1;i<=sizeof(int);i++)
{v[i]=v[i-1]+1;}
if (strcmp(j,"(ptr_x)")!=0)
strcpy(tmp,"Variavel");
else
strcpy(tmp,"Ponteiro");
printf("%s %-7s ocupa os enderecos: ",tmp,j);
for(i=0;i<sizeof(int);i++)
{printf("|%d| ",v[i]);}
printf("\n");
}
// FUNÇÃO Mostra as informações do Ponteiro
void mostra_pointer(int *ptr_x, char j, int end_ptr)
{
printf("\rConteudo do Ponteiro = endereco de memoria da variavel (%c): prt_x = %d\n", j, ptr_x);
printf("Conteudo da variavel apontada = variavel (%c): *prt_x = %d\n", j, *ptr_x);
printf("O Ponteiro tambem tem o seu proprio endereco: &prt_x = %d\n\n", end_ptr);
}
// FUNÇÃO
void tecle_anything()
{
printf("Digite qualquer tecla para continuar...");getch();
}

rimaS disse...

Olá ! Rotina básica sobre ponteiros:

// apresentação de Ponteiros
#include < stdio.h >
int a;
int *ptr_a = NULL;
void tecle_anything(); // protótipo da função
main()
{
while(1)
{
system("cls || clear");
printf("PONTEIRO: variavel que armazena endereco de memoria de outra variavel.\n");
printf("BASICO:\n\n");
printf("Declaracoes de variaveis feitas nesse programa:\n");
printf("int a;\n");
printf("int *ptr_a = NULL;\n\n");
printf("Vamos atribuir um valor para a variavel (a): ");scanf("%d", &a);
printf("Variavel (a): %d\n", a);
printf("Endereco de memoria da variavel (a): &a = %d\n", &a);
tecle_anything();
printf("\rE agora, escrevendo: ptr_a = &a; ... como fica ?\n");
ptr_a = &a;
tecle_anything();
printf("\rConteudo do Ponteiro = endereco de memoria da variavel (a): prt_a = %d\n", ptr_a);
printf("Conteudo da variavel apontada = variavel (a): *prt_a = %d\n", *ptr_a);
printf("O Ponteiro tambem tem o seu proprio endereco: &prt_a = %d\n\n", &ptr_a);
tecle_anything();
printf("\rAlterando o valor da variavel (a), atraves do Ponteiro: digite um valor: "); scanf("%d", ptr_a);
printf("Observe que tudo fica igual, menos o valor da variavel (a) e *prt_a:\n");
tecle_anything();
printf("\rAplicando scanf(\"%%d\", ptr_a); ou a instrucao *ptr_a = %d, obtemos:\n", *ptr_a);
printf("Variavel (a): %d\n", a);
printf("Endereco de memoria da variavel (a): &a = %d\n", &a);
printf("Conteudo do Ponteiro = endereco de memoria da variavel (a): prt_a = %d\n", ptr_a);
printf("Conteudo da variavel apontada = variavel (a): *prt_a = %d\n", *ptr_a);
printf("O Ponteiro tambem tem o seu proprio endereco: &prt_a = %d\n\n\n", &ptr_a);
system("pause");
}
}
// função tecle_anything
void tecle_anything()
{
printf("Digite qualquer tecla para continuar...");getch();
}

rimaS disse...

Olá ! Segue abaixo um programa simples, mas que ajuda a entender os ponteiros de ponteiros:

// Ponteiros de Ponteiros
#include < stdio.h >
int a;
int *ptr_a1 = NULL; // declaração do ponteiro da variável (a)
int **ptr_a2 = NULL; // declaração do ponteiro para o ponteiro da variável (a)
int ***ptr_a3 = NULL; // declaração do ponteiro para o ponteiro para o ponteiro da variável (a)
void tecle_anything(); // protótipo da função
main()
{
while(1)
{
system("cls || clear");
printf("PONTEIROS DE PONTEIROS: 3 niveis de Ponteiros\n");
printf("Observe como os (*) mudam o conteudo apontado pelos Ponteiros.\n");
printf("Nao ha um limite pre-determinado para o uso de niveis, atraves dos (*).\n\n");
printf("Declaracoes de variaveis feitas nesse programa:\n");
printf("int a;\n");
printf("int *ptr_a1 = NULL;\n");
printf("int **ptr_a2 = NULL;\n");
printf("int ***ptr_a3 = NULL;\n");
printf("Vamos atribuir um valor para a variavel (a): ");scanf("%d", &a);
printf("Variavel (a): %d\n", a);
printf("Endereco de memoria da variavel (a): &a = %d\n", &a);
tecle_anything();
printf("\rE agora, escrevendo: ptr_a1 = &a; ... como fica ?\n");
ptr_a1 = &a; // // ponteiro para a variável (a)
tecle_anything();
printf("\nPRIMEIRO NIVEL DE PONTEIRO:\n");
printf("\rConteudo do Ponteiro = endereco de memoria da variavel (a): ptr_a1 = %d\n", ptr_a1);
printf("Conteudo da variavel apontada = variavel (a): *ptr_a1 = %d\n", *ptr_a1);
printf("O Ponteiro tambem tem o seu proprio endereco: &ptr_a1 = %d\n\n", &ptr_a1);
tecle_anything();
printf("\rE agora, escrevendo: ptr_a2 = &ptr_a1; ... como fica ?\n");
ptr_a2 = &ptr_a1; // // ponteiro para ponteiro para a variável (a)
tecle_anything();
printf("\nSEGUNDO NIVEL DE PONTEIRO:\n");
printf("\rConteudo do Ponteiro = endereco de memoria do Ponteiro ptr_a1: ptr_a2 = %d\n", ptr_a2);
printf("Observe o conteudo que \x82 apontado pelo Ponteiro = *ptr_a2 = %d\n", *ptr_a2);
printf("Observe o conteudo que \x82 apontado pelo Ponteiro = **ptr_a2 = %d\n", **ptr_a2);
printf("O Ponteiro tambem tem o seu proprio endereco: &ptr_a2 = %d\n\n", &ptr_a2);
tecle_anything();
printf("\rE agora, escrevendo: ptr_a3 = &ptr_a2; ... como fica ?\n");
ptr_a3 = &ptr_a2; // // ponteiro para ponteiro para ponteiro para a variável (a)
tecle_anything();
printf("\nTERCEIRO NIVEL DE PONTEIRO:\n");
printf("\rConteudo do Ponteiro = endereco de memoria do Ponteiro ptr_a2: ptr_a3 = %d\n", ptr_a3);
printf("Observe o conteudo que \x82 apontado pelo Ponteiro = *ptr_a3 = %d\n", *ptr_a3);
printf("Observe o conteudo que \x82 apontado pelo Ponteiro = **ptr_a3 = %d\n", **ptr_a3);
printf("Observe o conteudo que \x82 apontado pelo Ponteiro = ***ptr_a3 = %d\n", ***ptr_a3);
printf("O Ponteiro tambem tem o seu proprio endereco: &ptr_a3 = %d\n\n", &ptr_a3);
system("pause");
}
}
// função tecle_anything
void tecle_anything()
{
printf("Digite qualquer tecla para continuar...");getch();
printf("\r \r");
}

Anônimo disse...

Ótimo artigo!

Anônimo disse...

E quem é Rimass no jogo do bixo???

Christopher disse...

Olá!
Muito bom esse curso. Uma dica remova o & antes das variáveis na função printf, pois este operador não é necessário ali. Abs!

Elizabeth Queiroz disse...

Christopher, ele está dando printf no endereço, e não no valor. Quando se usa o & antes do nome da variável estamos falando do endereço dela. Deu pra ver que você não leu ou não entendeu.

Matheus disse...

ótima didática ! Parabéns pela abordagem e exemplos de um assunto tão importante que é ponteiros

Paulo Ricardo disse...

Muito esse curso...explicações excelentes...muito didático...conciso, preciso, muito bom mesmo.

Unknown disse...

Quando rodei aqui, o código me retornou isso:

numero1:
Valor: 1
Endereco na memoria: 588443648

numero2:
Valor: 2
Endereco na memoria: 588443652

letra1:
Valor: a
Endereco na memoria: 588443646

letra2:
Valor: b
Endereco na memoria: 588443647

Alterando os valores...


o Endereço na memoria está me retornando "Lixo", certo?

Unknown disse...

Ótima explicação!