Revisão de C: Ponteiros e Arrays

De Aulas

Links Relacionados: DAS5102 Fundamentos da Estrutura da Informação

Ponteiros e Arrays

Ponteiros são fundamentais para a programação bem sucedida em C:

  • Passagem de parâmetros por referência;
  • Alocação dinâmica de memória;
  • Aumentar a eficiência de certar rotinas.

Essencialmente, um ponteiro nada mais é do que uma variável que ao invés de conter um valor, contém um endereço de memória.

O conceito de utilizar espaço em memória não é em absoluto novo. De fato, linguagens de máquina utilizam tal conceito todo o tempo.

Exemplo 1 – mnemônico (Intel 80C51) que executa a soma do conteúdo da RAM interna apontada por Ri ao Acumulador:

ADD A, @RI

Declaração de Ponteiros

<tipo> *<nome_variavel>

Exemplo2 - exemplo da declaração de diversos ponteiros:

1int *iptr;
2float *fptr;
3char *cptr;

Como um ponteiro armazena um endereço de memória e não um valor específico, pode parecer estranho a primeira vista que seja requerida a associação de um tipo ao se declaram um ponteiro. No entanto, muito comumente, deseja-se acessar o valor armazenado pela variável apontada por um ponteiro. Desta forma, o tipo do ponteiro serve para instruir o compilador na maneira pela qual o dado pontado será acessado. Essencialmente, o tipo de dados e a codificação do dado (como os bits serão interpretados, necessário por exemplo para tipos com base na notação de ponto flutuante).

Operadores de Ponteiros

  • "*" conteúdo: aplicado a um variável tipo ponteiro, retorna o conteúdo do endereço apontado pelo ponteiro;
  • "&" endereço: aplicado a uma variável, retorna seu endereço de memória.

Exemplo 3 – exemplo da utilização de ambos os operadores unários:

1int count; 
2int *iptr; 
3iptr = &count; 
4printf(%d, *iptr);

Inicialização de Ponteiros

  • Ponteiros são inicializados ao definirmos um endereço para o qual eles devem

apontar.

  • Ponteiros não inicializados são também chamados de “ponteiros selvagens”
  • A não inicialização de ponteiros é um dos principais fatores para erros em tempo de execução em programas escritos na linguagem C. Tais erros são difíceis de identificar e corrigir, porque o tempo decorrido entre o acesso ilegal à memória de um ponteiro e a efetiva ocorrência do erro é imprevisível.

Uma maneira de garantir que um ponteiro declarado não apontará para um lugar qualquer da memória é atribuir o valor NULL para o mesmo.

1int *iptr = null;

Ponteiros e Arrays

Figura 1 – Exemplo de posicionamento de um array na memória

Ponteiros são muito utilizados para a navegação de arrays, principalmente de arrays alocados de maneira dinâmica. A idéia geral é alocar o array usando ou o método estático ou o dinâmico e então criar uma variável do tipo ponteiro que apontará para a primeira posição do ponteiro. Utilizando aritmética de ponteiros (incremento, decremento, saltos) é possível percorrer o array sem ter que se preocupar com quantos bytes devem ser acessados, pois a tipagem do ponteiro cuidará deste detalhe.

É importante ressaltar que a utilização de ponteiros para indexação de arrays apresenta um risco intrínseco. Como C não é uma linguagem fortemente tipada, arrays não possuem um marcador de fim de alocação de memória tal como acontece em PASCAL. Ponteiros permitirão que a memória continue sendo acessada sequencialmente mesmo após o final da área previamente alocada para o array tenha sido toda percorrida.

Aritmética de Ponteiros

Uma das grandes vantagens decorrentes da utilização de ponteiros é a possibilidade de indexar posições de memória de maneira fácil e intuitiva. Tal endereçamento é alcançado a partir de operações aritméticas simples que tem como objetos variáveis do tipo ponteiro.

Operandos unários:

  • ++ incremento unitário;
  • -- decremento unitário.

Suponha que p é um ponteiro do tipo inteiro e suponha que o tipo inteiro na arquitetura de destino do exemplo sejam representados por 2 bytes cada. Considere o seguinte trecho de código:

1int vet[10]; 
2int *iptr; 
3iptr = vet; 
4iptr++;

Exemplo 4 – navegação de arrays usando ponteiros

Assumindo que o array “vet” seja alocado pelo “loader” do sistema operacional iniciando na posição 00H, iptr erá apontar para a posição 00H inicialmente. Ao se executar a operação iptr++, o endereço apontado por iptr será incrementado em uma unidade, o que corresponderá, dado o fato de que inteiros são representados por dois bytes, ao endereço 02H. (veja figura1 para exemplo)

