O documento discute os conceitos de vinculação em programação, definindo-a como a associação entre abstrações de código e entidades manipuladas pelo hardware. Apresenta os atributos de vinculação - nome, tipo, armazenamento e valor - e explica que vinculações podem ser estáticas ou dinâmicas, ocorrendo em diferentes momentos do ciclo de vida de um programa. Também aborda conceitos como escopo, vinculação de tipos e armazenamento.
1. Vinculação
As vinculações são as associações feitas para permitir que o
compilador tenha controle entre a abstração feita no código e a
entidade manipulada pelo hardware, e são caracterizadas por 4
atributos: nome (ligado a escopo), tipo, armazenamento (ligado ao
tempo de vida) e valor. Nem todos os atributos precisam existir –
podemos ter, por exemplo, variáveis sem nome ou sem valor. As
variáveis, que são exemplos de abstrações, e ajudam na legibilidade
do código.
As vinculações podem ser estáticas(não mudam durante o
programa) ou dinâmicas(mudam durante o programa). Elas podem
ocorrer durante:
- tempo de projeto da linguagem: aquele no qual decide
como a linguagem vai ser. Algumas vinculações são definidas, por
exemplo, o operador ‘ := ’ para vinculação de nome .
- tempo de implementação da linguagem: trata-se do
tempo de construção do compilador. Aqui se define por exemplo o
tamanho dos inteiros, mas não há exemplos de vinculação.
- tempo de compilação: no qual se traduz o código fonte
em código de máquina, e geralmente é feito em conjunto com o
tempo de ligação.
- tempo de ligação: o código fonte é incrementado com
códigos da biblioteca, como para o writeln por exemplo.
- tempo de carregamento: se tem a reserva de memória e
definição de endereços, isto é, Dados e instruções são copiados do
arquivo (no disco rígido) para essa porção de memória (fica sobrando
uma parte cujo uso é decidido durante a execução). Quando o
programa começar as ligações já estarão feitas
- tempo de execução: instante em que o programa
começa a ser executado
Vinculação de nomes
Como já tido, nem toda vinculação necessita de um nome,
sendo comum que o programador ou o compilador façam isso. As
variáveis podem ser criadas pelo programador, como os ponteiros
(que não possuem nome), ou pelo próprio compilador, como quando
2. ele cria uma variável temporária sem nome com o valor de um
resultado para depois passa-lo a outra variável. Exemplo x = a + b -
> temos uma variável sem nome com o tipo do resultado de (a+b),
que receberá esse valor para que depois seja passado a x.
Antigamente os nomes eram formados por apenas uma única
letra. Agora as linguagens podem delimitar seu tamanho por 8, 16,
255, etc. Esse tamanho pode ser definido tanto em tempo de projeto
quanto em tempo de compilação.
Quanto aos nomes também é importante lembrar que as
linguagens podem fazer distinção entre maiúsculas e minúsculas. Isso
prejudica na hora de lembrar o nome da variável e também em
questão de legibilidade porque podemos ter nomes considerados
idênticos (como Rosa e rosa em C) e que representam coisas
diferentes. Mas quando a linguagem nos impõe este tipo de notação,
nos proporciona colocarmos mais significado ao nome – somos
levados a isso. Por exemplo, junto com o nome, podemos colocar o
tipo da variável. Um exemplo de notação dessa forma é a notação
camelo.
Alguns nomes são especiais numa linguagem de programação.
Existem as palavras reservadas, cujo significado é o mesmo em
qualquer contexto e as palavras-chave, cujo significado é especial em
algum contexto. As palavras reservadas limitam o programador
principalmente se ele programar na língua de origem linguagem,
como o inglês. As palavras-chave variam seu sentido de acordo com o
significado. Exemplo: “real” em Fortran pode significar tanto um tipo
como uma variável qualquer dependendo de como é usado.
Muitas vezes, palavras especiais são apenas “pré-definidas”,
podendo ser redefinidas pelo programador. Neste caso, a linguagem
provavelmente exige uma biblioteca padrão que é incluída
automaticamente no programa, na qual o nome é definido.(Exemplo:
writeln).
É também comum encontrarmos mesmo nome para entidades
diferentes. Por exemplo, quando temos uma variável local em um
subprograma que representa um coisa, e , uma variável de mesmo
nome em outro subprograma que será usada para outra
coisa(problema que será resolvido por questão de escopo – explicado
adiante).
Assim também como é possível termos nomes diferentes para
uma mesma entidades – conhecido como apelidos (aliases) - como
3. no caso das uniões (exemplo da aula: valor e endereço são chamados
de valor). Nesse exemplo a criação é feita de forma implícita, mas
também podemos ter de forma explícita, como ocorre no caso dos
ponteiros. As aliases tem como desvantagem a legibilidade do código,
porque é necessário que o programador se lembre a todo momento
que elas indicam o mesma célula de memória, e alterar uma, altera a
outra. Antigamente, as aliases eram justificadas porque
economizavam memória - pensando aqui em uniões – pois permitiam
que uma mesma localização fosse usada para diferentes tipos de
dados em diferentes tempos. Mas com a evolução dos computadores,
e aumento de memória, tal justificativa se tornou infundada.
- Escopo (notas de aula são claras)
O escopo de uma variável é o trecho de código no qual seu
nome pode ser usado, ou melhor, existe um nome vinculado à ela.
Para que um nome esteja vinculado à uma única entidade
(variável ou subprograma), numa dada instrução, é comum que as
linguagens de programação determinem que uma vinculação de um
nome à uma entidade, invalida as vinculações anteriores para uma
determinada parte do programa. Neste caso, dizemos que uma
vinculação oculta a outra, num sistema de controle que lembra uma
pilha. Isto leva ao conceito informal de que "o nome que vale é o da
declaração mais interna". O problema com essa afirmação é que o
conceito de "interno" é bastante flexível. Ainda assim, nem sempre
uma vinculação de nome oculta as vinculações anteriores.
Dependendo do conceito de "interno" na linguagem de programação,
a nova vinculação pode simplesmente ser um erro. Os namespaces
são uma forma de manter os vários nomes disponíveis.
Esse sistema nos leva ao conceito de variável local (aquela cujo
nome foi declarado no próprio bloco de código em questão) e variável
externa (aquela cujo nome foi declarado fora do bloco em questão). O
conceito de local/externo é sempre relativo à algo. Uma variável pode
ser local a um bloco ao mesmo tempo que é externa a outro. O
conceito de “variável global” deve ser evitado, visto que é um
conceito absoluto e frequentemente mal usado. Por exemplo:
variáveis cujo escopo se estende por um arquivo inteiro são
frequentemente chamadas de globais, quando na verdade estão fora
de escopo em outros arquivos do mesmo programa.
4. A alteração de uma variável externa é chamada de efeito
colateral. Efeitos colaterais são perigosos e devem ser evitados
sempre que possível.
O conjunto de nomes que podem ser usados numa determinada
instrução é chamado de ambiente de referenciamento da instrução.
É desejável restringir tanto quanto possível o escopo das
variáveis e procedimentos, para que não haja múltiplos nomes
disponíveis à um bloco de código sem necessidade. Esse conceito é
frequentemente chamado de diretriz do escopo mínimo.
Conceito da localidade: o programa com mais variáveis locais é
mais legível que aquele em que as variáveis ficam todas espalhadas,
e por isso e que algumas linguagens possuem recursos próprios para
auxiliar nesse quesito de diminuição do escopo.
Escopo Estático
A vinculação feita no inicio do programa permanece até o
fim. As variáveis declaradas num bloco podem ser usadas em
seus sub-blocos desde que não haja uma nova vinculação
daquele mesmo nome.
Escopo Dinâmico
O escopo muda dependendo de onde se chama o
subprograma. Ou seja, as variáveis disponíveis mudam de
acordo com onde chamamos o subprograma.
Isso permite programas mais fáceis de reusar, pois torna
a passagem de informações flexível para subprogramas,
incentivando portanto o uso de variáveis externas. É um custo
alto, pois o escopo dinâmico prejudica a legibilidade do código,
já que o ambiente de referenciamento muda - o que faz o
escopo estático mais interessante - , torna os programas mais
lentos, pois a resolução dos nomes ocorre em tempo de
execução, e além disso impossibilita verificação de tipos das
variáveis externas.
A implementação do escopo dinâmico é mais fácil que do
escopo estático, e por isso, era comum nas linguagens mais
antigas. Mas a partir do ALGOL, as linguagens aderiram ao
5. escopo estático. Perl a partir de sua vs. 5 permite que o
programador escolha com qual dos 2 deseja trabalhar.
Vinculação de tipo
Pode ser estática – sempre desde tipo – ou dinâmica – decidida
em tempo de execução. Assim como explícita – define o tipo – ou
implícita – não define o tipo. Geralmente explicita é estática e
dinâmica é implícita.
Com a vinculação dinâmica de tipos, é comum que o tipo de
uma variável possa ser alterado a qualquer hora, em tempo de
execução, o que torna inviável a detecção de erros de tipo em tempo
de compilação, fazendo que os erros sejam encontrados em tempo de
execução. Esse problema pode ser amenizado exigindo caracteres
especiais – marcadores – que indicam tipos, como em Perl, no qual
temos $a para escalar(tipo primitivo), @a aglomerado(vetor/lista), #a
vetor associativo(hash), &a função. Isso exige atenção do
programador, pois se os marcadores forem diferentes e os nomes
iguais, isto irá diminuir a capacidade de detecção de erros.
Algumas linguagens tem vinculação estática de tipos, mas
fazem conversão automática (em muitos casos), gerando problemas
parecidos de detecção de erros (ex.: C). Nesse caso, se é esperado b,
e passa tipo a, ele converte a no formato de b, dando mais liberdade
ao programador mas prejudicando a detecção.
(?)A vinculação dinâmica facilita a criação do compilador, que
não precisa conhecer o tipo de uma variável durante o seu uso, ela é
mais comum nas linguagens interpretadas, nas linguagens antigas e
nas linguagens para web. Tais compiladores são eficientes para
celulares. Enquanto os compiladores com vinculação estática são
mais eficientes mais são mais pesados, úteis para programas web.
(?)A vinculação estática proporciona maior velocidade de
execução por dois motivos: (a) não é preciso gerar código para
sistema de tipos, pois o compilador já verificou a validade de todas as
operações e já determinou quais são as operações usadas (ver
sobrecarga de operadores) e (b) as informações sobre os tipos
permitem ao compilador fazer otimizações no código gerado.
Há uma variação entre estática e implícita, no qual mesmo
tendo vinculação estática não é necessário dizer qual é o tipo pois o
6. compilador o irá deduzir. Isto é conhecido com inferência de tipos,
na onde a dedução acontece em tempo de compilação. O que
possibilita checagem do erro de tipo com a facilidade de não exigir
declaração. Mas essa declaração, as vezes pode servir como
documentação para melhorar a legibilidade do programa. Essa
inferência nem sempre funciona, o que faz com que as linguagens
com esse recurso ofereçam a possibilidade de declaração, para que o
programador resolva esses casos especiais (Exe.: Python?).
Obs : Quanto a parte de verificação de erros e compatibilidade
de tipos as notas de aula são claras.
Vinculação de armazenamento
O tempo de vida de uma variável é o tempo durante o qual ela
está vinculada à uma célula de armazenamento (um endereço).
Em função do tempo de vida, uma variável pode ser classificada
como:
• estática: a vinculação existe durante toda a execução do
programa e não muda; tem endereçamento direto (mais rápido); não
existe overhead para alocar e desalocar; permitem criar variáveis
“sensíveis à história”; podem ser um desperdício de espaço se não
forem usadas ao longo de toda a execução; podem conflitar com a
recursividade.
• stack-dinâmica: a vinculação é criada quando a execução
atinge a declaração da variável e deixa de existir quando o bloco de
código termina sua execução; as vinculação de tipo são estáticas;
funciona como uma forma de compartilhar memória entre os vários
subprogramas. - pilha
• heap-dinâmica explícita: são criadas (sem nome) por
operadores e funções de alocação de memória e são acessadas por
meio de referências; tem vinculação estática de tipo;apresentam o
overhead do acesso indireto. Ex.: objetos em Java.
• heap-dinâmica implícita: são vinculadas ao armazenamento e
ao tipo durante uma atribuição; são muito flexíveis; reduzem a
capacidade do compilador de encontrar erros.
O gerenciamento do heap pode ser feito por algoritmos de
garbage collection.
O tempo de vida de uma variável geralmente é tempo de
execução de um bloco. Várias linguagens oferecem formas de
7. vinculação estática ao armazenamento, o que permite a criação de
um tipo de memória em subprogramas.
Nem sempre uma variável vinculada a um armazenamento é
acessível, o que é desejável, seguindo o princípio do escopo mínimo.
Vinculação dinâmica ao armazenamento é uma propriedade que
conflita com instruções tipo “goto”. (?)
- Inicialização
Inicialização é a vinculação ao valor no momento da vinculação
ao armazenamento.Recurso que não é oferecido por toda linguagem.
(ex.: Pascal)