• Nenhum resultado encontrado

Aula 13 Implementação da Programação Modular

N/A
N/A
Protected

Academic year: 2021

Share "Aula 13 Implementação da Programação Modular"

Copied!
37
0
0

Texto

(1)

Alessandro Garcia Alexander Chávez LES/DI/PUC-Rio Outubro 2016

Aula 13

Implementação da

Programação Modular

(2)

Especificação

Objetivo dessa aula

– Visão geral sobre compilação de programas modulares

– Estudar em detalhe a composição generalizada de módulos

– Estudar o uso de ponteiros

• Slides adaptados de: Staa, A.v. Notas de Aula em Programação Modular; 2008.

Referência básica:

– Capítulos 6, 8 e Apêndice 7 do livro texto

Referência complementar:

– Schildt H. C – Completo e Total, 3ª Edição. Makron Books, 1997.

(3)

Sumário

Pilha de execução e classes de memória

Ligação, pré-processamento e compilação

Composição de módulos e estrutura de diretórios

Referências, funções de acesso

Função como dado

(4)

Classes de memória real

• Executável

– é onde se armazena o código a ser executado e, possivelmente, algumas constantes numéricas

• Estática encapsulada

– contém todos os espaços de dados globais encapsulados (declarados static), e todas as constantes literais definidas no interior de módulos

• Estática visível

– contém todos os espaços de dados globais externados

• Automática

– contém a pilha de execução do programa

• Dinâmica

contém espaços de dados alocados pelo usuário (malloc, new)

• Persistente

– é onde se armazenam os dados que estarão disponíveis de uma

(5)

Processo de compilação simples (Ligação)

Compilador Compilador Compilador Ligador M3.OBJ M2.OBJ M1.OBJ L2.LIB L1.LIB PG.EXE Editor de programas M3.HPP M3.CPP M2.HPP M2.CPP M1.HPP M1.CPP

Programa

(6)

Ligação

A ligação combina

m >= 1 módulos objeto e módulos

contidos em uma ou mais bibliotecas, produzindo o

programa carregável

(.EXE, .COM)

No módulo objeto todos os endereços gerados

são

deslocamentos

(offsets) relativos a zero dentro da

respectiva classe de memória

O ligador

(linker) justapõe (concatena) os espaços de cada

uma das classes de memória (segmentos: executável e

estática) definidos nos módulos objeto, formando um único

grande espaço para cada classe de memória

Estes dois grandes segmentos

constituem o programa

carregável

(7)

Composição de um módulo objeto

Dados

estáticos

Código

Tab Simb

M1.OBJ

Declarados e definidos, relativos a 0

Tabela de símbolos

- referências a nomes externos

declarados e não definidos

- referências a nomes externos

declarados e definidos (externados

pelo módulo)

(importados)

Relocável, relativo a 0

Tab Reloc

Tabela de relocação

- informa os locais contendo endereços

a serem relocados

(8)

Símbolos definidos no módulo objeto

Cada módulo objeto contém uma tabela de símbolos

agregando os nomes globais externos, particionada em

– símbolos somente declarados

– símbolos declarados e definidos

Os símbolos somente declarados definem uma lista de todos

os locais

no código (ou nos dados) em que o símbolo é

referenciado

Um nome externo somente declarado em um determinado

módulo necessariamente deverá estar declarado e definido

em exatamente um outro módulo do programa sendo

composto

(9)

Processo de compilação simples

Compilador Compilador Compilador Ligador M3.OBJ M2.OBJ M1.OBJ L2.LIB L1.LIB PG.EXE Editor de programas M3.HPP M3.CPP M2.HPP M2.CPP M1.HPP M1.CPP

Programa

(10)

Composição de um executável

Passos

1. concatenar os módulos objeto

– código e dados estáticos

2. relocar os endereços do

módulo de modo que estejam em conformidade com a

origem na concatenação

3. resolver os nomes externos ao

módulo definidos em outro módulo

p.exe

código dados estáticos 0 300 530 0 10 30 m1 m2 m3 m1 m2 m3 int a int b int c a a b c

(11)

Relocação

O ligador ajusta os endereços

dos elementos contidos em

cada segmento de modo que passem a ser deslocamentos

relativos à origem

dos correspondentes segmentos

do

programa

A relocação ocorre com relação aos segmentos

– código

– estático local e externo

A tabela de relocação

contida no módulo objeto informa os

pontos no código e nos dados globais do módulo que

deverão ser ajustados

– no módulo objeto os deslocamentos são relativos a zero

– para relocar basta somar a origem do segmento do módulo definida no segmento composto às referências internas ao módulo registradas na tabela de relocação

(12)

Resolução de nomes externos, sem bibliotecas

• O ligador cria uma tabela de símbolos que conterá os nomes externos. Cada símbolo informa

– o endereço no segmento composto

