notas-de-aula- Schneider
29 pág.

notas-de-aula- Schneider

Disciplina:Linguagens de Programação I252 materiais732 seguidores
Pré-visualização11 páginas
A inferência de tipos nem sempre
funciona, ou seja, nem sempre é possível determinar o tipo das variáveis pelo seu uso, o que faz
com que as linguagens que usam esse recurso ofereçam formas de declarar tipos, permitindo que o
programador resolva esses casos especiais.

2.2.3.2 Verificação de tipos
Deve-se verificar se os operadores (ou sub-rotinas) e seus operandos fazem sentido juntos. Os tipos
devem ser compatíveis1 ou deve existir alguma regra que permita que seja feita coerção de tipos.

Pode ser estática (em tempo de compilação) ou dinâmica (tempo de execução). A forma dinâmica
exige que se mantenha informação sobre os tipos em tempo de execução.

Existe uma tendência a se preferir que haja verificação de tipos e que ela seja feita em tempo de
compilação (perda de flexibilidade para o programador).

Uma linguagem que permite ao compilador/interpretador detectar qualquer erro de tipos é dita uma
linguagem com tipificação forte. Na prática, sempre existe algum erro de tipo que não pode ser
verificado, porém existem linguagens com tipificação notadamente “mais forte” do que outras.
Essas linguagens são tidas como “mais confiáveis”.

Além da vinculação dinâmica de tipos, a existência de muitas regras para coerção automática de
tipos também reduz a capacidade de verificação de erros de tipo, levando ao que chamamos de
“linguagem com tipificação fraca”. A linguagem C, por exemplo, apesar da vinculação estática de
tipos, é considerada uma linguagem com tipificação fraca.

A forma como uma linguagem lida com a verificação de tipos é frequentemente chamada de o
sistema de tipos (ver 2.8) da linguagem. Ela é de extrema importância pois os erros de tipos são
fortes indícios de outros erros.

2.2.3.3 Compatibilidade de Tipos
Alguns tipos deveriam ser passíveis de serem usados em lugar de outros sem que haja necessidade
de coerção. Isso pode feito de duas formas:

• compatibilidade de nome: duas variáveis tem o mesmo tipo se estão na mesma declaração
ou são declaradas com tipos de mesmo nome.

• compatibilidade de estrutura: duas variáveis tem o mesmo tipo se têm estruturas idênticas.

A primeira forma é muito restritiva e com frequência diferencia tipos que o programador não queria
diferenciar. Exige que tipos sejam declarados globalmente, podendo causar problemas com
bibliotecas.

A segunda forma traz problemas de indireção pois tipos podem ser formados de outros tipos e
podem ser recursivos. Ela torna impossível a diferenciação de tipos com a mesma estrutura (o que
pode ser desejável)

1 Na versão traduzida do livro texto, onde se define o que é a verificação de tipos, lê-se que um tipo que seja
convertido automaticamente é compatível. Isso causa algum conflito com a definição da compatibilidade de tipos
(ver 2.2.3.3) e provavelmente dificulta a fixação desse segundo conceito. Por isso, a definição de verificação de
tipos aparece aqui ligeiramente diferente.

prof. Bruno Schneider 9

Notas de Aula de GCC105 - BCC e BSI - UFLA

Na prática as linguagens usam uma mistura dos dois tipos pois nenhum dos dois produz os
resultados tidos como desejáveis em alguns casos. Na orientação a objetos a compatibilidade de
tipos está fortemente ligada ao conceito de herança.

2.2.4 Vinculação de Armazenamento (tempo de vida)
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.

• 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 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”.

2.2.5 Inicialização
Inicialização é a vinculação ao valor no momento da vinculação ao armazenamento. Variáveis
estáticas em termos de armazenamento precisam ser vinculadas a um valor antes da execução.
Nesse caso, pode não ser possível usar uma expressão para a inicialização.

Nem toda linguagem oferece recursos de inicialização (ex.: Pascal).

2.3 Avaliação de expressões
Uma linguagem deveria determinar precedência de operadores, regras para não coincidência de
tipos e para avaliação em curto-circuito.

2.3.1 Expressões aritméticas
Consistem em operadores, operandos, parênteses e funções. Operadores podem lidar com um ou

prof. Bruno Schneider 10

Notas de Aula de GCC105 - BCC e BSI - UFLA

mais operandos e podem ser sobrecarregados em algumas linguagens.

Quase sempre as linguagens usam parênteses para fazer associação explícita.

2.3.1.1 Ordem de avaliação de operadores
É parcialmente determinada pela precedência dos operadores e parcialmente pela associatividade na
expressão; seguindo os conceitos da matemática.

Um operador pode ter associatividade à direita (ex.: x = z = 1;) ou à esquerda (à esquerda é mais
comum). Um operador pode ser não associativo, exigindo a presença de parênteses. Quando os
operadores são de mesma precedência, a associatividade à esquerda diz que o operador da esquerda

Por serem aproximações das expressões matemáticas, os limites dos tipos podem tornar uma
expressão não-associativa (ex.: soma com números muito grades e muito pequenos).

As linguagens C, C++ e Java oferecem um operador condicional ternário (?:).

Algumas ordem podem ser mais rápidas que outras, pode ser interessante deixar que o compilador
escolha a ordem.

2.3.1.2 Ordem de avaliação dos operandos
A ordem de avaliação dos operandos pode influenciar o resultado (ex.: funções com efeitos
colaterais).

Algumas ordens de avaliação podem ser mais rápidas que outras.

Algumas linguagens deixam a ordem a critério do compilador e existem que o programador se
preocupe em evitar os casos em que a ordem é importante (ex.: Pascal e Ada) enquanto outras
determinam uma ordem (ex.: Java).

2.3.2 Sobrecarga de Operadores
A sobrecarga, no contexto de linguagens de programação, é a capacidade de fazer com que um
único identificador seja usado denotar várias abstrações.

Usar o mesmo operador para representar funções completamente diferentes prejudica a legibilidade.
Erros de digitação ficam mais difíceis de serem detectados.

Algumas linguagens (notadamente as que permitem tipos abstratos) permitem que o programador
sobrecarregue operadores. Quando bem usado, esse recurso pode aumentar a legibilidade.

Se diversas bibliotecas sobrecarregarem os mesmos operadores, podem surgir conflitos.

2.3.3 Conversões de tipo
Podem ser de estreitamento (inseguras) ou alargamento (o novo tipo inclui aproximações para todos
os valores do tipo original). Os nomes “alargamento” e “estreitamento”