Qual a diferença entre ponteiro é uma variável não ponteiro?

(excepto o último tópico, adaptado de http://www.mspc.eng.br/info/cpp_ptr_10.shtml)

Introdução

Os ponteiros são um dos recursos mais poderosos da linguagem C. Qualquer programa de utilidade prática escrito em C dificilmente dispensará o uso de ponteiros. A tentativa de evitá-los implicará quase sempre códigos maiores e de execução mais lenta.

Para quem está a começar, pode parecer um conceito difícil. Mas não há outro caminho senão enfrentar a realidade.

São muitas as aplicações de ponteiros. A seguir, apresenta-se uma relação das mais comuns.

• Aceder a endereços de memória que o programa aloca em tempo de execução.
• Aceder a variáveis que não são visíveis (locais) numa função.
• Manipulação de arrays (vectores).
• Manipulação de strings (cadeias de caracteres).
• Passar o endereço de uma função para outra (sim, as funções também têm o seu próprio endereço!)
• Devolver mais do que um valor numa função (através do

return apenas é possível devolver um valor)

Variáveis revisitadas: lvalue e rvalue

Uma variável é uma zona (conjunto contíguo de bytes) na memória. Por exemplo, no caso de uma variável do tipo inteiro, uma zona de 4 bytes. Estando todas as posições de memória endereçadas univocamente, podemos associar a cada variável o endereço de memória onde essa zona começa.

A esse endereço chamamos endereço da variável

Sendo assim, a uma variável estão sempre associados dois valores:

  • lvalue (left value): é o endereço da variável. Sempre que uma variável aparece do lado esquerdo do operador de atribuição, é interpretada por este valor.
  • rvalue (right value): é o valor atribuído, de facto, à variável. Sempre que uma variável aparece noutros contextos que não o anterior, é substituída por este valor.

Exemplo:

...

int x, y;

...

x = 10;         // No endereço de memória de x (lvalue de x) armazenar 

                // o valor 10;

y = x + 1;      // No endereço de memória de y (lvalue de y) armazenar 

                // o resultado da avaliação da expressão "x + 1". Durante

                // a avaliação da expressão, x será substituído pelo seu

                // rvalue, que é 10 (a expressão resulta em 11).

y = 2 * y;      // No endereço de memória de y (lvalue de y) armazenar 

                // o resultado da avaliação da expressão "2 * y". Durante

                // a avaliação da expressão, y será substituído pelo seu

                // rvalue, que é 11 (a expressão resulta em 22).

...

Definição e declaração 

Um ponteiro é uma variável que contém o endereço de um objecto de dados, em geral, outra variável. Essa é a razão para o seu nome: ele aponta para outra variável.

Conforme qualquer variável em C, o ponteiro deve ser declarado antes de ser usado. Basta inserir o operador de indirecção (*) após o tipo da variável:

int *ptr;

Esta declaração define a variável

ptr como um ponteiro para uma variável do tipo int.

Uma declaração de ponteiro não tem o mesmo significado que uma declaração de uma variável. Ela indica apenas o tipo de objeto de dados apontado e o tamanho em bytes que ocupa não tem relação com o tamanho do objeto apontado. O tamanho do ponteiro é fixo e depende apenas do modelo de memória do sistema (2 bytes ou 4 bytes, normalmente).

Para declarar mais de um ponteiro por linha, usa-se um operador de indirecção (

*) para cada um deles. Se um operador for omitido (exemplo: char *ch2, ch2;), a variável correspondente não será ponteiro e, certamente, provocará erro de execução se usada como tal:

char *ch2, ch2;     (um ponteiro para uma variável do tipo char e uma variável do tipo char).

char *ch2, *ch2;    (dois ponteiros para variáveis do tipo char).

O ponteiro pode ser declarado para qualquer tipo legal de variável em C (

char, int, float, double, etc), além de void *, que declara um ponteiro genérico, que pode apontar para qualquer tipo de dados.

Inicialização de um ponteiro

A simples declaração de um ponteiro não faz dele útil. É necessária a indicação da variável para a qual ele aponta.

Qual a diferença entre ponteiro é uma variável não ponteiro?
Fig 01

int var;
int *ptr;
var = 10;
ptr = &var;

Na sequência acima, são declarados uma variável tipo int (var) e um ponteiro para o mesmo tipo (ptr). A terceira linha atribui o valor 10 a var e a última linha inicializa o ponteiro ptr.

Observe-se o uso do operador de endereçamento (

&) para inicialização do ponteiro. Isso significa, no código ptr = &var, que ptr passa a conter o endereço de var, e não o seu valor.

Supondo que o sistema é endereçado por 2 bytes, a Figura 01 acima dá uma idéia gráfica dessa associação: o conteúdo de ptr é 4052, que é o endereço do primeiro byte da variável var (ptr ocupa 2 bytes por causa do endereçamento do sistema e var também ocupa 2 bytes, mas por ser do tipo int).

O valor 4052 para a posição de memória de

var é apenas ilustrativo. Na prática, dependerá do local de memória onde o programa foi carregado.

Com

ptr apontando para var, é possível realizar operações com esta última de forma indirecta (por isso se chama ao operador * de indirecção), a partir de ptr. Exemplos a seguir.

Acrescentando a linha

int outra_var = *ptr;

ao código anterior, o valor de

outra_var seria 10, que é o valor de var lido indiretamente através de ptr.

E a linha

*ptr = 20;

modifica o valor de

var para 20, ou seja, altera o valor de var de forma indireta através de ptr.

É importante lembrar que um ponteiro declarado e não inicializado poderá ter conteúdo nulo ou aleatório, a depender da alocação sistema. Nessas condições, se o conteúdo apontado for lido/modificado, algumas posições de memória verão seus valores indevidamente lidos/alterados e as consequências serão imprevisíveis.

Passagem de parâmetros por valor e por referência

Até aqui, e durante o estudo que fizemos da utilização de funções, a passagem de parâmetros que utilizámos foi sempre por valor. Isto significa que o que a função recebe é uma cópia dos valores das variáveis que são passadas por parâmetro, sendo os seus parâmetros formais (que funcionalmente são variáveis locais à função) inicializados com estes valores na altura da chamada da função.

E se desejarmos que o valor de uma variável externa à função seja alterada dentro da função?

Podemos aproveitar o conceito de ponteiro para conseguir este efeito. Passando o valor de um endereço a uma função, a função ganha acesso indirecto a essa posição de memória.

Estude e execute o seguinte código:

Passagem por valor -vs- Passagem por referência

#include<stdio.h>

// PASSAGEM POR VALOR - Não altera as variáveis

// da função main()!!!

void troca1(int x, int y){

     int aux;

     aux = x;

     x = y;

     y = aux;

}

// PASSAGEM POR REFERÊNCIA - Permite alterar as 

// variáveis da função main()!!!

void troca2(int *x, int *y){

     int aux;

     aux = *x;

     *x = *y;

     *y = aux;

}

int main(){

    int a=10, b=20;

    printf("Antes de troca1: a=%d, b=%d\n", a, b);

    troca1(a, b);

    printf("Depois de troca1: a=%d, b=%d\n", a, b);    

    troca2(&a, &b);

    printf("Depois de troca2: a=%d, b=%d\n", a, b);    

    getchar();      

}


Na função main(), repare na diferença entre a chamada de troca1() e troca2(). Na segunda são passados à função os endereços das variáveis a e b, e não os seus valores. Dentro de troca2(), os valores de a e b são modificados indirectamente (por indirecção).

O que é uma variável ponteiro?

Um ponteiro é uma variável que contém um endereço de memória. Esse endereço é normalmente a posição de uma outra variável na memória. Se uma variável contém o endereço de uma outra, então a primeira variável edita para apontar para a segunda. Se uma variável irá conter um ponteiro, ela deve ser declarada como tal.

Qual a diferença entre um ponteiro é uma referência?

A diferença entre ponteiro e referência é sutil. Conceitualmente são dois acessos de indiretos à dados. Tanto ponteiros quanto referência o fazem através de um endereçamento. A diferença é que nas referências esse endereçamento é mais abstrato que no caso dos ponteiros.

O que é um ponteiro em programação?

Um ponteiro é uma variável que armazena o endereço de memória de um objeto. Os ponteiros são usados extensivamente em C e C++ para três propósitos principais: para alocar novos objetos no heap, para passar funções para outras funções.

Qual é a função do ponteiro?

O uso de ponteiros para funções em C serve principalmente para definir, em tempo de execução, qual função será executada, sem a necessidade de escrever o nome da função, de forma explícita naquele ponto do código.