– a lista dos locais que referenciam o símbolo ainda não definidos

• Ao encontrar um nome externo

– adiciona-o à tabela caso ainda não figure lá

– se for um nome externo declarado e definido

• se a tabela de símbolos do ligador já define o nome, é emitido um erro de duplicação de definição

• caso contrário, percorre a lista dos locais que referenciam o símbolo e atribui o endereço definido

– se for um nome externo somente declarado

• se a tabela de símbolos do ligador já define o nome, atribui esta definição aos locais no módulo que referenciam este símbolo

• caso contrário, o ligador acrescenta a lista do módulo à lista do ligador

• Ao terminar o processamento

– para cada símbolo não definido contido na tabela do ligador, é emitido um erro de símbolo não definido

(13)

Resolução de nomes externos, com bibliotecas

Uma biblioteca estática

(.lib) é formado por

– uma lista de módulos

– uma tabela de símbolos contendo os símbolos externados pelos módulos e a referência para o código do respectivo módulo na lista de módulos

Após compor todos os módulos objeto, para cada símbolo

ainda não definido

– o ligador procura este símbolo, segundo a ordem de fornecimento das bibliotecas

• caso seja encontrado, o módulo correspondente é extraído da biblioteca e acrescentado ao programa sendo montado

– para isso segue o procedimento anterior

• caso não seja encontrado, é emitido um erro de símbolo não definido

(14)

Ligação dinâmica

Bibliotecas dinâmicas

(.dll) são carregadas à medida que

forem acessadas durante o processamento

– ao encontrar um símbolo externo ainda não resolvido

utiliza a .dll, se já carregada, ou então carrega ela

• substitui a referência ao símbolo para a referência à função

– cada biblioteca é compartilhada por todos os programas que a usem

– cada programa estabelece espaços próprios para os dados

Vantagens

– uma biblioteca dinâmica é carregada uma única vez

considerando todos os programas em execução simultânea

– pode-se trocar uma biblioteca sem precisar recompilar ou religar todo o programa

(15)

Ligação dinâmica

Problemas

– precisa-se projetar com muito cuidado as bibliotecas

dinâmicas, visando explicitamente a possibilidade do seu reúso em diversos programas

– as bibliotecas são conhecidas pelo nome, portanto pode ocorrer colisão de nomes

• bibliotecas diferentes com o mesmo nome

– é necessário assegurar que a versão correta da biblioteca seja utilizada com cada um dos programas

• todos os programas utilizam a mesma versão da biblioteca, a menos que se possa armazenar as bibliotecas em locais distintos

(16)

Carga de um programa

Para poderem ser executados programas precisam estar em

memória real

– fragmentos de um programa executável podem estar em

qualquer um dos segmentos: executável, pilha (automático), estático, e dinâmico. A origem estará no segmento executável.

Ao ativar um programa é ativado o carregador

que recebe

como parâmetro o nome do arquivo

contendo o programa a

ser carregado

O carregador

– determina onde serão colocados os segmentos executável e estático e copia os segmentos do arquivo para a memória

– efetua as necessárias relocações de modo a ajustar os endereços contidos nesses segmentos

(17)

Pré-processamento

Um pré-processador

– é um processador de linguagem

– recebe um arquivo contendo texto fonte e diretivas de pré-processamento

(18)

Padrão de programação C

Ao desenvolver programas em C ou C++ siga o

recomendado no apêndice 1 Padrão de Composição de

Módulos C e C++.

Todos os módulos que podem ser incluídos devem conter

um controle de compilação única

– módulo de definição

– tabelas de definição

– tabelas de dados

#if !defined( Nome-arquivo_MOD ) #define Nome-arquivo_MOD

/* Comentário cabeçalho do arquivo */

Corpo do arquivo

#endif

(19)

Consistência das interfaces C/C++

Problema devido a propriedades sintáticas das linguagens

C/C++

– variáveis globais externas devem aparecer exatamente uma vez sem o declarador extern

– todas as outras vezes devem vir precedidas deste declarador

Pode-se conseguir isso com código de pré-processamento

que assegure

– sempre que um cliente compilar o módulo de definição, as declarações de variáveis globais externas estejam precedidas de extern

– sempre que o próprio módulo compilar o módulo de definição, as variáveis globais externas não estejam precedidas de

(20)

Padrão para o módulo de definição

Inicie o código do corpo do módulo de definição com o

seguinte esquema de código:

/* Controle de escopo do arquivo de definição */ #ifdef Nome-arquivo-modulo_OWN

#define Nome-arquivo-modulo_EXT #else

#define Nome-arquivo-modulo_EXT extern #endif

Ao final do código do módulo de definição coloque o código:

#undef Nome-arquivo-modulo_EXT

(21)

Padrão para o módulo de definição

Declare cada variável global externa

não inicializada da

