CONCEITOS BÁSICOS SOBRE PROGRAMAÇÃO Programação Orientada a Objetos (POO) Profa. Vania V. Estrela, Profa. Albany E. Herrmann 1. Programação Programação requer o uso de uma linguagem, cuja sintaxe geralmente é mais restrita do que a de uma linguagem natural como o Português. Apesar disso, uma linguagem de programação é poderosa o suficiente para resolver qualquer problema, que envolva computação e flexível o bastante para permitir resolver um mesmo problema por programas diferentes. O leitor interessado em aprender mais sobre os tópicos abordados nesta apostila deve consultar as referências bibliográficas [1-4]. O sistema operacional (SO) cria um ambiente onde usuários podem confeccionar programas e executá-los sem se preocupar com detalhes de hardware. Podemos imaginar o SO como um intermediário entre o programador e o hardware, o qual oferece bibliotecas com funções de alto nível para ler e escrever dados nestes dispositivos. Exemplos de SOs são o Linux, o Windows, e o MS-DOS. Na hipótese de existir mais de um programa para solucionar um problema, uns podem ser mais eficientes do que outros. Esta eficiência está relacionada principalmente ao tempo de CPU e à quantidade de memória necessários a execução do programa. Logo, espaço de memória e tempo de execução precisam ser conciliados em computação. A correção dos códigos é muito importante e o ideal é que um programa funcione corretamente para qualquer dado de entrada válido. A correção e a eficiência são objetivos fundamentais de qualquer programa, porém é impossível testar um com todas as entradas possíveis. Provar matematicamente a correção de um programa é difícil. Outro fator complicador é que não são conhecidos algoritmos eficientes para resolver vários problemas importantes. O primeiro passo, em se tratando de programação, inicia-se com a confecção de um programa ou código fonte (PF) e termina com a geração de um programa executável (PE) ou código executável.
Normalmente, o PF é formado por uma sequência de instruções, cujo formato é especificado por alguma linguagem de programação e deve ser criado num editor de textos (com ou sem formatação, dependendo do ambiente de desenvolvimento). Depois, o PF é submetido a um compilador, para ser analisado e convertido num códigoobjeto (ou programa-objeto; PO). O PO é a versão em linguagem de máquina (números binários) do PF. Caso o programa contenha chamadas a funções das libraries ou bibliotecas (função cosseno, por exemplo), o linker ou ligador combina o PO com a(s) respectiva(s) biblioteca(s) e gera um PE. 1.1 Geração do Código Executável Para cada linguagem de programação, a máquina pode ser vista como um sistema dedicado à execução de programas. Diferentes linguagens fazem com que aparentemente tenhamos diferentes máquinas virtuais, cuja implementação não interessa ao usuário, na maioria dos casos. Em princípio, pelo menos, para quem executa um programa escrito em FORTRAN não faz diferença se o hardware da máquina executa o código FORTRAN diretamente, ou se uma tradução é feita para o código que é finalmente executado (a linguagem da máquina), possivelmente em vários passos. A máquina virtual de uma linguagem de programação pode sempre ser vista como a implementação de uma interface entre o usuário e a máquina. Essa implementação pode ser, até certo ponto, estendida ou modificada pelo usuário. Na prática, a implementação de uma máquina virtual nunca é totalmente transparente (invisível): alguns aspectos da forma pela qual foi feita podem ficar aparentes para seus usuários, através do tempo gasto para a execução, ou de mensagens provenientes de etapas intermediárias da implementação, aspectos esses que deveriam ser invisíveis.
Montador: Assembly Compilador: Ling. de Programação Figura 1 Etapas para a geração de um código executável. Para implementar uma linguagem de programação numa máquina, esta deve dispor de circuitos que, para cada instrução da linguagem, se encarregam das ações correspondentes à mesma. Um PE pode ser gerado, a grosso modo, por um dos três tipos de programas auxiliares. Principais Termos: Código-Fonte: Criado via editor de textos, contém os comandos da linguagem de programação (C++, Pascal...) e serve como entrada para o compilador. Código-Objeto (PO) ou Arquivo Objeto: Criado pela conversão do PF em linguagem de máquina. É gerado pelo montador, compilador ou interpretador, quando não há erros no PF (extensões do código-objeto mais comuns:.obj,.o) Ligador ou Linkeditor: Combina o PO com as bibliotecas necessárias a geração do PE (por exemplo, extensões das bibliotecas.dll ou.lib).
Programa Executável: Código passível de execução pelo SO (extensão do PE:.EXE) Tempo de Compilação: Intervalo de tempo decorrido para conversão do PF em PO. Tempo de Execução: Começa após a ativação do PE. Para cada arquivo de PF, é gerado um arquivo com um PO, o qual é ligado a outros, através de um ligador, resultando num PE ou biblioteca. Um PO não só contém código objeto, mas também informações sobre alocação de memória, os símbolos do programa e também informações sobre depuração ou debug. Há vários formatos de PO. Originalmente, cada computador possuía formato próprio, mas com o advento de SOs portáveis (como por exemplo o UNIX), outros formatos, foram padronizados e utilizados em diferentes sistemas. É comum o mesmo formato de PO servir tanto para entrada, quanto para saída do ligador, sendo portanto utilizado no PE ou biblioteca gerada. O formato dos POs é parte importante no projeto de um SO: uma vez que afeta o tempo que os programas levam para serem acionados e, assim, afeta o tempo de desenvolvimento dos sistemas. Além disso, se o formato também é utilizado para os PEs, ele influi no tempo que o programa leva para começar a executar (responsividade do programa sob o ponto de vista do usuário final). A maioria dos formatos é estruturada como blocos do mesmo tipo, podendo ser mapeados conforme necessário pelo sistema de gerenciamento de memória virtual do SO, sem necessidade de mais processamento antes de serem utilizados. O tipo mais simples de código objeto executável é o.com do DOS, que contém apenas bytes, sempre carregados no mesmo endereço da memória. Outros formatos, cuja especificação abrange várias páginas, contém várias estruturas e sub-estruturas. Os programas cuja extensão é.exe também contem código em linguagem de máquina executável. 1.2 Geração de Código Objeto Os tradutores dividem-se em tradutores de linguagem fonte e a linguagem simbólica para uma linguagem de máquina (numérica). O tradutor é chamado de montador,
quando processa uma linguagem de baixo nível ou Assembly ou linguagem de montagem. Já o compilador traduz de uma linguagem de nível médio ou de alto nível para o código executável. Outra opção é usar um interpretador. 1.2.1 Montador Traduz literalmente os mnemônicos do Assembly, implementado-os em linguagem de montagem para todas as instruções da máquina e modos de endereçamento, além de traduzir comandos em linguagem de montagem válidos (pseudo-instruções ou diretivas de execução) para seus equivalentes em linguagem de máquina. Por exemplo, programa fonte sob a forma de menemônicos (linguagem de máquina simplificada), cuja extensão é.asm pode ser traduzido para um programa em binário pronto para a execução na RAM (linguagem de máquina). A montagem é um processo simples, porém tedioso e propenso a erros quando feito manualmente. Montadores comerciais geralmente possuem as seguintes características: O programador pode especificar explicitamente as posições de dados e programas durante a execução. Valores de dados na memória podem ser inicializados antes da execução do programa. Rótulos simbólicos (labels) podem ser usados para representar endereços e constantes. Variáveis podem ser definidas num programa em linguagem de montagem e usadas posterioriormente em outros programas. Subrotinas (semelhantes às funções em linguagem de alto nível) podem ser definidas uma vez e serem chamadas onde e quantas vezes for necessário. Permitem a definição de macros, ou seja, trechos em código de máquina, que podem ser definidas uma vez e, depois, instanciadas quando necessário, implicando na substituição da chamada a macro por seu código equivalente. Portanto, trata-se de uma forma resumida de escrever o programa, mas que não implica num código de máquina menor.
A maioria dos montadores leem os programas fonte duas vezes, e são chamados de montadores de dois passos (etapas de montagem). O primeiro passo determina o endereço de todos os itens de dados e instruções de máquina, para selecionar quais palavras binárias (instruções conforme serão escritas na RAM) serão geradas para cada linha de código fonte. Os endereços dos itens de dados e instruções são determinados por meio do uso de um contador de programa para a montagem, chamado contador de instrução (program counter ou instruction counter). O contador de instrução gerencia o endereço da instrução executada e dos itens de dados a ela associados durante a montagem, que geralmente é inicializada com 0 (zero). No início do primeiro passo, é incrementado de acordo com o tamanho de cada instrução. Durante este passo, o montador também efetua quaisquer operações aritméticas em tempo de montagem, e insere as definições de todos os rótulos de funções e variáveis e as constantes, em uma tabela chamada Tabela de Símbolos (TS). Código-fonte (Source code) Compilador Código Objeto Código de Máquina (Assembly) Figura 2. Sequência típica para geração de um programa em linguagem de máquina. Figura 3 Etapas envolvidas na geração de um código executável (código de máquina pronto para carregar).
PROGRAMA FONTE Dados de Entrada COMPILADOR PROGRAMA OBJETO LIGADOR (LINKER)/ CARREGADOR (LOADER) SISTEMA OPERACIONAL (SO) PROGRAMA EM LINGUAGEM DE MÁQUINA (ASSEMBLY) Resultados (Dados) de Saída Armazenados PROGRAMA EXECUTÁVEL (.exe) Resultados (Tela) Figura 4 Etapas do processo de compilação/montagem. A razão principal para exigir uma segunda passagem do montador é permitir que símbolos possam ser usados no programa antes de serem definidos. Após a primeira passagem, o montador terá identificado todos os símbolos e os colocado na TS. Na segunda passagem, gerará código de máquina, inserindo os identificadores dos símbolos os quais são conhecidos agora. 1.2.2 Interpretador Programa que lê e executa as tarefas do programa fonte linha a linha, ou seja, cada linha implica em executar uma ação, sem gerar código-objeto explicitamente. Em geral é lento devido à execução passo a passo. A grande vantagem de um interpretador é o maior controle sobre o programa em execução, tornando possível detectar situações de erro invisíveis ou imprevisíveis. Esse tipo de tradução aparece em linguagens de
comandos (scripts como os do MATLAB); ou em linguagens de programação, que permitem construções de tamanho não determinado antecipadamente. 1.2.3 Compilador É um programa que transforma um texto de programa escrito (programa fonte) em uma linguagem de programação de alto/médio níveis (linguagem fonte), num programa equivalente (programa objeto) numa linguagem de baixo nível (geralmente chamada de linguagem objeto). Em geral, o código objeto ainda não está em condições de ser carregado na memória do computador, pois faz-se necessário o mapeamento do programa objeto na RAM; inserção de bibliotecas de software; e resolução de endereços e labels. Adicionalmente, é possível adaptar o código gerado por um compilador para cada comando de acordo com seu contexto, aproveitando diferenças, que não podem ser tratadas com a mesma facilidade pelo interpretador. Um Compilador C++ é um programa, cuja finalidade é traduzir ou converter um programa fonte escrito numa linguagem linguagem fonte para um programa objeto escrito na linguagem objeto; o programa objeto é o resultado da tradução. A compilação também pode gerar PO, que não será executado diretamente, mas o qual será usado em conjunto com outros. Estes programas são chamados de bibliotecas, e fornecem funcionalidades básicas e avançadas. A junção do PO dos programas com o PO das bibliotecas é realizada pelo ligador. 1.3 Ligador (Linker) O ligador coleta procedimentos traduzidos separadamente, ligando-os uns aos outros para que eles possam executarum PE. Se o compilador ou o montador lesse um conjunto de PFs e produzisse diretamente um PE em linguagem de máquina pronto para ser executado, bastaria que um único comando fonte fosse alterado, para que todos os PFs tivessem que ser novamente traduzidos.
Usando módulos objeto separados, o único procedimento a ser traduzido seria aquele modificado. Havendo a necessidade de realizar apenas a etapa de ligação dos módulos separados novamente, sendo esta tarefa mais rápida que a tradução. 1.4 Carregadores São programas que colocam módulos de carregamento na memória principal. Conceitualmente, a tarefa do carregador não é difícil, pois consiste em carregar os vários segmentos de memória com seus valores corretos e inicializar certos registradores, tais como o apontador para pilha do sistema, responsável pelo escopo das rotinas em execução e o contador de instruções contido no processador, com seus valores iniciais, indicando assim onde o programa deve ser iniciado. Em SOs modernos, vários programas estão residentes na memória a todo instante, e não há como o montador ou o ligador saber em quais endereços os módulos de um programa irão residir. O carregador deve relocar estes módulos durante o carregamento adicionando um deslocamento a todos os endereços, permitindo desta forma acessar cada módulo individualmente na memória. Esse tipo de carregamento é chamado de carregamento com relocação. Simplificadamente, o carregador modifica endereços relocáveis, dentro de um único módulo de carregamento, para que vários programas passem a residir na memória ao mesmo tempo. 1.5 Ambientes de Desenvolvimento (IDEs) Em muitos casos, os compiladores ou montadores estão acoplados a ambientes de desenvolvimento (IDEs), que incluem outras ferramentas, como por exemplo: Depuradores ou Debuggers visando teste e detecção de erros. Profilers para medir o tempo gasto pelo programa. Software para construção de diagramas. Aplicativo para a edição de programas fontes.
1.6 Sistema Operacional (SO) No passado, os usuários dos sistemas interagiam muito mais com o hardware. Atualmente, muitas da funções que eram executadas pelos próprios usuários, são agora realizadas pelo sistema operacional. Reúnem programas, quase sempre transparentes ao usuário, que desempenham rotinas necessárias ao funcionamento do computador: gerenciamento da memória; administração dos dados; acionamento dos dispositivos; e execução de programas utilitários. 1.6.1 DOS (Disk Operating System) O DOS (Disk Operating System) da Microsoft é um dos mais utilizados no mundo. O MS-DOS trabalha com comandos que informam ao sistema as tarefas a serem realizadas. Tarefas básicas do DOS: gerenciar arquivos e diretórios; manutenção de discos (rígidos e flexíveis); configurar o hardware; otimizar o uso da memória; melhorar o desempenho dos programas; e personalizar o MS-DOS. O MS-DOS indica que está preparado para receber comandos através do aviso de comando ou "prompt". Ex.: a> Os comandos digitados a partir de um prompt como o acima especificam as tarefas a serem realizadas pelo MS-DOS.
Referências [1] http://producao.virtual.ufpb.br/books/camyle/introducao-a-computacaolivro/livro/livro.chunked/ch05s03.html [2] Stroustrup, Bjarne. (2002). Linguagem de Programação C++. Lisboa. 3. ed. Porto Alegre: Bookman. [3] Patterson, D.A., Hennessy, J.L. (2008). Computer Organization and Design, Fourth Edition: The Hardware/Software Interface (The Morgan Kaufmann Series in Computer Architecture and Design). [4] Stallings, W. (2009). Computer Organization and Architecture: Designing for Performance, Prentice Hall, 8 edition, ISBN-13: 978-0136073734