sobre variáveis, ponteiros, arrays, parâmetros e alocação ...lucas.frucht/variaveis.pdf ·...
TRANSCRIPT
Sobre Variáveis, Ponteiros, Arrays, Parâmetros e Alocação de Memória
Queridos,
Este texto ainda não está terminado. Ainda vou mudar alguns trechos e ajeitar a
formatação, principalmente dos exemplos. Ainda assim, preferi divulgar essa versão
temporária para que vocês possam estudar para a prova de terça-feira.
O texto trata também de alguns assuntos que vocês ainda não aprenderam. Por ora,
podem pular as partes que falam de linguagens forte e fracamente tipadas, ponteiros e
alocação de memória.
Como sempre, fico a disposição para tirar qualquer dúvida que possa surgir sobre o
texto.
Boa prova!
Cordialmente,
Lucas Frucht
Variáveis
Por trás de todos os demais conceitos que serão abordados nesse texto está o conceito de variável.
Embora seja um conceito relativamente simples (ou talvez, justamente, por ser simples), ele não costuma
ser explorado em profundidade. Dominar este conceito é a chave para entender os demais.
Declarar uma variável em seu código-fonte é dizer ao compilador que ele deve reservar um espaço na
memória com um determinado tamanho (que depende do tipo da variável) e interpretar o conteúdo
daquele espaço como sendo de um determinado tipo. A segunda é mais importante nas linguagens
fortemente tipadas. O nome que damos à variável é um rótulo com o qual a identificamos para o
compilador.
program variavel;
var
a : integer;
b : char;
c : real;
begin
end.
Rotulos Endereço Conteúdo
0000 0000
⁞
a 00c4 5d00
00c4 5d01
b 00c4 5d02
c 00c4 5d03
00c4 5d04
00c4 5d05
00c4 5d06
00c4 5d07
⁞
Uma vez que o espaço é reservado, ele poderá ser usado para armazenar diferentes valores ao longo da
execução do programa. Sempre que usamos uma variável o compilador acessa a posição da memória
associada àquele rotulo para obter seu conteúdo atual. Quando atribuímos um novo valor a ela, o
compilador armazena o valor na posição de memória associada ao rotulo.
Vale lembrar que não existe posição de memória vazia. Quando o compilador reserva o espaço na
memória ele continuará com a informação que estava lá antes do programa ser executado. Por esse
motivo, nunca podemos assumir que uma variável guarda um determinado valor sem antes inicializá-la
(dar a ela um valor inicial).
program variavel;
var
a : integer;
b : char;
c : real;
begin
a := 20;
end.
Rotulos Endereço Conteúdo
0000 0000 ?
⁞ ⁞
a 00c4 5d00 20
00c4 5d01
b 00c4 5d02 ?
c 00c4 5d03
? 00c4 5d04
00c4 5d05
00c4 5d06
00c4 5d07 ?
⁞ ⁞
Pascal é uma linguagem fortemente tipada. Suas variáveis dos tipos byte e char, por exemplo, têm o
mesmo tamanho, mas seus conteúdos são interpretados de forma distinta. O tipo byte é usado para
armazenar números, enquanto o tipo char é usado para armazenar caracteres. A linguagem não permite
que variáveis do tipo byte sejam usadas em funções (e procedimentos) criadas para manipular caracteres.
Da mesma forma, não é permitido que variáveis do tipo char sejam usadas em funções (e procedimentos)
criadas para manipular números, nem em expressões aritméticas. Qualquer um desses usos inadequados
irá resultar em um erro de compilação.
C é uma linguagem fracamente tipada. Não há nenhum outro tipo nativo cujas variáveis tenham o mesmo
tamanho das variáveis do tipo char. Variáveis desse tipo podem ser usadas tanto para armazenar
caracteres quanto números e cabe ao desenvolvedor garantir que as funções que manipularão essas
variáveis interpretarão seu conteúdo de forma adequada. É possível, inclusive, somar o caractere ‘1’ com
o caractere ‘2’; e o resultado não será o caractere ‘3’ (tipicamente será o caractere ‘c’). Já se o caractere
‘a’ for somado com o número 1 o resultado, tipicamente, será ‘b’1.
Ponteiros
Embora exista uma grande mítica em torno deles, os ponteiros não são nada além de mais um tipo de
variável. A única diferença está no modo como o compilador interpreta o conteúdo de variáveis desse
tipo. Ao invés de interpretar como um caractere, um número inteiro ou um decimal, o conteúdo de
ponteiros será interpretado como um endereço de memória2. Por isso, que se diz que um ponteiro
“aponta” para uma posição de memória.
As linguagens que possuem ponteiros oferecem operadores para permitir que seja acessado o conteúdo da
posição de memória que está armazenada em um ponteiro. Adicionalmente, o tipo do ponteiro indica o
1 Foram considerados resultados típicos os que são obtidos quando é usada uma tabela de codificação baseada no ASCII, como
é a maioria das utilizadas atualmente. 2 Em linguagens fracamente tipadas um número inteiro ou real pode ser interpretado com um ponteiro e vice-versa.
tamanho e como deve ser interpretado este conteúdo. Essas linguagens oferecem também operadores que
indicam o endereço de uma variável.
program ponteiros;
var
a, b, c : integer;
ptrB, ptrC : ^integer;
begin
a := 2;
ptrA := @a;
ptrB := @b;
^ptrB := 3;
c := ^ptrA;
end.
Rotulos Endereço Conteúdo
0000 0000 ?
⁞ ⁞
a 00c4 5d00 2
00c4 5d01
b 00c4 5d02 3
00c4 5d03
c 00c4 5d04 2
00c4 5d05
ptrA 00c4 5d06
00ca 5d00 00c4 5d07
00c4 5d08
00c4 5d09
ptrB 00c4 5d0a
00c4 5d02 00c4 5d0b
00c4 5d0c
00c4 5d0d
⁞ ⁞
Arrays
Um array é um conjunto de variáveis de um mesmo tipo. Geralmente elas são dispostas seqüencialmente
na memória. Desta forma, supondo que um inteiro ocupe 4 bytes, quando se declara um array de 10
inteiros, se está indicando que o compilador deve reservar 40 bytes de memória e interpretar cada 4 bytes
como um número inteiro. O nome dado ao array é um rotulo para o endereço da primeira posição do
array, ou de um ponteiro para sua primeira posição. O operador que indica as posições dentro do array
indica, na verdade, um deslocamento a partir do endereço inicial.
program ponteiros;
var
a : array [1..5] of integer;
b : integer;
begin
for b := 1 to 5 do
a[b] := b * 2;
end.
Rotulos Endereço Conteúdo
0000 0000 ?
⁞ ⁞
a 00c4 5d00 2
00c4 5d01
00c4 5d02 4
00c4 5d03
00c4 5d04 6
00c4 5d05
00c4 5d06 8
00c4 5d07
00c4 5d08 10
00c4 5d09
b 00c4 5d0a 1 2 3 4 5
00c4 5d0b
00c4 5d0c ?
00c4 5d0d ?
⁞ ⁞
Alocação de Memória
Como já foi visto, para toda variável e todo array declarado no seu programa o compilador separa um
espaço na memória onde o conteúdo será armazenado. O tamanho total desse espaço deve ser conhecido
no momento que o código for compilado. Sempre que o programa for carregado na memória esse espaço
será reservado. Esta alocação é chamada estática.
Passagem de Parâmetros
Os parâmetros passados para uma função (ou procedimento)3 são a comunicação de quem está chamando
a função para a função. Devem estar na lista de parâmetros todas as informações que a função deve
receber para funcionar de forma genérica. Embora, em teoria, essa comunicação possa se dar através de
variáveis globais, essa não é considerada uma boa prática pois deixa o código mais confuso e dificulta o
reaproveitamento de código.
Variáveis locais, que são usadas internamente para a operação da função e não tem sentido fora dela, não
devem estar na lista de parâmetros. Por outro lado, os parâmetros declarados já são variáveis locais da sua
função e não podem ser redeclarados no corpo dela.
A comunicação de volta da função para quem a chamou costuma se dar através de um valor de retorno.
No Pascal, este valor é definido em uma variável com o mesmo nome da função.
3 Diferentemente de Pascal, a maioria das linguagens não faz distinção entre funções e procedimentos, considerando um
procedimento como uma função que não retorna valor algum. Por esse motivo, as explicações serão dadas com base em
funções, mas, com exceção do valor de retorno, tudo se aplica de forma idêntica aos procedimentos.
program ponteiros;
function somatorio(
a : array of integer;
i : integer;
limite : integer) : integer;
var
i : integer;
limite : integer;
begin
somatorio := 0;
for i := 0 to limite -1 do
somatorio := somatorio + a[i];
end;
var
a : array[0..2] of integer;
b : integer;
begin
a[0] := 4;
a[1] := 7;
a[2] := 2;
b := 3;
b := somatorio(a, b);
end.
Existem duas formas de passagem de parâmetros. A passagem por valor consiste em copiar o valor de
uma variável (ou constante) para o parâmetro correspondente na função. Esta correspondência costuma se
dar pela ordem dos parâmetros na declaração e na chamada da função.4 Caso a função altere o valor dos
parâmetros ela estará manipulando uma variável completamente diferente, logo, essa alteração não terá
reflexo em quem chamou a função.
Já na passagem por referência a função não recebe o valor da variável, mas sua referência, ou seja o local
onde ela está armazenada. Desta forma, ao invés de se criar uma variável nova, se está dando apenas um
novo nome para a variável que já existe. Esse nome é válido apenas dentro da função, mas permite que
ela manipule diretamente a variável original. Caso a função altere o valor de um parâmetro passado por
referência, a alteração acontece também onde a função foi chamada. Desta forma a passagem por
referência serve também como uma comunicação “indireta” da função para quem a chamou.
4 Algumas linguagens trabalham com parâmetros nomeados, não importando a ordem com que eles são passados.
program ponteiros;
procedure s1(x, y, z : integer);
begin
z := x + y;
end;
procedure s2(x, y : integer;
var z : integer);
begin
z := x + y;
end;
var
a, b, c, d : integer;
begin
a := 2;
b := 3;
s1(a, b, c);
s2(a, b, d);
end.
Rotulos Endereço Conteúdo
0000 0000 ?
⁞ ⁞
s1.x 00c4 5d00 2
00c4 5d01
s1.y 00c4 5d02 3
00c4 5d03
s1.z 00c4 5d04 5
00c4 5d05
s2.x 00c4 5d06 2
00c4 5d07
s2.y 00c4 5d08 3
00c4 5d09
a 00c4 5d0a 2
00c4 5d0b
b 00c4 5d0c 3
00c4 5d0d
c 00c4 5d0e ?
00c4 5d0f
d s2.z 00c4 5d10 5
00c4 5d11
⁞ ⁞