seguinte forma:

Nome-arquivo-modulo_EXT declaração-de-variável ;

Declare cada variável global externa inicializada da seguinte

forma

Nome-arquivo-modulo_EXT declaração-variavel #ifdef Nome-arquivo-modulo_OWN = Inicialização ; #else ; #endif

(22)

Exemplo de módulo de definição

#ifndef EXEMP_MOD #define EXEMP_MOD #ifdef EXEMP_OWN #define EXEMP_EXT #else

#define EXEMP_EXT extern #endif

/***** Tipo de dados exportado pelo módulo *****/ typedef struct

{

int UmInt ; int OutroInt ;

} EX_tpMeuTipo ; /* declaração de tipos não é afetada pelas regras */ /***** Estruturas de dados exportada pelo módulo *****/

EXEMP_EXT int EX_vtNum[ 5 ]

#if defined( EXEMP_OWN ) = { 1 , 2 , 3 , 4 , 5 } ; #else

; #endif

(23)

Padrão para o módulo de implementação

• No módulo de implementação redija o código de inclusão do respectivo módulo de definição na forma a seguir:

#define Nome-Arquivo-Modulo_OWN #include "Nome-Arquivo-Modulo.H" #undef Nome-Arquivo-Modulo_OWN • Exemplo /* Inclusões do compilador / #include <stdio.h>

/* Inclusão do próprio módulo de definição / #define EXEMP_OWN

#include "EXEMP.H" #undef EXEMP_OWN

/* Inclusão de módulos de definição de servidores */ #include "Modulo1.H"

#include "Modulo2.H" #include "Tabela.INC" . . .

(24)

Estrutura de diretórios (pastas)

É fortemente recomendável criar uma estrutura de

diretórios

(pastas) padrão para cada projeto

– pastas com menos arquivos são mais fáceis de manipular

– cada pasta conterá arquivos de alguns poucos tipos

– Exemplo: autotest/instrum

+ Projeto X

+ Batches contém arquivos .bat

+ Composicao contém arquivos .make, .comp, ... + ModulosFonte contém arquivos .c, .h, ...

+ ModulosObjeto contém arquivos .obj, .build, ... + Produto contém arquivos .exe, .log, ...

+ Tabelas contém arquivos .tabstr, .incstr, ... + Teste contém arquivos .script, .count, ... + ... (por exemplo os de apoio ao ambiente)

(25)

O que são referências?

Uma referência

é formada por um conjunto de parâmetros

a

serem fornecidas a uma função de acesso

para acessar um

determinado espaço

– exemplo: elemento de um vetor em C

< origemVetor , dimElemento , inxElemento > – exemplo: disco físico

< unidade , cilindro , trilha , setor > – exemplo: arquivo

< pArq , inxByte , idOrigem >

Cada classe de referência

possui uma função de acesso

associada a ela

exemplo vetor: tpX vtX[ dimVtX ] ; ... vtX[ i ] ...

– exemplo disco físico: um serviço do sistema operacional

(26)

Dereferenciação

Dereferenciar

é a operação de converter uma referência em

um endereço real através de sua função de acesso,

exemplos:

(obs.: não são fragmentos de código em C)

A[j] :: &A + j * sizeof( tpA ) – tudo medido em bytes*pX :: [ pX ] ou por extenso: conteúdo de pX

seja: pElemTabSimb * ObterElemTabSimb(

char * pszSimbolo)

*( ObterElemTabSimb( "um_simbolo" ))

é o espaço de dados associado ao símbolo "um_simbolo“

• :: operador “é definido por”

operador de dereferenciação de ponteiro

(27)

Dereferenciação

A dereferenciação pode ser realizada até chegar ao valor

expA = expB

RHS

right hand side LHS

left hand side

busca-se o valor referenciado pelo endereço RHS

i.e. dereferencia-se o endereço RHS atribui-se o valor RHS

(28)

Dereferenciação composta

Assumindo que os espaços de dados referenciados existam:

typedef struct tgElemLista

{

char szSimbolo[ DIM_SIMBOLO ] ; unsigned IdSimbolo ;

struct tgElemLista * pProx; } tpElemLista ;

tpElemLista * Tabela[ DIM_TABELA ] ;

( Tabela[ ObterHash( szSimboloDado )]->pProx)->szSimbolo

ou

*(*( Tabela[ ObterHash( szSimboloDado )]).pProx ).szSimbolo

(29)

Ponteiros

Ponteiros são casos especiais

de referências

função de acesso é implícita: *pX :: conteúdo de pX

Há quem prefira outra definição de modo que se caracterize

o controle que se pode ter quando se usa referências,

contrastando com a falta de controle quando se usa

ponteiros, exemplo

vtA[ inxA ] ::

if (( inxA < 0 ) || ( inxA >= dimA ))