Comparação de Ponteiros

O ponto importante a respeito da comparação de ponteiros a ser considerado diz respeito a semântica da comparação. Como ponteiros são variáveis que contém endereços de memória, ao compararmos ponteiros o que estamos fazendo realmente é verificando se dois endereços de memória são idênticos ou não.

A primeira vista, tal tipo de comparação pode parecer de pouca importância, no entanto ela será de grande valor futuramente quando ponteiros forem utilizados para manipular estruturas de dados complexas tal como listas ligadas ou árvores. Podemos comparar se dois ponteiros são iguais menores ou maiores exatamente como se compararam valores de variáveis.

1int vet[7]; 
2int *iptr1, *iptr2; 
3iptr1 = &vet[0]; 
4iptr2 = &vet[6]; 
5iptr1 == iptr2; 
6iptr1 <  iptr2; 
7iptr1 >  iptr2; 
8iptr1 <= iptr2;

Exemplo 5 – comparação de ponteiros

Programa Exemplo

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4int main()
 5{
 6    //int count = 22;
 7    //int aux = 456;
 8    int *iptr = (int*) malloc(100*sizeof(int));
 9    //int *idx = NULL;
10    //idx = iptr;
11    int i;
12    for (i = 0; i < 100; i++)
13    {
14        iptr[i] = i * 10;
15        //*idx = i * 10;
16        //idx++;
17    }
18
19    for (i = 0; i < 10; i++)
20    {
21        printf("Elemento %d = %d\n", i, iptr[i]);
22    }
23
24    //iptr = &count;
25    /*
26    if (iptr != NULL)
27    {
28        printf("Endereco: %p - Valor %d\n", &count , count);
29        printf("Ponteiro: %p - Valor0 %d - Valor1 %d\n", iptr, iptr[0], iptr[1]);
30    }
31    else
32    {
33        printf("Ponteiro Nulo");
34    }
35    */
36    return 0;
37}

Alocação de vetores de Strings

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4#define SIZE	2
 5
 6int main()
 7{
 8	int i;
 9	// Vetor estatico de strings estaticas
10	char vet1[SIZE][255] =  { "Estatico-1", "Estatico-2" };
11	for (i = 0; i < SIZE; i++)
12	{
13		printf("%s - %s\n", vet1[0], vet1[1]);
14	}
15
16	// Vetor dinamico de strings dinamicas
17	char **vet;
18	vet = (char**) malloc (SIZE * sizeof(char*));
19	for (i = 0; i < SIZE; i++)
20	{	
21		vet[i] = malloc (255 * sizeof(char));
22		sprintf(vet[i], "Dinamico-%d", i);
23	}
24	for (i = 0; i < SIZE; i++)
25	{ 
26		printf("%s\n", vet[i]);
27	}
28}

Lista de Exercícios

1. O que á de errado com o programa abaixo? Como poderíamos corrigi-lo?

1void main( void )
2{ 
3  int x, *p; 
4  x = 10; 
5  *p = x; 
6}

2. Seja o seguinte trecho de programa:

1int i = 3, j = 5; 
2int *p, *q; 
3p = &i; 
4q = &j;

Qual será o valor das seguintes expressões:

a) p==&i;

b) *p - *q;

c) &p==&i;

d) 3* *p/(*q)+7

3. Qual será a saída do programa supondo que i ocupa o endereço 4094H na memória?

1int main( void ) 
2{ 
3  int i=5, *p; 
4  p=&i; 
5  printf(%d %d %d %d %d\n, p, *p+2, **&p, 3**p, **&p+4); 
6}

4. Assumindo que Pulo[] é um array do tipo int, quais das seguintes expressões referenciam o valor do terceiro elemento do array?

a) *(pulo+2)

b) *(pulo+4)

c) pulo+4

d) pulo+2

5. Suponha a declaração:

1int mat[4], *p, x;

Quais das expressões abaixo são válidas? Justifique:

a) p = mat+1;

b) p = mat++;

c) p = ++mat;

d) x = (*mat)++;


6. Explique a diferença entre os comandos abaixo: (assuma que p é um ponteiro do tipo int)

a) p++;

b) (*p)++;

c) *(p++);


7. Explique o programa abaixo. Encontre o erro, corrija-o para que o mesmo escreva o número 10 na tela.

 1#include <stdio.h> 
 2int main( void ) 
 3{ 
 4  int x, *p, **p; 
 5  p = &x; 
 6  q = &p; 
 7  x = 10; 
 8  printf(“\n%d\n, &q); 
 9  return ( 0 ); 
10}