Nomes, Vinculações, Verificação de Tipos e Escopos George Darmiton da Cunha Cavalcanti (gdcc@cin.ufpe.br)
Tópicos Introdução Nomes Variáveis O conceito de vinculação (binding) Verificação de tipos Tipificação forte Compatibilidade de tipos Escopo e tempo de vida Ambientes de referenciamento Inicialização de variáveis
Introdução Linguagens Imperativas são abstrações da arquitetura do computador de von Neumann Memória Processador Variáveis são caracterizadas por atributos Tipo Projetos de tipo escopo, tempo de vida, checagem de tipo, inicialização e compatibilidade de tipos
Nomes Questões de projeto para nomes: Tamanho máximo? Caracteres de conexão são permitidos? Os nomes fazem distinção entre maiúsculas e minúsculas? As palavras especiais são palavras reservadas ou palavras-chave?
Nomes Tamanho Se muito pequeno, não são conotativos Exemplos de linguagens: FORTRAN I: máximo de 6 COBOL: máximo de 30 FORTRAN 90 e ANSI C: máximo de 31 Ada e Java: sem limite C++: sem limite, mas implementadores geralmente impõem limites
Nomes Caracteres de conexão Pascal, Modula-2 e FORTRAN 77 não permitem Outras linguagens permitem
Nomes Distinção entre maiúsculas e minúsculas Desvantagem: Legibilidade (nomes semelhantes são diferentes) Pior em C++ e em Java, pois nomes possuem maiúsculas e minúsculas exemplo: IndexOutOfBoundsException Nomes em C, em C++ e em Java fazem distinção entre maiúsculo e minúsculo Os nomes em outras linguagens não fazem
Nomes Palavras especiais Usadas para tornar os programas mais legíveis ao dar nome a ações que devem ser executadas Usadas para delimitar ou separar as entidades sintáticas Uma palavra-chave é uma palavra que é especial em certos contextos, por exemplo em Fortran Real VarName (Real é um tipo de dado seguido por um nome, assim Real é uma palavra-chave) Real = 3.4 (Real é uma variável) Uma palavra reservada é uma palavra especial que não pode ser usada como nome
Variáveis Uma variável é uma abstração de um célula de memória Variáveis podem ser caracterizadas por uma sêxtupla de atributos: Nome Endereço Valor Tipo Tempo de vida Escopo
Atributos das variáveis Nome nem todas as variáveis possuem nome Endereço o endereço de memória com o qual ela é associada Uma variável pode ter diferentes endereços em diferentes tempos no programa Uma variável pode ter diferentes endereços em diferentes lugares no programa Se dois nomes são usados para acessar a mesma posição de memória, eles são chamados de apelidos (aliases) Apelidos (aliases) são criados através de ponteiros, de variáveis de referência e de uniões em C e em C++ Apelidos são um problema para a legibilidade
Atributos das variáveis Tipo determina a faixa de valores das variáveis e o conjunto de operações definidas para os valores do tipo no caso de ponto flutuante, o tipo também determina a precisão Valor o conteúdo da célula de memória associada à variável Abstração da célula de memória A célula física ou o conjunto de células associadas a variável
O Conceito de Vinculação O valor-l de uma variável é seu endereço O valor-r de uma variável é seu valor Uma vinculação (binding) é uma associação, como, por exemplo, um atributo e uma entidade ou entre uma operação e um símbolo O momento em que uma vinculação se desenvolve é chamado de tempo de vinculação
Possíveis tempos de vinculações Tempo de projeto da linguagem Vinculação de símbolos de operação a operação, ex. * Tempo de implementação da linguagem Vinculação de um tipo (float) a um conjunto de valores possíveis Tempo de compilação Vinculação de uma variável a um tipo de dado em particular Tempo de carregamento Vinculação de uma variável à célula de memória Tempo de execução Vinculação de uma variável local não-estática a uma célula de memória
Vinculação Estática e Dinâmica Uma vinculação é estática se ocorrer antes do tempo de execução e permanecer inalterada ao longo da execução de um programa. Uma vinculação é dinâmica se ocorrer durante a execução ou puder ser modificada no decorrer da execução de um programa
Vinculação de tipos Como o tipo é especificado? Quando a vinculação acontece? Tipos podem ser especificados estaticamente por meio de alguma forma de declaração explícita ou implícita
Declaração de variáveis: explícita e implícita Uma declaração explícita é uma instrução em um programa que lista nomes de variáveis e especifica que elas são de um tipo particular Uma declaração implícita é um meio de associar variáveis a tipos por convenções em vez de instruções FORTRAN, PL/I, BASIC e Perl dispõem de declarações implícitas Vantagem: capacidade de escrita Desvantagem: legibilidade
Vinculação dinâmica de tipos Vinculação dinâmica de tipos (JavaScript e PHP) A variável é vinculada ao tipo quando lhe é atribuído algum valor, por exemplo JavaScript list = [2, 4.33, 6, 8]; list = 17.3; Vantagem: Flexibilidade Desvantagens: Alto custo Difícil detecção de erros
Inferência de tipos Inferência de tipos (ML, Miranda e Haskell) Ao invés de uma instrução de atribuição, tipos são determinados pelo contexto Em ML, fun circumf(r) = 3.14159 * r * r; fun vezes10(x) = 10*x; ML rejeita a função fun quadrado(x) = x * x; Opções fun quadrado(x:int) = x * x; fun quadrado(x) = (x:int) * x; fun quadrado(x) = x * (x:int);
Vinculação de Armazenamento e Tempo de Vida Alocação Marcar\tomar uma célula de memória de um conjunto de memória disponível Desalocação Devolver a célula ao conjunto de memória disponível O tempo de vida de uma variável se inicia quando ela é vinculada a uma célula específica e encerra-se quando ela é desvinculada
Categorias de variáveis baseado no tempo de vida Variáveis Estáticas Variáveis Dinâmicas na Pilha Variáveis Dinâmicas no Monte Explícitas Variáveis Dinâmicas no Monte Implícitas
Variáveis Estáticas Vinculadas a células de memória antes que a execução do programa se inicie. Exemplo: todas as variáveis do FORTRAN 77 e as variáveis static do C Vantagens: eficiência (endereçamento direto) suporta subprogramas sensíveis à história Desvantagem: pouca flexibilidade (não permitem recursão)
Variáveis Dinâmicas na Pilha São aquelas cujas vinculações de armazenamento criam-se a partir da elaboração de suas instruções de declaração, mas cujos tipos são estaticamente vinculados (Elaboração: processo de alocação e de vinculação de armazenamento) Ocorre em tempo de execução Exemplo Variáveis locais em subprogramas C e métodos em Java Vantagem: permite recursão; compartilhamento de espaço de memória Desvantagens: Sobretaxa de alocação e de desalocação em tempo de execução Subprogramas não podem ser sensíveis à história
Variáveis Dinâmicas no Monte Explícitas As variáveis dinâmicas no monte explícitas são células de memória sem nome (abstratas) alocadas e desalocadas por instruções explícitas em tempo de execução, especificadas pelo programador. Essas variáveis alocadas no monte e desalocadas para o monte só podem ser referenciadas por meio de variáveis de ponteiro ou de referência. O monte é um conjunto de células de armazenamento altamente desorganizado, devido à imprevisibilidade de seu uso.
Variáveis Dinâmicas no Monte Explícitas Exemplo: Objetos dinâmicos em C++ (via new e delete) Todos os objetos em Java Vantagem: Convenientes para estruturas dinâmicas: listas encadeadas e árvores Desvantagens: Dificuldade de usar ponteiros e referência corretamente Custo das referências para as alocações e para as desalocações
Variáveis Dinâmicas no Monte Implícitas Alocação e desalocação causadas por instruções de atribuição Todas as variáveis em APL Todas as strings e vetores em Perl e em JavaScript Vantagem Flexibilidade Desvantagens Ineficiente, pois todos os atributos são dinâmicos Perda de grande parte da capacidade de detectar erros
Verificação de Tipos Verificação de tipos é a atividade de assegurar que os operandos de um operador sejam de tipos compatíveis. Um tipo compatível é aquele válido para o operador ou com permissão, nas regras da linguagem, para ser convertido pelo compilador para um tipo válido Essa conversão automática é chamada de coerção Um erro de tipo é a aplicação de um operador a um operando de tipo impróprio
Tipificação Forte Uma linguagem de programação é fortemente tipificada se erros de tipos são sempre detectados Vantagens Permite a detecção de todos os usos equivocados de variáveis que resultem em erros de tipo Exemplos de linguagens Pascal não é: variant records (registros variante) C e C++ não são: permitem funções cujos parâmetros não são verificados quanto ao tipo Ada é quase fortemente tipificada (Java é similar)
Tipificação Forte Regras de Coerção Podem enfraquecer a tipificação forte (C++ versus Ada) Ada possui poucas regras de coerção Embora Java possua metade das regras de coerção do C++, logo sua detecção de erros é melhor do que a do C++, sua tipificação forte é bastante inferior a da Ada
Compatibilidade de Tipos Existem dois métodos diferentes de compatibilidade de tipos Compatibilidade de Nome Compatibilidade de Estrutura
Compatibilidade de Tipo de Nome Significa que duas variáveis possuem tipos compatíveis se elas estiverem na mesma declaração ou em declaração que usam o mesmo nome de tipo A compatibilidade de tipo de nome é fácil de implementar mais muito restritiva Uma variável subfaixa dos números dos inteiros não seria compatível com uma variável do tipo inteiro type indextype = 1..100; {um tipo subfaixa} var cont: integer; indice: indextype As variáveis cont e indice não são compatíveis. cont não seria atribuída a indice e vice-versa.
Compatibilidade de Tipo de Estrutura Significa que duas variáveis têm tipos compatíveis se os seus tipos tiverem estruturas idênticas Mais flexível, porém mais difícil de implementar
Compatibilidade de Tipos Considere os problemas de tipo entre duas estruturas: Dois registros são compatíveis no tipo se eles possuem a mesma estrutura mas usam diferentes nomes para os campos? Dois vetores são compatíveis se eles são os mesmos exceto pela faixa de indexação? Exemplo: [1..10] e [0..9] Usando compatibilidade de tipo de estrutura não é possível diferenciar entre tipos que tenham a mesma estrutura Exemplo: Diferentes unidade de velocidade, ambas ponto flutuante Celsius e Fahrenheit, ambos ponto flutuante
Escopo O escopo de uma variável é a faixa de instruções na qual a variável é visível Uma variável é visível em uma instrução se puder ser referenciada nessa instrução As variáveis não-locais de uma unidade ou de um bloco de programa são as visíveis dentro deste, mas não são declaradas lá
Escopo Estático Método para vincular nomes a variáveis não-locais Para conectar uma referência a uma variável, o compilador precisa encontrar a declaração Processo de busca: Caso a declaração não for encontrada localmente, passa-se a buscar em escopos mais amplos O pai-estático (static parent) é o subprograma no qual encontra-se a declaração Os ancestrais estáticos são todos os subprogramas até se chegar a declaração
Escopo Estático procedure big; var x: integer; procedure sub1; begin { sub1 }...x... end; { sub1 } A variável x em sub1 é declarada no procedimento big procedure sub2; var x: integer; begin { sub2 }... end; begin { big }... end; { big }
Escopo Estático Variáveis podem ser escondidas de uma unidade quando a mesma possui uma variável com o mesmo nome program main; var x: integer; procedure sub1; var x: integer; begin { sub1 }...x... end; { sub1 } begin { main }... end; { main } C++ e Ada permitem acesso a essas variáveis escondidas Em Ada: unit.name Em C++: class_name::name
Blocos Um método para criar novos escopos estáticos no meio do código executável introduzido no ALGOL 60 Permite que uma seção de código tenha suas próprias variáveis locais cujo escopo é minimizado Essas variáveis são tipicamente dinâmicas na pilha Alocada quando a seção é iniciada e desalocada quando ela é finalizada Exemplo em Ada... declare TEMP: integer; begin TEMP := First First := Second Second := TEMP end...
Avaliação do Escopo Estático Assuma que MAIN chama A e B A chama C e D B chama A e E
Avaliação do Escopo Estático Um grafo com chamadas potenciais a procedimento, no sistema. Um grafo com as chamadas desejáveis do programa exemplo.
Avaliação do Escopo Estático Suponha que a especificação é alterada e E deve acessar algum variável em D Soluções: Colocar E em D (porém, E não poderá acessar o escopo de B) Mover as variáveis de D, que são necessárias em E, para MAIN (isso permite o acesso por todos os os procedimentos De maneira geral: escopo estático encoraja o uso de variáveis globais
Escopo Dinâmico Baseia-se na seqüência de chamada de subprogramas, não em suas relações espaciais (temporal versus espacial) Desta forma o escopo pode ser determinado apenas em tempo de execução Quando a procura por declarações locais falha, as declarações do pai-dinâmico (procedimento de chamada) são pesquisadas, e assim sucessivamente Caso nenhuma declaração for encontrada em qualquer ancestral dinâmico, haverá um erro em tempo de execução
Escopo dinâmico: exemplo procedure big; var x: integer; procedure sub1; begin { sub1 }...x... end; { sub1 } procedure sub2; var x: integer; begin { sub2 }... end; BIG chama SUB2 SUB2 chama SUB1 SUB1 usa x Nesse caso, SUB1 usa o x declarado em SUB2 begin { big }... end; { big }
Avaliação do Escopo Dinâmico Vantagem Conveniência Desvantagem Pouca legibilidade Linguagens que usam escopo dinâmico APL, SNOBOL4 e nas primeiras versões do LISP Perl também permite que as variáveis sejam declaradas com escopo dinâmico
Escopo e Tempo de Vida Escopo e Tempo de Vida, algumas vezes, parecem estar relacionados, mas são conceitos diferentes void printheader(){... } /* fim de printheader */ void compute() { int sum;... printheader(); } /* fim de compute */ O escopo da variável sum é completamente contido pela função compute Porém, o tempo de vida de sum estende-se ao longo do tempo durante o qual printheader é executado
Ambientes de Referenciamento O ambiente de referenciamento de uma instrução é o conjunto de todos os nome visíveis na instrução Em uma linguagem com escopo O ambiente de referenciamento é formado pelas variáveis locais mais todas as variáveis de seus escopos ancestrais visíveis Um subprograma é ativo se sua execução tiver começado, mas ainda não tiver terminado Em um linguagem com escopo dinâmico O ambiente de referenciamento é formado pelas variáveis locais, mais as variáveis de todos os subprogramas ativos
Constantes Nomeadas Uma constante nomeada é uma variável vinculada a um valor somente no momento em que ela é vinculada a um armazenamento Seu valor não pode ser mudado por uma instrução de atribuição Exemplo uso da constante pi ao invés do valor 3,14159 Vantagem Legibilidade Confiabilidade
Inicialização de Variáveis Inicializações são geralmente feitas através de instruções de declaração Exemplo: em Java int sum = 0; Nem Pascal, nem Modula-2 oferecem uma maneira de inicializar variáveis, exceto durante a execução através de instruções de atribuição
Resumo Nomes Tamanho; caracteres de conexão; distinção entre maiúsculas e minúsculas; palavras especiais Variáveis nome, endereço, valor, tipo, tempo de vida, escopo Vinculação é a associação de atributos a entidades do programa Variáveis escalares são categorizadas como static stack dynamic explicit heap dynamic implicit heap dynamic Tipificação forte é conceito de exigir que todos os erros de tipo sejam detectado