ErroAcesso( __FILE__ , __ LINE__) ;

pA[ inxA ] :: ??? pois não se sabe o valor de dimApA + inxA :: ??? idem

(30)

Ponteiros – Problemas comuns

Vazamento de Memória:

– Retornar de função sem destruir espaços de memória (não

encadeados em alguma estrutura) referenciados por ponteiros locais

Falha de Segmentação:

– Dereferenciar ponteiro com valor NULL

– Dereferenciar ponteiro não inicializado

– Dereferenciar ponteiro cujo espaço de memória foi destruído

(free/delete)

Apêndice 7: regras

e recomendações

para o uso de

ponteiros

(31)

Ponteiro para função

Funções possuem:

– Um tipo

– Um espaço de dados que é ocupado pelo código da função

– Um nome que referencia esse espaço de dados

Em C é possível criar variáveis e parâmetros do tipo

“Ponteiro para função”:

int soma (int a, int b) { return a + b;

}

int main () {

int (*operacao) (int, int); operacao = soma;

operacao(1, 2); }

(32)

Função como um dado

O tipo de uma função é estabelecido pela sua assinatura

física

– valor retornado

– lista dos tipos de parâmetros

• os nomes dos parâmetros não fazem parte da assinatura

• o nome da função não faz parte da assinatura

exemplo: int ( int , double )

exemplo: tpHistórico * ( tpIdAluno )

Uma função A será do

mesmo tipo

que a função B caso

ambas tenham a mesma assinatura

– note que não se pergunta o que a função faz – semântica

(33)

Dado tipo ponteiro para função

Em C e C++ podem ser definidas variáveis do tipo ponteiro

para função

exemplo: int (* VarF1 )( int , double )

VarF1 é um ponteiro para função do tipo: int ( int , double )

exemplo: tpHist * (* VarF2 )( tpIdAl )

VarF2 é um ponteiro para função do tipo: tpHist * ( tpIdAl )

– Exemplo de atribuição

int Func( int X , double Y ) {

...

} /* Func */

VarF1 = Func ;

Func é uma constante do tipo ponteiro para uma função do tipo:

(34)

Exemplo simples: integração

/* Função de integração numérica utilizando regra dos trapézios */ double Integrar( double LimInf ,

double LimSup , int NumInt ,

double ( * Func )( double X )) {

double Integral = 0.0 ; double Intervalo ;

int i ;

assert( NumInt >= 2 ) ;

Intervalo = ( LimSup - LimInf ) / ( NumInt - 1 ) ; Integral += Func( LimInf ) ;

Integral += Func( LimSup ) ; Integral /= 2.0 ;

for ( i = 1 ; i < NumInt - 1 ; i++ ) {

Integral += Func( Intervalo * i ) ; } /* for */ Integral *= Intervalo ; return Integral ; A B Y X 0 1 2 3 4 5 6 7 8

(35)

Exemplo simples: integração, uso

/* exemplo de uso */

double Quadrado( double X ) {

return X ** 2 ; }

double Cubo( double X ) { return X ** 3 ; } printf( ″\n F = x ** 2 de x=1 a x=10: %lf″ , Integrar( 1.0 , 10.0 , 20 , Quadrado )) ; printf( ″\n F = x ** 3 de x=1 a x=10: %lf″ , Integrar( 1.0 , 10.0 , 20 , Cubo )) ;

(36)

Ponteiro para função

(37)

Referências

Documentos relacionados

Este estudo abordou a identificação e avaliação dos principais atributos e das respectivas dimen- sões da qualidade em serviços de uma Instituição de Ensino Superior (IES),

Consumo de tempo para se criar um ST em que a chaves são as palavras em les_miserables.txt e os valores o número de ocorrências. estrutura

afloramentos no Estado de Minas Gerais: Fazenda Tereza, Município de João Pinheiro; às margens do ribeirão São José e ribeirão Quiricó, Fazenda São José, Município

Apesar de estes episódios de rajadas ocorrerem em todas as noites da estação seca, há uma variabilidade em relação ao horário de ocorrência, que pode ser

Tamás újra felsóhajtott. Olyanok voltak ezek az iskolások, mint a nedves agyagcsomók, amelyeknek formát kell adni a fazekaskorongon. Türelem, türelem és

Para valores menores ou iguais que a unidade de medida caseira: PERCENTUAL DE MEDIDA CASEIRA FRAÇÃO A INDICAR até 30% 1/4 de ... METODOLOGIA A SER EMPREGADA PARA

Os caminhos para comunicação, através dos tdbulos dentinários, canais laterais e acessórios e forame apical, constituem um relacionamento funcional entre dentes e tecidos de

Em relação ao hodierno contexto do lugar, é cada vez mais comum a eleição de um símbolo em particular por parte dos moradores de Ilha de Guaratiba, qual seja o Túnel da Grota