Nomes, Vinculações, Verificação de Tipos e Escopos George Darmiton da Cunha Cavalcanti ([email protected]) 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 } procedure sub2; var x: integer; begin { sub2 } ... end; begin { big } ... end; { big } A variável x em sub1 é declarada no procedimento 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 ... declare TEMP: integer; Exemplo em Ada 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 } BIG chama SUB2 SUB2 chama SUB1 SUB1 usa x procedure sub2; var x: integer; begin { sub2 } ... end; 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