Tópicos avançados de Física Computacional
Transcrição
Tópicos avançados de Física Computacional
Tópicos avançados de programação No âmbito da Cadeira de Fı́sica Computacional – 2006 Gestão de Programas Extensos Bibliotecas A Bilioteca gsl Interface C -FORTRAN : SLATEC e LAPACK Automatização com o GNU make Outros tópicos Vı́tor M. Pereira Departamento de Fı́sica da FCUP e Centro de Fı́sica do Porto ii iv Conteúdo Conteúdo vi Listagens vii Nota Prévia ix I. Gestão de Programas Extensos 1 por Vı́tor M. Pereira 1. O modelo de compilação do C 1.1. Ideias Básicas . . . . . . . . . . . . . 1.2. O modelo de compilação em C . . . 1.3. Os passos da compilação em detalhe 1.3.1. O pré-processador . . . . . . 1.3.2. O compilador . . . . . . . . . 1.3.3. O assembler . . . . . . . . . . 1.3.4. O linker . . . . . . . . . . . . 1.4. Linkagem estática e dinâmica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 4 6 7 8 8 10 2. Compilação de múltiplas files 2.1. Distribuição de código . . . . . . . . . . . . . . . . . 2.2. Criando ficheiros de objectos a partir das fontes . . . 2.3. Criando executáveis a partir de ficheiros de objectos 2.4. Recompilar e re-linkar . . . . . . . . . . . . . . . . . 2.5. Partilha de variáveis . . . . . . . . . . . . . . . . . . 2.5.1. Âmbito (Scope) . . . . . . . . . . . . . . . . . 2.5.2. Classes de armazenamento (storage classes) . 2.6. Organização dos dados em cada ficheiro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 16 16 16 18 18 19 21 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Criação de Bibliotecas 23 4. Criação e gestão de uma Makefile 27 II. Interface C -FORTRAN : As Bibliotecas SLATEC e LAPACK 31 por J. Lopes dos Santos 4.1. Rotinas em Fortran a partir de C . . . . . 4.1.1. Convenção de nomes . . . . . . . . 4.1.2. Chamada por referência (pointers) 4.1.3. Compilação . . . . . . . . . . . . . 4.2. Exemplos de utilização de rotinas SLATEC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 33 33 35 35 v Conteúdo 4.2.1. Apontadores para funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3. Documentação da Biblioteca SLATEC . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4. Endereços úteis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 39 40 III. A Biblioteca gsl 41 por Eduardo Castro e Vitor M. Pereira (em Construção) 5. Tópicos preliminares 5.1. O que é a gsl . . . . . . . . . . . . . . . . 5.2. Utilização básica . . . . . . . . . . . . . . . 5.3. Geradores de Números Aleatórios . . . . . . 5.4. Funções como argumentos . . . . . . . . . . 5.5. Funções de número de argumentos variável 5.6. A função gsl function . . . . . . . . . . . . . . . . . 43 43 43 45 47 48 50 6. Exemplos 6.1. Minimização de Funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1. Funcional de energia livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.2. Inicialização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 53 53 54 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IV. Outros Tópicos por Vı́tor M. Pereira 57 7. Argumentos ao main 7.1. argv e argc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2. Variáveis de ambiente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 59 61 A. Lista de Comandos 65 Anexos 65 Bibliografia 67 vi Listagens 1.1. 1.2. 1.3. 1.4. 2.1. 2.2. 2.3. 2.4. 2.5. 3.1. 3.2. 3.3. 4.1. 4.2. 4.3. 4.4. 4.5. 5.1. 5.2. 6.1. 6.2. 7.1. 7.2. 7.3. Código fonte do programa Hello World (hello.c). . . . . . . . . . . . . . . . . . . Código fonte pré-processado I (hello.i) . . . . . . . . . . . . . . . . . . . . . . . . Código fonte pré-processado II (hello.i) . . . . . . . . . . . . . . . . . . . . . . . Código fonte em assembly (hello.s) . . . . . . . . . . . . . . . . . . . . . . . . . . Código fonte do segmento main.c. . . . . . . . . . . . . . . . . . . . . . . . . . . . Código fonte do segmento hello fn.c. . . . . . . . . . . . . . . . . . . . . . . . . . Código fonte do header hello.h. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Uma alteração local a main.c. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exemplos de âmbito de variáveis (scope.c). . . . . . . . . . . . . . . . . . . . . . . Função bye() (bye fn.c). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Header hello2.h. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Driver para biblioteca libhello.a. . . . . . . . . . . . . . . . . . . . . . . . . . . . Makefile para o projecto Hello World (Makefile). . . . . . . . . . . . . . . . . . . Exemplo de código FORTRAN (twice.f). . . . . . . . . . . . . . . . . . . . . . . . . . Exemplo de um programa em C que chama uma rotina de FORTRAN (calltwice.c). Prólogo da rotina DGAUS8 do SLATEC (dgaus8.f). . . . . . . . . . . . . . . . . . . . Exemplo de programa em C que chama a rotina DGAUS8 do SLATEC (calldgaus8.c). Utilização básica da gsl (gsl-bessel.c) . . . . . . . . . . . . . . . . . . . . . . . Números aleatórios com a gsl (gsl-random.c) . . . . . . . . . . . . . . . . . . . . Definição da entropia e energia livre para uma dada magnetização (minimization.c). Programa principal (minimization.c). . . . . . . . . . . . . . . . . . . . . . . . . Argumentos ao main() (show args.c). . . . . . . . . . . . . . . . . . . . . . . . . . Problema 4 da folha “Exercı́cios de linguagem C ” (show args.c). . . . . . . . . . . Exemplo com variáveis de ambiente (environ.c). . . . . . . . . . . . . . . . . . . . 5 6 6 7 13 14 14 17 19 23 23 24 27 34 34 36 38 43 45 53 54 60 61 62 vii Listagens viii Nota Prévia Estas notas destinam-se primariamente aos alunos do curso de Fı́sica Computacional, leccionado no departamento de Fı́sica da FCUP, e, por extensão, a todos quantos se interessarem pelos tópicos aqui aflorados. Reúne-se aqui material novo e textos já existentes no contexto desta cadeira em anos anteriores, preparados por alguns dos seus docentes. Os exemplos apresentados estão orientados para o compilador gcc do projecto GNU (www.gnu.org), incluindo algumas funcionalidades que, não sendo embora parte do standard do C , consituem extensões a esse standard por parte do gcc 1 . Ao longo do texto existem frequentes concessões no que respeita à utilização de termos de lı́ngua inglesa sem sobreaviso e destaque. A opção pela utilização dos termos directamente na lı́ngua inglesa em vez da sua tradução é evidente: a quase totalidade da literatura existente nos tópicos abordados nestas notas é redigida nessa lı́ngua. Seria talvez mesmo algo improdutivo estar a introduzir traduções para termos como assembler ou linker, os quais o leitor depois não encontraria em mais lado algum, causando confusões desnecessárias. Relativamente às convenções, sempre que haja um comando a ser lançado na shell ele será apresentado como $ comando (0.1) O elemento $ serve para identificar o texto à sua frente como o comando a lançar e, naturalmente, não faz parte dele. Sempre que um comando devolva algum output, isso será apresentado como $ comando linhas de output (0.2) (0.3) As listagems de código fonte cujos ficheiros estão acessı́veis juntamente com este documento serão feitas dentro de caixas com devido destaque e numeração, sendo também apresentado o nome do ficheiro de código fonte ao qual a listagem se refere. Finalmente, estas notas não são um produto acabado, encontrando-se em fase de desenvolvimento e aperfeicoamento. Como tal, quaisquer comentários são benvindos. Vı́tor M. Pereira2 Porto, 25 de Maio de 2006. 1 Para a lista completa das extenções do gcc : info gcc ’C Extensions’. 2 [email protected] ix Nota Prévia x Parte I. Gestão de Programas Extensos por Vı́tor M. Pereira 1 1. O modelo de compilação do C 1.1. Ideias Básicas Uma nota prévia acerca de uma linguagem de programação que é diferente da linguagem da linguagem C : a shell da GNU . Quando um comando é lançado na shell, ele é imediatamente executado. Além disso, a shell é ela própria uma linguagem de programação, no sentido em que os comandos que o utilizador escreve são um programa (e que pode também criar um ficheiro de texto contendo uma sequência de comandos da shell1 ). Por outro lado, considere o caso do C . Enquanto que um script de comandos de shell pode ser executado directamente, um programa em C precisa de ser criado essencialmente a dois tempos: 1. Em primeiro lugar, o código deve ser escrito num ficheiro de texto simples, usando por exemplo um editor como o emacs . Ao programa nesta forma (ao(s) ficheiro(s) de texto contendo o código) dá-se o nome de código fonte. 2. Depois, o código fonte necessita ser processado por um compilador que gerará um novo ficheiro contendo uma tradução do código fonte numa linguagem de máquina. Este ficheiro é chamado de executável, e diz-se que o executável foi compilado a partir do código fonte. Para executar um programa compilado, em geral é necessário escrever o nome do executável directamente na shell. Se o executável se chamar prog, então para o correr bastará lançar na shell2 : $ ./prog (1.1) 1.2. O modelo de compilação em C Quando compila um programa, o compilador opera executando uma sequência ordenada de tarefas a que se chama passos. Essa sequência consiste aproximadamente no seguinte: 1. Pré-processamento (expansão de macros, inclusão de outros ficheiros, etc.); 2. Compilação (do código fonte para linguagem assembly ); 1 É o que se designa normalmente como um script. parte do ponto-barra, ’./’, diz apenas à shell que o executável se encontra na directoria actual. Caso contrário, ’./’ deverá ser substituı́do pelo caminho correspondente. O ’./’ pode ainda ser omitido se $PATH já o contiver. 2O 3 1. O modelo de compilação do C Figura 1.1.: Esquema das diferentes fases no modelo de compilação de C . 3. Assembling (de linguagem assembly para ficheiros de objectos); 4. Link (criação do executável final a partir de um ou vários ficheiros de objectos). Esta sequência encontra-se esquematizada na Fig. 1.1. Em sistemas GNU , a compilação de um programa pode ser tão simples quanto: $ gcc source.c (1.2) Esta instrução criará o executável chamado a.out a partir do código fonte contido no ficheiro source.c. Em geral, estamos interessados em criar um executável com um nome mais personalizado e, para isso, podemos pedir directamente ao compilador para gerar um executável com o nome desejado. Por exemplo, se prog for esse nome, farı́amos, para compilar e correr: $ gcc -o prog source.c (1.3) $ ./prog (1.4) Um detalhe importante é que o gcc , para “adivinhar” o conteúdo de um determinado ficheiro, usa algumas convenções relativamente às extensões nos nomes dos ficheiros, como as que se apresentam na Tabela 1.13 . 1.3. Os passos da compilação em detalhe Uma instrução de compilação simples como a instrução (1.3), esconde, na verdade, toda uma sequência de passos intermédios desde o código fonte até ao executável. No entanto é sempre 3 Esta 4 é uma lista abreviada. A lista completa encontra-se no man gcc. 1.3. Os passos da compilação em detalhe Nome filename.c filename.i filename.h filename.o filename.a filename.so filename.s filename Interpretação atribuı́da pelo gcc Código fonte que necessita pré-processamento; Código fonte que não necessita de pré-processamento; Header file a ser incluı́da pelo pré-processador; File de objectos; Biblioteca estática de objectos; Biblioteca partilhada de objectos; Código assembly ; Código executável. Tabela 1.1.: Interpretação das extensões de ficheiros (gcc ). possı́vel, e altamente instrutivo, efectuar cada um dos passos listados na Fig. 1.1 separada e independentemente. De seguida iremos fazer isso para o “hello world”, o primeiro programa tipicamente apresentado em qualquer livro sobre C . Suponhamos que o código fonte desse programa existe num ficheiro chamado hello.c, e que é o seguinte: 1 3 5 /∗ ∗ File ’ hello . c ’ ∗ Apenas i m p r i m e a mensagem de um programa recem−n a s c i d o . ∗ ∗ −− V i t o r M. P e r e i r a ∗/ 7 #i n c l u d e < s t d i o . h> 9 11 13 i n t main ( v o i d ) { p r i n t f ( ” H e l l o , w o r l d ! \ n” ) ; return 0; } Listagem 1.1: Código fonte do programa Hello World (hello.c). Naturalmente que para o testar bastará: $ gcc -o hello hello.c (1.5) $ ./hello (1.6) Hello, world! (1.7) a seguir ao que aparecerá no terminal a mensagem acima. Mas vamos então dissecar o processo de compilação, fazendo-o explicitamente passo-a-passo. 5 1. O modelo de compilação do C 1.3.1. O pré-processador O primeiro passo é então invocar o pré-processador para expandir os macros e os header files. Para executar este passo, corremos (1.8) $ cpp hello.c > hello.i , ou, alternativamente, (1.9) $ gcc -E hello.c > hello.i O resultado é o ficheiro hello.i, contendo o código fonte com todos os macros e headers expandidos. Note-se que o ficheiro hello.i é ainda um ficheiro com código fonte em C . Por exemplo, as primeiras 10 linhas contêm o resultado da expansão do header stdio.h, 2 4 # # # # 1 1 1 1 ” h e l lo . c” ”<b u i l t −i n >” ”<command l i n e >” ” h e l lo . c” 6 8 10 # 1 ” / u s r / i n c l u d e / s t d i o . h” 1 3 4 Listagem 1.2: Código fonte pré-processado I (hello.i) , e nas linhas finais vem então o código editado em hello.c, 933 e x t e r n v o i d f u n l o c k f i l e ( FILE ∗ s t r e a m ) # 850 ” / u s r / i n c l u d e / s t d i o . h” 3 4 attribute (( nothrow 935 # 7 ” h e l lo . c” 2 937 939 941 i n t main ( v o i d ) { p r i n t f ( ” H e l l o , w o r l d ! \ n” ) ; return 0; } Listagem 1.3: Código fonte pré-processado II (hello.i) 6 )); 1.3. Os passos da compilação em detalhe 1.3.2. O compilador O passo seguinte é a compilação propriamente dita, do código pré-processado para linguagem assembly adequada ao processador especı́fico da máquina onde a compilação está a ser efectuada. A opção -S ordena ao gcc que converta o código pré-processado em linguagem assembly , sem que seja criado nenhum ficheiro de objectos: $ gcc -Wall -S hello.i (1.10) Em resultado deste comando, será criado um ficheiro hello.s contendo o código em agora em assembly . Eis como resulta num processador Intel Centrino (i686) o código assembly assim gerado: . file ” hello . c” . section . rodata 2 . LC0 : 4 6 8 10 12 14 16 18 20 22 24 26 . s t r i n g ” Hello , world ! ” . text . g l o b l main . type main , @ f u n c t i o n main : leal 4(% e s p ) , %e c x andl $ −16 , %e s p pushl −4(%e c x ) pushl %ebp movl %esp , %ebp pushl %e c x subl $4 , %e s p movl $ . LC0 , (% e s p ) call puts movl $0 , %e a x addl $4 , %e s p popl %e c x popl %ebp leal −4(%e c x ) , %e s p ret . size main , .− main . i d e n t ”GCC : (GNU) 4 . 1 . 0 20060304 ( Red Hat 4 . 1 . 0 − 3 ) ” . section . n o t e . GNU−s t a c k , ” ” , @ p r o g b i t s Listagem 1.4: Código fonte em assembly (hello.s) Ainda que de passagem, notemos o aspecto seguinte. No nosso código em hello.c, invocámos a função printf, que pertence à biblioteca standard do C . Ou seja, esta função – este objecto – não é definido por nós mas existe algures numa biblioteca pré-complilada4. Este facto é revelado na linha 17 do código assembly : a instrução call puts é uma chamada à função externa que fará a tarefa do printf. 4 Essa biblioteca, no meu sistema, chama-se libc.so 7 1. O modelo de compilação do C 1.3.3. O assembler O objectivo do assembler é converter linguagem assembly em código de máquina e gerar um ficheiro de objectos. Havendo chamadas a funções externas no código assembly , o assembler deixa os endereços dessas funções indefinidos, para serem depois completados pelo linker. O assembler pode ser invocado através da seguinte linha de comandos: $ as hello.s -o hello.o (1.11) , ou, alternativamente, $ gcc -c hello.c (1.12) , ou, ainda, $ gcc -c hello.s (1.13) O ficheiro resultante, hello.o, contém as instruções em linguagem de máquina para o programa hello world, mas contem ainda uma referência indefinida à função externa printf (ou, no meu caso, como vimos acima, puts), uma vez que esta foi invocada, mas não definida, em hello.c. Nesta fase em que temos já o nosso código em linguagem de máquina está quase tudo pronto para podermos executar o programa. Mas se isso fosse feito neste momento, o sistema operativo não saberia como imprimir o texto Hello World! porque não saberia onde encontrar a tal função printf. Para verificarmos isto mesmo podemos ver quais são os objectos (as funções) existentes no ficheiro hello.o. Basta usar o comando nm: $ nm hello.o (1.14) 00000000 T main U puts Como era de esperar, existem apenas duas funções: o main do nosso código em hello.c, e o printf, aqui representado por puts. A parte importante é que, enquanto que ao main está associado um endereço (o número 00000000 na primeira coluna), ao puts não, e por isso, aparece a letra U (undefined symbol) na coluna central relativa a este objecto. Portanto está tudo pronto, falta só colar os objectos, que é como quem diz, procurar onde estão os objectos que foram deixados indefinidos pelo assembler de modo a que, quando o programa for executado, o sistema operativo saiba o que fazer para executar todas as tarefas pedidas no código. 1.3.4. O linker Chegamos então ao estádio final da compilação: o linker. Este irá linkar os ficheiros de objectos e os objectos criando o executável final. Na prática, um executável requer bastantes mais funções associadas à interface com o sistema operativo durante o tempo de execução (as chamadas run time libraries). Consequentemente, as instruções de linkagem usadas internamente pelo gcc são em geral bastante complicadas. Por exemplo, o comando completo para linkar o programa Hello World usando o GNU linker ld é 8 1.3. Os passos da compilação em detalhe $ ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat-linux/3.2.3/crtbegin.o -L/usr/lib/gcc-lib/i686/3.3.1 hello.o -lc /usr/lib/gcc-lib/i386-redhat-linux/3.2.3/crtend.o /usr/lib/crtn.o -o hello O resultado deste comando será o executável chamado hello (passado através da opção -o hello acima), do nosso programa. Bom, mas felizmente que não é necessário reter na memória todas as bibliotecas passadas acima ao ld. O gcc faz isso por nós de forma muito mais transparente (e conveniente!). A instrução anterior é, na verdade, equivalente a lançar na shell $ gcc hello.o -o hello (1.15) Dada a extensão .o do ficheiro hello.o, o gcc sabe que se trata de um ficheiro de objectos e, como é o único ficheiro passado, ele encarrega-se de linkar os seus objectos com a biblioteca standard do C e gerar o executável. Temos finalmente um programa utilizável: $ ./hello Hello, world! (1.16) (1.17) Este executável em nada se distingue daquele que obtivemos mais acima de forma bastante menos penosa através de uma invocação única do gcc em (1.5). Para terminar podemos mesmo confirmar que o nosso executável incorpora um conjunto muito maior de objectos além dos que definimos no nosso código fonte (o main e o printf), examinando a tabela de sı́mbolos do executável. Para tal recorremos novamente ao comando nm: $ nm hello 08049544 A 080482d0 t 08049544 b 08049444 d 08049440 d 08049538 D 08049538 W 080483e4 t 080482f8 t 0804953c D 0804944c d 08049448 d 08049454 d 08049544 A 0804843c r 08049548 A 08048408 T 08049440 a 08049440 a 08048424 R __bss_start call_gmon_start completed.1 __CTOR_END__ __CTOR_LIST__ __data_start data_start __do_global_ctors_aux __do_global_dtors_aux __dso_handle __DTOR_END__ __DTOR_LIST__ _DYNAMIC _edata __EH_FRAME_BEGIN__ _end _fini __fini_array_end __fini_array_start _fp_hw 08048334 t frame_dummy 0804843c r __FRAME_END__ 08049520 d _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 08048254 T _init 08049440 a __init_array_end 08049440 a __init_array_start 08048428 R _IO_stdin_used 08049450 d __JCR_END__ 08049450 d __JCR_LIST__ w _Jv_RegisterClasses 08048388 T __libc_csu_fini 08048390 T __libc_csu_init U __libc_start_main@@GLIBC_2.0 0804835c T main 08049540 d p.0 08049440 a __preinit_array_end 08049440 a __preinit_array_start U puts@@GLIBC_2.0 080482ac T _start 9 1. O modelo de compilação do C 1.4. Linkagem estática e dinâmica O processo de linkagem pode ser estático ou dinâmico. Os sistemas UNIX e Linux (e em geral todos os sistemas modernos) permitem a criação e utilização destes dois tipos de bibliotecas: dinâmicas5 ou estáticas. Bibliotecas estáticas não são mais do que conjuntos de ficheiros de objectos que serão linkados a um dado programa durante a fase do link da compilação. Nesta fase todos os objectos requeridos pelo programa são reunidos no executável. As bibliotecas estáticas, por si só, não são relevantes depois de gerado o executável nem durante a execução6 . As bibliotecas partilhadas, por outro lado, são linkadas a um programa em dois tempos. Inicialmente, durante a compilação, o linker verifica que todos os objectos requeridos pelo programa estão, ou linkados ao programa, ou linkados a uma das suas bibliotecas partilhadas. Todavia, os objectos da biblioteca dinâmica não são inseridos directamente no executável como acontece no caso estático. Quando se corre o executável, um outro programa do sistema7 vai encarregar-se de verificar quais são as bibliotecas partilhadas que foram linkadas com o executável, de as carregar na memória e de anexar uma cópia sua ao executável residente na memória. A relativa complexidade de carregar dinamicamente os objectos partilhados torna o programa relativamente lento (no que se refere ao inı́cio da sua execução), quando comparado com o mesmo programa linkado estaticamente. No entanto, nas aplicações mais correntes – como sejam programas para uso no nosso desktop – este aspecto é amplamente ultrapassado pelas vantagens que surgem quando um segundo programa que usa a mesma biblioteca partilhada é executado: este pode usar a mesma cópia da biblioteca em memória e poupar assim nos recursos pedidos ao sistema. Por exemplo, a biblioteca standard do C é normalmente uma biblioteca partilhada, e é utilizada por todos os programas em C . O truque está em que apenas uma cópia da biblioteca é carregada na memória, significando que é necessária muito menos memória para a execução de qualquer programa em C . Outra vantagem óbvia é a de que, não sendo os objectos partilhados incorporados directamente no executável, o seu tamanho final será muitı́ssimo mais reduzido, poupando-se também em espaço de disco. Existe porém um detalhe importante na utilização de bibliotecas partilhadas. Suponhamos que estamos a correr um programa compilado com uma dada biblioteca partilhada. Se essa biblioteca for recompilada e tentarmos correr uma segunda cópia do nosso programa com a nova biblioteca, teremos um problema evidente: o loader irá ver que uma biblioteca com o mesmo nome está já carregada em memória (para o primeiro programa) e irá linkar esta versão antiga ao segundo programa, em vez daquela recentemente compilada. Quando um programa é compilado para usar bibliotecas partilhadas, estas precisarão de ser carregadas dinamicamente ao tempo de execução de modo a que seja possı́vel usar os objectos externos que elas fornecem. O comando ldd examina um executável e devolve uma lista das bibliotecas partilhadas que esse programa requer para poder correr. Tais bibliotecas constituem as dependências partilhadas do executável. Por exemplo, o comando seguinte mostra como encontrar (no meu sistema) as dependências do programa Hello Wold : 5 Também designadas de partilhadas. é, depois de gerado o executável a existência, ou não, da biblioteca estática no sistema é totalmente irrelevante para a execução do programa (da mesma forma que o ficheiro de objectos hello.o é totalmente irrelevante depois de gerado o executável). 7 O chamado dynamic loader. 6 Isto 10 1.4. Linkagem estática e dinâmica $ ldd hello linux-gate.so.1 => (0x002e2000) libc.so.6 => /lib/libc.so.6 (0x00300000) /lib/ld-linux.so.2 (0x002e3000) Mas... isto significa que o executável hello não é independente! Ele só correrá num sistema onde existam estas 3 bibliotecas. Bom, neste caso isso não é tão grave como parece porque, em geral, qualquer sistema GNU /Linux terá um sistema de C e, sendo o mesmo tipo de arquitectura, o nosso executável correrá lá em princı́pio. Mas quando as dependências partilhadas são bibliotecas especı́ficas8, é importante garantir que elas estarão disponı́veis no sistema onde pretendemos correr o programa com linkagem dinâmica9 . Há casos em que poderemos não estar muito interessados em que o nosso programa final fique com linkagem dinâmica. Para esses casos há, felizmente, uma solução simples. É, em geral possı́vel especificar ao gcc que o executável deve ser linkado estaticamente, mesmo quando os objectos externos se encontram em bibliotecas partilhadas. O que acontece é que, quando nada é dito, o gcc procede à linkagem dinâmica daqueles objectos que se encontram em bibliotecas partilhadas (porque em geral, como vimos, é o modo mais eficiente de usar os recursos em memória e disco). Mas, se os objectos se encontram lá arquivados nessas bibliotecas, deve ser possı́vel usá-los como qualquer outros ficheiros .o e, em particular, linká-los estaticamente. Isso consegue-se passando a opção --static ao gcc . Portanto, se eu quiser criar um executável do Hello World com linkagem estática basta fazer: $ gcc -Wall hello.c --static -o hello-static (1.18) , ou, se já tiver o ficheiro de objectos: $ gcc hello.o --static -o hello-static (1.19) Para ter a certeza de que o nosso novo executável hello-static não tem linkagem dinâmica, corremos outra vez o tabelador de dependências dinâmicas, ldd: $ ldd hello-static not a dynamic executable (1.20) (1.21) Cá está: não tem dependências dinâmicas nenhumas. Mas, claro, existem sempre os tais senão a que aludimos acima. Como o hello-static está agora linkado estaticamente, todos os objectos fazem parte do executável, incluindo os das run time libraries. Ora vejamos. Para hello, que é dinâmico, tenho no meu sistema: $ nm hello | wc -l 39 (1.22) (1.23) 8 Como acontece com bibliotecas para resolver determinados problemas numéricos. um exemplo prático. Quando instalamos uma peça qualquer de software num sistema GNU /Linux temos essencialmente duas hipóteses. Uma é instalar a partir das fontes: consiste em copiar a totalidade do código fonte do programa (normalmente um pacote .tar.gz), compilá-lo na nossa máquina e depois colocar os executáveis nos lugares apropriados (isto, em geral é feito de forma simples com dois comandos apenas: make e make install). A outra hipótese é instalar apenas os binários – o código executável que alguém já se encarregou de compilar antes. Ora, sendo um binário é preciso garantir que a nossa máquina tem a mesma arquitectura daquela para a qual o software foi compilado (daı́ que, nestes casos, existam várias versões de binários para diferentes arquitecturas). Além, disso, como quase todas as aplicações recorrem a bibliotecas partilhadas, tanto num caso como no outro os scripts de instalação devem (e fazem-no em geral) verificar se as dependências existem no sistema. 9 Eis 11 1. O modelo de compilação do C exactamente 39 objectos. Mas no hello-static, que é estático, tenho: $ nm hello-static | wc -l 1787 (1.24) (1.25) nada mais nada menos do que 1787 objectos! Isto reflecte-se, obviamente, no tamanho do executável: enquanto que o executável hello ocupa $ ls -lh hello -rwxr-xr-x 1 vpereira users 4.6K May 24 10:57 hello (1.26) (1.27) 4.6 KB, o estático hello-static ocpupa $ ls -lh hello-static -rwxr-xr-x 1 vpereira users 478K May 24 11:01 hello-static 478 KB, ou seja, é cerca de 100 vezes maior! 12 (1.28) (1.29) 2. Compilação de múltiplas files 2.1. Distribuição de código Depois de alguma prática – ou necessidade – de programação, facilmente se chega a um ponto em que se torna conveniente dividir o código de um programa por vários ficheiros separados. Entre muitas outras vantagens, esta divisão torna o código muito mais fácil de gerir e, sobretudo se se trata de código extenso e complexo, de entender. Entre as maiores vantagens de assim proceder, está a possibilidade de compilar as diversas partes desse programa separada e independentemente. Os programadores, em geral, desenham um programa dividindo-o por secções representativas de determinadas tarefas que se pretende desempanhar. A ideia é que cada uma destas secções esteja contida num ou mais ficheiros, os quais poderão conter uma ou várias funções. Um dos ficheiros conterá necessariamente o main(), e os restantes poderão ser considerados como uma biblioteca de funções. Para perceber como tal é possı́vel e como se efectua na prática, nada melhor do que um exemplo simples, nesta altura já nosso conhecido. No exemplo seguinte, dividiremos o programa Hello World por três ficheiros separados: main.c, hello fn.c e hello.h que será o nosso header file particular. Eis no que consiste o código de main.c: 2 4 6 /∗ ∗ F i l e ’ main . c ’ ∗ U t i l i z a d a p a r a d e m o n s t r a r a d i s t r i b u i c a o de c o d i g o ∗ por d i v e r s a s f i l e s . ∗ ∗ −− V i t o r M. P e r e i r a ∗/ 8 #i n c l u d e ” h e l l o . h” 10 12 14 i n t main ( v o i d ) { h e l l o ( ” world ” ) ; return 0; } Listagem 2.1: Código fonte do segmento main.c. Comparando-o com o código fonte mostrado na Listagem 1.1 da página 5, vemos que a chamada ao printf foi aqui substituı́da por uma chamada a uma nova função, apropriadamente chamada 13 2. Compilação de múltiplas files hello. Claro que esta última não faz parte da biblioteca do C , sendo definida por nós. Mas, em vez de a declararmos e definirmos neste mesmo ficheiro main.c vamos fazê-lo no ficheiro independente hello fn.c cujo conteúdo é: 2 4 6 8 10 /∗ ∗ File ’ hello fn . c ’ ∗ U t i l i z a d a p a r a d e m o n s t r a r a d i s t r i b u i c a o de c o d i g o ∗ p o r d i v e r s a s f i l e s . Contem a d e f i n i c a o da f u n c a o ∗ ’ h e l l o ’ u s a d a p e l o ’ main . c ’ ∗ ∗ −− V i t o r M. P e r e i r a ∗/ #i n c l u d e < s t d i o . h> #i n c l u d e ” h e l l o . h” 12 14 16 void h e l l o ( c o n s t c h a r ∗ name ) { p r i n t f ( ” H e l l o , %s ! \ n” , name ) ; } Listagem 2.2: Código fonte do segmento hello fn.c. Ou seja, esta função apenas imprime a string passada como argumento para o stdout, em geral, o terminal. Claro que, como sabemos, o compilador precisa de conhecer as declarações de todas as funções antes que estas sejam chamadas pela primeira vez. Daı́ que, como a função hello será definida fora de main.c, seja necessário incluir um protótipo seu para que o seu tipo, argumentos e return value sejam conhecidos pelo compilador quando este processar o ficheiro main.c. Isso está assegurdo pela instrução #include "hello.h" passada no cabeçalho de main.c1. No nosso header poremos apenas o tal protótipo: 1 3 5 /∗ ∗ File ’ hello2 . h ’ ∗ U t i l i z a d a p a r a d e m o n s t r a r a d i s t r i b u i c a o de c o d i g o ∗ p o r d i v e r s a s f i l e s . S e r a o h e a d e r comum que contem o ∗ p r o t o t i p o da f u n c a o h e l l o r e q u e r i d a p e l o ’ main . c ’ ∗/ 7 9 v o i d h e l l o ( c o n s t c h a r ∗ name ) ; v o i d bye ( v o i d ) ; Listagem 2.3: Código fonte do header hello.h. Portanto, não obstante estar repartido por três ficheiros, este programa faz exactamente o mesmo que o código apresentado na Listagem 1.1. Para o compilar, um dos métodos consiste em passar 1 Note-se que foi usado #include "hello.h" e não #include <hello.h>. É importante que saiba a diferença e o significado de cada uma das formas. 14 2.1. Distribuição de código todos os ficheiros do código fonte ao gcc : $ gcc -Wall main.c hello fn.c -o newhello (2.1) , chamando newhello ao novo executável. Note-se que o header hello.h não consta da lista de ficheiros passadas ao gcc , precisamente porque a instrução include no código encarrega-se de dar a informação àcerca da necessidade deste ficheiro ao compilador. Note-se também que se apenas passássemos main.c, terı́amos problemas: $ gcc -Wall main.c /tmp/ccGPQgAP.o: In function ‘main’: main.c:(.text+0x19): undefined reference to ‘hello’ collect2: ld returned 1 exit status , e o compilador queixar-se-ia, com toda a razão, de que não encontra uma tal função hello2 . Bom, mas correndo o executável gerado com o comando 2.1, oter-se-á, à semelhança de 1.5, $ ./newhello Hello, world! (2.2) (2.3) Depois do capı́tulo anterior, já sabemos que, ao lançar o comando gcc acima na shell, um conjunto ordenado de passos ocorre atrás do pano, para que o executável seja gerado. Neste caso, em que vários ficheiros com código fonte foram passados ao compilador, cada uma delas foi processada independentemente até à fase do assembling, sendo que, na fase seguinte do linking, o linker juntou e organizou os todos objectos gerados independentemente até aı́ para os deixar no executável final. Ora isto claramente abre uma nova possibilidade. Quando um programa está inteiramente contido num ficheiro único é óbvio que qualquer alteração na fonte, implica a recompilação de todo o código. E recompilar leva algum tempo, sobretudo se pensarmos num programa com vários milhares de linhas de código e dezenas de funções/objectos. Além disso, em geral as alterações ao código são relativamente localizadas: isto é, depois de termos um programa de pé, o mais certo é serem necessários pequenos ajustes, algumas correcções, e não uma reescrita completa da fonte desde o zero. Quando os programas são organizados de forma a que os seus objectos estejam definidos modularmente em ficheiros separados, estas tarefas ficam altamente simplificadas, uma vez que apenas os ficheiros alterados necessitam de nova compilação3 . Neste método de trabalho, os ficheiros de código fonte são compiladas separadamente e depois linkadas – um procedimento a dois tempos. No primeiro, compilam-se as fontes gerando apenas os ficheiros de objectos correspondentes. No segundo, estes ficheiros de objectos são combinadas (linkadas) no executável final. 2 Note-se que é precisamente o linker (o já nosso conhecido ld) quem se queixa através da mensagem collect2: ld returned 1 exit status 3 Além do que, como é evidente, fica muito mais fácil trabalhar com vários ficheiros pequenos, do que com um mega-ficheiro onde, apesar de toda a diligência dos editores de texto, se poderá perder mais tempo à procura da linha a corrigir, do que a efectuar ou pensar na correcção. 15 2. Compilação de múltiplas files 2.2. Criando ficheiros de objectos a partir das fontes Já sabemos desde o capı́tulo anterior que ao gcc pode ser pedido que interrompa o processo de compilação no final de cada um dos seus passos intermédios. Em particular, sabemos já que o comando $ gcc -Wall -c main.c (2.4) irá compilar o código em main.c e, em vez de gerar um executável gera apenas um novo ficheiro de objectos main.o. O comando correspondente para o ficheiro que contém a definição da função é $ gcc -Wall -c hello fn.c (2.5) Nestes casos, não é necessário usar a opção -o para instruir o compilador àcerca do nome para o ficheiro de objectos resultante, já que o gcc cria automaticamente um ficheiro com o mesmo nome, substituindo .c por .o4 . 2.3. Criando executáveis a partir de ficheiros de objectos O passo final na criação de um executável é usar o linker para linkar os nossos ficheiros de objectos. Na prática é muito mais fácil (e seguro) usar o próprio gcc para essa tarefa. Se lhe forem passados apenas ficheiros de objectos, o gcc sabe que deverá apenas proceder ao link desses ficheiros e gerar um executável. Isto é, o nosso executável newhello obtém-se de: $ gcc main.o hello fn.o -o newhello (2.6) De passagem, repare-se que esta é uma das raras vezes em que não invocámos a opção -Wall para o gcc , uma vez que ela apenas diz respeito ao compilador e, nesta fase, tanto main.c como hello fn.c foram já compilados com sucsso. Além disso, a fase do linking é um processo sem qualquer margem para ambiguidades: ou linka ou não linka5 , de modo que não faz sentido ter avisos nesta fase. Seja o gcc , seja o ld chamado a fazer o link, o produto final é mesmo, pronto a funcionar: $ ./newhello Hello, world! (2.7) (2.8) 2.4. Recompilar e re-linkar Claro, nenhum código minimamente sério, fica pronto, no seu estado final, depois da primeira compilação. Para efeitos do nosso exemplo, admitamos que não era exactamente esta a mensagem 4 Como é evidente, a opção -o pode ser usada também aqui, no caso de querermos que o ficheiro de objectos tenha um nome diferente. No entanto isso raramente se justifica. 5 O link falha sempre que existam objectos que o linker não consegue identificar ou encontrar. E se isso acontece o executável não pode ser criado: não faz sentido dar ao linker o livre arbı́trio de decidir continuar neste ou naquele caso (como o compilador, no sentido estrito, faz) porque perderı́amos totalmente o controlo e deixarı́amos de estar a fazer programação. 16 2.4. Recompilar e re-linkar que pretendemos imprimir no ecrã. Bom, nesse caso editamos main.c e fazemos a alteração necessária: 2 4 6 /∗ ∗ F i l e ’ main . c ’ ∗ U t i l i z a d a p a r a d e m o n s t r a r a d i s t r i b u i c a o de c o d i g o ∗ por d i v e r s a s f i l e s . ∗ ∗ −− V i t o r M. P e r e i r a ∗/ 8 #i n c l u d e ” h e l l o . h” 10 12 14 i n t main ( v o i d ) { h e l l o (” everyone ” ) ; return 0; } Listagem 2.4: Uma alteração local a main.c. Para obter um novo executável basta compilar o ficheiro recém alterado $ gcc -Wall -c main.c (2.9) Dado que nada mudou no que diz respeito aos restantes segmentos do código, não há necessidade de recompilar a outro ficheiro hello fn.c. A única coisa a fazer é re-linkar o main.o com os ficheiros de objectos já compilados anteriormente: $ gcc main.o hello fn.o -o newhello (2.10) Como é esperado, este newhello executa as novas instruções, imprimindo: $ ./newhello Hello, everyone! (2.11) (2.12) Para um projecto computacional extenso, este modo de proceder poupa valioso tempo ao programador entre compilações. Estes passos podem ser facilmente automatizados através de um script que incorpore a sequência de comandos acima. Tudo isso, incluindo a verificação automática dos ficheiros que carecem de recompilação, é feito de um modo muito eficiente pelo programa make que abordaremos mais adiante. É importante que não se fique com a ideia de que utilidade deste esquema de compilação se limita aos casos de grandes√programas, e aplicações complexas. Um exemplo tão prosaico como um programa que calcule 2 é já um exemplo desta metodologia em funcionamento: (quase) ninguém irá definir a sua própria função sqrt() para obter tal resultado. Irá, isso sim, recorrer, por exemplo ao sqrt() fornecido pela biblioteca matemática do C . Mas ninguém irá compilar a totalidade da biblioteca standard do C sempre que quiser calcular uma raı́z quadrada! Toda a gente sabe que basta simplesmente “utilizá-la” compilando o código com a opção -lm. Esta utilização 17 2. Compilação de múltiplas files tão trivial só é possı́vel porque sqrt(), juntamente com todos os outros objectos que constituem essa biblioteca, existem já no sistema pré-compilados. O programador apenas trata de os linkar (o -lm faz isso mesmo) com o seu código, de forma completamente transparente e cómoda. Numa última nota, igualmente evidente, diremos que a modularização de um programa permite que uma determinada função, ou conjunto de funções, possa ser utilizado por vários programas diferentes, sem que haja a necessidade de reescrever ou copiar constantemente o seu código de programa para programa. Isto é útil em todas as circunstâncias, aplicando-se nomeadamente às rotinas numéricas desenvolvidas no âmbito da cadeira de Fı́sica Computacional. 2.5. Partilha de variáveis No exemplo modular que apresentámos acima, toda a informação a partilhar entre as diferentes funções era passada através de parâmetros de funções – usava apenas variáveis locais. Esta é uma das formas de tornar acessı́veis a um módulo, variáveis e objectos declarados e inicializados noutro. Só que passar tudo como argumento de funções pode tornar-se laborioso muito rapidamente, quando a lista de parâmetros é extensa, ou o número de módulos elevado. Além disso, a passagem de argumentos por valor implica a criação de cópias locais das variáveis envolvidas, e, necessáriamente, duplicação da memória, introduzindo dificuldades quando se trata de grandes arrays ou estruturas. Para obviar a estas dificuldades e/ou para criar um programa mais simples e legı́vel, é comum o recurso a variáveis com um âmbito (scope) mais vasto do que apenas o local. Para código num ficheiro único, isso consegue-se através de variáveis declaradas fora de qualquer bloco/função, que assim terão âmbito global e duração permanente. Mas para código repartido por vários ficheiros, existem algumas subtilezas. 2.5.1. Âmbito (Scope) Qualquer variável presente no código fonte têm um âmbito6 . O âmbito da variável define a porção do programa onde essa variável está acessı́vel, pode ser acedida e manipulada. Para uma analogia, imagine-se um transeunte na rua. A partir do seu posto, ele pode ver determinadas coisas, como sejam os prédios à sua volta, ou outras pessoas na rua. Mas há certas coisas que ele não consegue ver, como sejam as pessoas que estão dentro dos prédios que ele vê. Mas estas últimas conseguirão ver o transeunte das suas janelas. Precisamente do mesmo modo, algumas variáveis de um programa são – como este homem – visı́veis a partir de qualquer outra parte do programa (as variáveis globais), enquanto que outras se encontram escondidas – como os moradores – dentro das paredes que são os parentesis curvos {}. Os principais âmbitos possı́veis são: Protótipo Variáveis/funções numa lista de parâmetros de um protótipo de função têm âmbito de protótipo. Como se trata de um âmbito altamente limitado, estes identificadores são, na prática, pouco mais do que comentários. 6 Não 18 apenas as variáveis, mas qualquer identificador, tem um âmbito. 2.5. Partilha de variáveis Bloco Variáveis/funções declaradas dentro de um bloco ({}) têm o âmbito desse bloco. Os parâmetros de uma função, em particular, têm âmbito de bloco sendo este delimitado pelos ({}) que delimitam a sua definição. O âmbito inicia no ponto em que a variável é declarada, e termina com o } do bloco correspondente. File Variáveis/funções declaradas fora de todos os blocos e listas de parâmetros têm âmbito de ficheiro. O âmbito de ficheiro principia no ponto da declaração extendendo-se até ao final desse ficheiro de código. A listagem seguinte exemplifica alguns casos: 6 /∗ ∗ F i l e ’ scope . c ’ ∗ Exemplos de s c o p e / a m b i t o de v a r i a v e i s ∗ ∗ −− V i t o r M. P e r e i r a ∗/ 8 int a ; 2 4 10 12 14 i n t main ( ) { int b ; 16 // Ambito g l o b a l . A v a r i a v e l ’ a ’ e v i s i v e l n e s t e p o n t o do // programa , e a t e ao f i n a l d e s t e f i c h e i r o de c o d i g o mas // a s v a r i a v e i s ’ b ’ e ’ c ’ nao s a o v i s i v e i s a q u i . // // // // Ambito l o c a l no main ( ) . As v a r i a v e i s ’ a ’ e ’ b ’ s a o v i s i v e i s a q u i . A v a r i a v e l ’ b ’ e v i s i v e l a t e ao } que t e r m i n a o main ( ) . V a r i a v e i s ’ c ’ e ’ d ’ nao s a o v i s i v e i s aqui . 18 { int c ; 20 22 } 24 int d ; 26 // Ambito l o c a l n e s t e b l o c o d e l i m i t a d o p o r e s t e s // { . . . } . V a r i a v e i s ’ a ’ , ’ b ’ e ’ c ’ s a o t o d a s v i s i v e i s a q u i . // // // // Ambito l o c a l no main ( ) . As v a r i a v e i s ’ a ’ e ’ b ’ e ’ d ’ s a o v i s i v e i s a q u i . A v a r i a v e l ’ d ’ tem um s c o p e a n a l o g o ao de ’ b ’ , mas m a i s l i m i t a d o , uma v e z que e s s e s c o p e comeca n e s t e p o n t o e a t e ao f i m do main ( ) . return 0; 28 } Listagem 2.5: Exemplos de âmbito de variáveis (scope.c). 2.5.2. Classes de armazenamento (storage classes) A sintaxe geral para a declaração de uma variável é [storage_class] type D1 [, D2, ...]; 19 2. Compilação de múltiplas files Posição da Declaração Especificador Âmbito Duração Fora de qualquer bloco Dentro de um bloco Dentro de um bloco nenhum, extern, static nenhum, auto, register extern, static File Bloco Bloco Estática Automática Estática Tabela 2.1.: Classes de armazenamento, âmbito e suração de variáveis. , onde as partes entre [...] são opcionais. O ponto onde uma variável é declarada não determina univocamente o seu âmbito quando o código se encontra repartido por diferentes ficheiros. Temos assim de considerar as classes de armazenamento de uma variável, as quais deteminam o seu âmbito, a sua duração e o seu modo de linkar. Quanto ao seu tempo de vida, podemos ter variáveis: Estáticas A variável é gerada e inicializada apenas uma vez, antes de o programa iniciar. A variável existe continuamente ao longo da execução do programa. Automáticas A variável é gerada de novo sempre que a execução do programa entra no bloco no qual ela está definida. Quando esse bloco é terminado, a memória ocupada pela variável é libertada. A classe de armazenamento de uma variável é determinada pela posição da sua declaração no código fonte, e pelo especificador de armazenamento, se houver. Este é um de entre: auto Determina que a variável terá uma duração automática. É raramente usado uma vez que todas as variáveis declaradas dentro de um bloco sem nenhum identificador de armazenamento têm duração automática, por omissão. static Variáveis assim declaradas têm duração estática. Este especificador é utilizado para declarar variáveis estáticas com um âmbito limitado. extern Este especificador usa-se para declarar variáveis com duração estática e que podem ser utilizadas em todo o código fonte, incluindo ficheiros separadas. register Solicita ao compilador para armazenar a variável em causa num registo do CPU, se tal for possı́vel. Consequentemente, o operador de endereçamento deixa de poder ser usado, mas, em todos os outros aspectos, são equivalentes às variáveis declaradas como auto. Tendo isto em conta, podemos resumir as várias propriedades das variáveis na tabela 2.1. É importante notar, que, em C , todas as funções são automaticamente extern e têm o âmbito de ficheiro. É também para respeitar as regras de âmbito, que é necessário incluir um protótipo de todas as funções (ou a sua definição, se for o caso) antes que elas sejam chamadas nalguma porção de código: só assim se garante que elas estejam no âmbito correcto. Por esse motivo, os protótipos vão, ou devem ir, sempre especificados nos header files. 20 2.6. Organização dos dados em cada ficheiro 2.6. Organização dos dados em cada ficheiro Todos os ficheiros de código devem ter uma organização coerente e funcional. A ordem pela qual as instruções aparecem num ficheiro de código é, tipicamente: • Um preâmbulo, onde constarão vários #define, #include, e typedef relativos a tipos de dados importantes; • Declaração de todas as variáveis externas e globais. Estas últimas poderão ser inicializadas também nesta fase; • Uma ou várias funções. A ordem destes items é importante, uma vez que em C todo e qualquer objecto tem de ser declarado antes de utilizado pela primeira vez. Funções com return, devem ser definidas, ou pelo menos prototipadas, antes de serem chamadas. Estes protótipos encontram-se geralmente numa das header file (*.h). 21 2. Compilação de múltiplas files 22 3. Criação de Bibliotecas Um biblioteca é uma colecção de ficheiros de objectos reunidos num único ficheiro designado de arquivo. É, portanto, um modo conveniente de distribuir um largo número de ficheiros de objectos relacionados entre si. Para exemplicar esta funcionalidade, e como se pode tirar partido dela, demonstraremos a seguir como usar a aplicação ar (GNU archiver) para criar um biblioteca estática1 . Invoquemos o nosso sempre presente projecto do Hello World, para o qual vamos criar uma biblioteca chamada libhello.a que vai conter a definição de duas funções. Uma delas é a função hello() que está definida no ficheiro hello fn.c apresentado na Listagem 2.2. A outra será a função bye() que definiremos num ficheiro chamado bye fn.c: 1 3 5 7 /∗ ∗ F ile ’ bye fn . c ’ ∗ U t i l i z a d a p a r a d e m o n s t r a r a d i s t r i b u i c a o de c o d i g o ∗ p o r d i v e r s a s f i l e s . Contem a d e f i n i c a o da f u n c a o ∗ ’ bye ’ u s a d a p e l o ’ main . c ’ ∗ ∗ −− V i t o r M. P e r e i r a ∗/ 9 11 #i n c l u d e < s t d i o . h> #i n c l u d e ” h e l l o 2 . h” 13 15 17 v o i d bye ( v o i d ) { p r i n t f ( ” Goodbye ! \ n” ) ; } Listagem 3.1: Função bye() (bye fn.c). Ambas as funções usam o header hello2.h: 2 4 6 /∗ ∗ File ’ hello2 . h ’ ∗ U t i l i z a d a p a r a d e m o n s t r a r a d i s t r i b u i c a o de c o d i g o ∗ p o r d i v e r s a s f i l e s . S e r a o h e a d e r comum que contem o ∗ p r o t o t i p o da f u n c a o h e l l o r e q u e r i d a p e l o ’ main . c ’ ∗/ 1 Limitamo-nos aqui à descrição de bibliotecas estáticas 23 3. Criação de Bibliotecas 8 v o i d h e l l o ( c o n s t c h a r ∗ name ) ; v o i d bye ( v o i d ) ; Listagem 3.2: Header hello2.h. O primeiro passo na criação da biblioteca é compilar as fontes e gerar os ficheiros de objectos correspondentes: $ gcc -Wall -c hello_fn.c $ gcc -Wall -c bye_fn.c $ ls bye_fn.* hello_fn.* bye_fn.c bye_fn.o hello_fn.c hello_fn.o De seguida criamos a biblioteca usando o ar: $ ar cr libhello.a hello fn.o bye fn.o (3.1) O cr acima trata-se de duas opções para o ar (c: criar o arquivo, r: substituir no caso de já existir um .o com o mesmo nome no arquivo). O nome pretendido para a biblioteca (libhello.a) é passado antes dos seus membros e, se ela ainda não existir será então criada. O ar também permite listar o conteúdo das bibliotecas, através da opção t (tabelar): $ ar t libhello.a hello_fn.o bye_fn.o Bom, agora que temos uma biblioteca há que lhe dar algum uso. Para isso criamos um main bem simples: 2 4 6 /∗ ∗ F i l e ’ main . c ’ ∗ U t i l i z a d a p a r a d e m o n s t r a r a u t i l i z a c a o de ∗ uma b i b l i o t e c a recem−c r i a d a . ∗ ∗ −− V i t o r M. P e r e i r a ∗/ 8 #i n c l u d e ” h e l l o 2 . h” 10 12 14 16 i n t main ( v o i d ) { h e l l o (” everyone ” ) ; bye ( ) ; return 0; } Listagem 3.3: Driver para biblioteca libhello.a. 24 Temos agora duas hipóteses para compilar este programa (main.c). A primeira é simplesmente: $ gcc -Wall main.c libhello.a -o hello (3.2) , que fará com que o main() seja linkado com os objectos da biblioteca. A segunda hipótese é usar um atalho e recorrer à opção -l do gcc : $ gcc -Wall -L. main3.c -lhello -o hello (3.3) Neste último comando, a opção -lhello instrui o gcc a linkar com a biblioteca libhello.a, e -L serve para dizer ao gcc para procurar essa biblioteca na directoria actual2 . Note-se que a nossa recém-criada biblioteca libhello.a é passada ao gcc como passarı́amos, por exemplo, a biblioteca matemática usando -lm. A parte a reter é que, quando uma bibliteca é passada ao gcc através do -l, deve indicar-se apenas a porção no seu nome que vem à frente de lib, tal como fizémos acima. Correndo o nosso executável obtemos então: $ ./hello Hello, everyone! Goodbye! , e confirmamos a simplicidade que é criar e usar uma biblioteca definida à medida das nossas necessidades. Fica implı́cito no que se referiu acima que esta nossa biblioteca é usada e invocada como qualquer outra biblioteca estática do sistema ou standart do C (não tem um estatuto superior — nem inferior — por ter sido criada por nós). 2 É preciso indicar isto ao gcc porque, por omissão, ele só procura bibliotecas num conjunto restrito de caminhos. Uma alternativa a usar -L constantemente consiste em adicionar caminhos à variável de ambiente LIBRARY PATH ou LD LIBRARY PATH. 25 3. Criação de Bibliotecas 26 4. Criação e gestão de uma Makefile Para aqueles não familiarizados com o programa make , este capı́tulo apresenta uma demonstração simples da sua utilização. O make entra em cena sempre que o nosso código começa a ficar demasiado extenso e/ou segmentado em diferentes ficheiros e bibliotecas. É quase impensável compilar um programa com dezenas de ficheiros de código, usando uma instrução gcc para cada um deles numa shell. Além disso, já sabemos que em muitos casos, apenas um ficheiro precisa de ser recompilado, mesmo em projectos grandes. O make determina automaticamente quais são as partes do programa que carecem de recompilação e procede à compilação apenas destes segmentos. Para isso, o make usa um script, um conjunto de regras e instruções, criado pelo programador e normalmente chamado Makefile . A Makefile contém basicamente um resumo do projecto, das dependências e das instruções de compilação para gerar um ou vários produtos finais. Mais concrectamente, a Makefile especifica um conjunto de regras de compilação em termos de alvos (targets) — como sejam os executáveis finais pretendidos — e das suas dependências — como sejam as fontes ou ficheiros de objectos — de acordo com o formato seguinte: alvo: depend^ encias comando Para cada alvo, o make verifica o tempo da última alteração de todas as suas dependências para deteminar quais delas foram alteradas e, consequentemente, necessitam de ser recompiladas usando o comando. É muito importante reter que as linhas onde se encontra(m) o(s) comando devem ser indentadas com um único TAB, sem espaços. O make da GNU contém um conjunto implı́cito de regras que simplificam muito a construção de Makefile ’s. Estas especificam, por exemplo, que ficheiros .o são obtidos de ficheiros .c através de compilação; e que um executável se cria linkando os ficheiros .o. Estas regras implı́citas estão definidas em termos de variáveis, tais como CC (o compilador de C ), ou CFLAGS (as opções de compilação a passar ao compilador). Estas e quaisquer outras variáveis podem ser definidas usando instruções do tipo VARIAVEL=VALOR em qualquer ponto da Makefile . Vejamos um exemplo do make em acção retomando o nosso exemplo do programa Hello World repartido em ficheiros separados, tal como discutido na secção 2. Antes de mais nada criamos a Makefile seguinte usando um editor de texto convencional: 27 4. Criação e gestão de uma Makefile 2 4 # F i l e ’ Makefile ’ # Exemplo de uma m a k e f i l e p a r a o ’ H e l l o World ’ # D e f i n a m o s que o n o s s o c o m p i l a d o r e ’ o g c c : CC=g c c 6 8 10 # D e f i n a m o s q u a i s a s o p c o e s a p a s s a r ao g c c CFLAGS=−W a l l # Definamos o t a r g e t para c o m p i l a r e g e r a r o e x e c u t a v e l main : main . o h e l l o f n . o 12 14 # O t a r g e t ’ clea n ’ s e r v e apenas para apagar os ∗ . o clean : rm −f main . o h e l l o f n . o Listagem 4.1: Makefile para o projecto Hello World (Makefile). Esta Makefile lê-se do seguinte modo: 1. Usando o compilador de C gcc , e considerando a opção de compilação -Wall, contrua-se o alvo main (um executável) a partir dos ficheiros de objectos main.o e hello fn.o. 2. Este últimos, por sua vez, são gerados a partir de main.c e de hello fn.c, respectivamente. 3. O alvo clean não depende de nada e simplesmente remove os ficheiros de objectos produzidos durante a compilação. No passo 1 temos as regras e definições explı́citamente criadas pelo programador, enquanto que em 1, confiámos nas regras implı́citas do make , não sendo necessário especificar nenhuma regra. Para usar esta Makefile basta lançar o comando make na shell, na directoria onde a Makefile reside. Quando chamado sem argumentos, o make executa o primeiro alvo que encontrar: neste caso o alvo main: $ make gcc -Wall -c -o main.o main.c gcc -Wall -c -o hello_fn.o hello_fn.c gcc main.o hello_fn.o -o main $ ./main Hello, world! Como output, o make devolve a sequência dos comandos que ele próprio está a executar. E vemos claramente que são os comandos que nós executarı́amos se fossemos compilar este projecto à mão, ficheiro a ficheiro, como o fizémos anteriormente. No final o nosso executável main está pronto e só foi preciso digitar make na linha de comandos! Vejamos o que acontece quando editamos o main.c (Listagem 2.1), alteramos a mensagem a imprimir, e corremos o make : 28 $ emacs main.c & $ make gcc -Wall -c -o main.o main.c gcc main.o hello_fn.o -o main $ ./main Hello, everyone! Vemos aqui que o make só recompilou o main.c (a única parte que necessitava de nova compilação) e linkou o main.o gerado com o hello fn.o que já tinha sido compilado acima. Tudo isto sem que tivesse sido necessário dizer nada além de, simplesmente, ’make ’. Tendo o programa pronto, já nos podemos libertar dos ficheiros de objectos usando o segundo alvo da Makefile . Mas para usar qualquer alvo que não seja o primeiro, é necessário especificá-lo como argumento passado ao make , isto é, fazendo: $ make clean rm -f main.o hello_fn.o Em geral, uma Makefile mais sofisticada terá vários alvos e intrincadas dependências, bem como definições de regras, etc. que saem do contexto destas notas. Remetemos os detalhes mais avançados sobre o make e sobre Makefile ’s para as referências. O make é um programa bastante poderoso e altamente personalizável de acordo com as nossas necessidades. Convém não ficar com a ideia de que só é útil em contextos de programação porque, na verdade, pode aplicar-se nos mais variados contextos que requeiram este tipo de tarefas: criar um produto final usando um dado comando, produto esse que depende de determinados ficheiros, os quais terão eventualmente mais dependências encadeadas. Por exemplo, é comum usar-se uma Makefile para criar documentos de texto em LATEX. 29 4. Criação e gestão de uma Makefile 30 Parte II. Interface C -FORTRAN : As Bibliotecas SLATEC e LAPACK por J. Lopes dos Santos 31 4.1. Rotinas em Fortran a partir de C Resumo Nesta parte descreve-se o modo como rotinas escritas em FORTRAN podem ser usadas por programadores em C . O objectivo é facilitar a utilização de rotinas existentes, já que as bibliotecas cientı́ficas são quase todas escritas em FORTRAN . Os exemplos apresentados usam rotinas da cml (Common mathematical library) do SLATEC (Sandia, Los Alamos, Air Force Weapons Laboratory, Tecnhical Expert Committee). Os compiladores em que os exemplos foram testados são os compiladores da GNU , gcc e g77, a correr em máquinas linux. O material deste artigo é baseado no site de Bertrand Laubsh da Universidade do Oregon. 4.1. Rotinas em Fortran a partir de C Há três aspectos a considerar para usar rotinas escritas em FORTRAN a partir de C. • Convenção de nomes • Chamada por referência • Compilação 4.1.1. Convenção de nomes A maior parte dos compiladores de FORTRAN, junta um “undescore” , , aos nomes das funções ou subrotinas. Os compiladores de C, em geral, não. Assim acontece com os compiladores gcc e g77. Uma rotina com o nome TWICE em FORTRAN será referida num programa em C como twice 4.1.2. Chamada por referência (pointers) Em FORTRAN a passagem de argumentos para uma subrotina ou função é sempre feita por referência e não por valor como em C. Assim num programa em fortran a chamada TWICE(A) passa à subrotina TWICE o endereço da variável A. Se nesta subrotina existir a instrução 33 A= 2*A a variável A terá o seu valor alterado no programa de chamada. Isto significa que no programa em C a função twice deve ter como argumento um apontador (pointer) para a variável A. A declaração de twice seria então double twice (double *); e um fragmento de código possı́vel seria: double twice (double *); double b; double a=3.0; b = twice (&a); Eis um exemplo completo de uma função em FORTRAN e de um programa em C que a chama, adapatados do site acima referido: 1 3 5 7 9 11 C∗∗∗ F i l e : twice . f C F u n c t i o n to be c a l l e d by a C program C DOUBLE PRECISION FUNCTION TWICE(X) C∗∗∗ Comment C∗∗∗ Return 2∗ argument DOUBLE PRECISION X , Y Y=2.0 TWICE=Y∗X RETURN END C THE END Listagem 4.2: Exemplo de código FORTRAN (twice.f). 1 3 5 7 9 11 13 /∗ ∗ file : c a l l t w i c e . c T h i s program c a l l s t w i c e ( x ) ∗ Notes : ∗ − g c c n e e d s t h e u n d e r s c o r e appended t o f u n c i o n names . ∗ − F o r t r a n ALWAYS P a s s e s r e f e r e n c e s , n o t v a l u e s . So a r g u m e n t s i n f u n c − ∗ t i o n c a l l h a v e t o be p o i n t e r s . ∗ ∗ −− V i t o r M. P e r e i r a ∗/ #i n c l u d e <math . h> double t w i c e ( double ∗ ) ; 34 4.2. Exemplos de utilização de rotinas SLATEC 15 /∗ argument must be p o i n t e r ∗/ 23 i n t main ( ) { double x , y ; x =2.0; y=t w i c e (&x ) ; p r i n t f ( ”Two t i m e s two i s %g . \ n” , y ) ; return 0; } 25 /∗ end o f c a l l t w i c e ∗/ 17 19 21 Listagem 4.3: Exemplo de um programa em C que chama uma rotina de FORTRAN (calltwice.c). 4.1.3. Compilação O problema essencial da compilação é a inclusão correcta das bibliotecas usadas pelos programas. No caso acima indicado há duas possibilidades: • Compilar files nomef .f com g77 e compilar e “linkar” os ficheiros nomec .c com nomef .o e a biblioteca matemática do C com gcc; • Compilar files nomec .c com gcc e compilar e “linkar” os ficheiros nomef .f com nomec .o com o g77. No caso da secção anterior qualquer dos seguintes procedimentos funciona: $ gcc -c calltwice.c $ g77 calltwice.o twice.f Ou, $ g77 twice.f $ gcc twice.o calltwice.c -lm 4.2. Exemplos de utilização de rotinas SLATEC 4.2.1. Apontadores para funções Nesta secção mostramos um exemplo completo de um programa em C que usa a rotina SLATEC, dgaus8, que calcula um integral num intervalo finito por um algoritmo adaptativo de GaussLegendre com 8 pontos. 35 As rotinas de integração numérica disponı́veis nas bibliotecas numéricas usam, quase sempre, métodos de quadratura gaussiana. Estes métodos são baseados numa aproximação do tipo Z a b dxw(x)f (x) ≈ X f (xk )wk k em que as abcissas, xk , e os pesos, wk , são calculados a partir de polinómios, Pk (x), que satisfazem uma relação de ortogonalidade com a função de peso w(x) Z a b dxw(x)Pk (x)Pk′ (x) = 0 k 6= k ′ . Para escrever de raiz um programa deste tipo é necessário dispor de (ou calcular) uma tabela de abcissas e pesos para os vários tipos de funções de peso, w(x). É necessário gerar os polinómios; encontrar as raı́zes de Pn (x),que são as abcissas de uma regra de integração com n nodos; calcular os pesos. É ainda necessário um controlo de erros, através de processos recursivos de sub-divisão do intervalo. Os conhecimentos técnicos de análise numérica para estas tarefas são apreciáveis. Por isso, o recurso a bibliotecas numéricas, como a SLATEC, pode ser precioso. Mesmo assim, estas rotinas exigem do utente um conhecimento dos métodos mais detalhado do que os pacotes de software de alto nı́vel como o Maple ou o Mathematica. Estes tendem a esconder todos os detalhes de implementação do utente. São de uso mais fácil, mas, em contrapartida, são mais lentos e tornam mais difı́cil o controlo de erros. Vejamos primeiro a documentação desta rotina. As funções slatec são documentadas integralmente no próprio código fonte. Reproduz-se aqui o prólogo de dgaus8.f 2 4 6 8 10 12 14 16 18 20 22 24 ∗DECK DGAUS8 SUBROUTINE DGAUS8 (FUN , A , B , ERR, ANS , IERR ) C∗∗∗ BEGIN PROLOGUE DGAUS8 C∗∗∗PURPOSE I n t e g r a t e a r e a l f u n c t i o n o f one v a r i a b l e o v e r a f i n i t e C i n t e r v a l u s i n g an a d a p t i v e 8− p o i n t L e g e n d r e −Gauss C algorithm . Intended p r i m a r i l y f o r high accuracy C i n t e g r a t i o n o r i n t e g r a t i o n o f smooth f u n c t i o n s . C∗∗∗ LIBRARY SLATEC C∗∗∗CATEGORY H2A1A1 C∗∗∗TYPE DOUBLE PRECISION (GAUS8−S , DGAUS8−D) C∗∗∗KEYWORDS ADAPTIVE QUADRATURE, AUTOMATIC INTEGRATOR , C GAUSS QUADRATURE, NUMERICAL INTEGRATION C∗∗∗AUTHOR Jones , R . E . , (SNLA) C∗∗∗ DESCRIPTION C C A b s t r a c t ∗∗∗ a DOUBLE PRECISION r o u t i n e ∗∗∗ C DGAUS8 i n t e g r a t e s r e a l f u n c t i o n s o f one v a r i a b l e o v e r f i n i t e C i n t e r v a l s u s i n g an a d a p t i v e 8− p o i n t L e g e n d r e −Gauss a l g o r i t h m . C DGAUS8 i s i n t e n d e d p r i m a r i l y f o r h i g h a c c u r a c y i n t e g r a t i o n C o r i n t e g r a t i o n o f smooth f u n c t i o n s . C C The maximum number o f s i g n i f i c a n t d i g i t s o b t a i n a b l e i n ANS C i s t h e s m a l l e r o f 18 and t h e number o f d i g i t s c a r r i e d i n C double p r e c i s i o n a r i t h m e t i c . C 36 4.2. Exemplos de utilização de rotinas SLATEC 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 C D e s c r i p t i o n o f Arguments C C I n p u t −−∗ FUN , A , B , ERR a r e DOUBLE PRECISION ∗ C FUN − name o f e x t e r n a l f u n c t i o n to be i n t e g r a t e d . T h i s name C must be i n an EXTERNAL s t a t e m e n t i n t h e c a l l i n g program . C FUN must be a DOUBLE PRECISION f u n c t i o n o f one DOUBLE C PRECISION argument . The v a l u e o f t h e argument to FUN C i s t h e v a r i a b l e o f i n t e g r a t i o n w h i c h r a n g e s from A to B . C A − lower l i m i t of i n t e g r a t i o n C B − u p p e r l i m i t o f i n t e g r a t i o n ( may be l e s s t h a n A) C ERR − i s a r e q u e s t e d p s e u d o r e l a t i v e e r r o r t o l e r a n c e . Normally C p i c k a v a l u e o f ABS(ERR) s o t h a t DTOL . LT . ABS(ERR) . LE . C 1 . 0 D−3 where DTOL i s t h e l a r g e r o f 1 . 0 D−18 and t h e C double p r e c i s i o n u n i t r o u n d o f f D1MACH ( 4 ) . ANS w i l l C n o r m a l l y h a v e no more e r r o r t h a n ABS(ERR) t i m e s t h e C i n t e g r a l o f t h e a b s o l u t e v a l u e o f FUN(X ) . Usually , C s m a l l e r v a l u e s o f ERR y i e l d more a c c u r a c y and r e q u i r e C more f u n c t i o n e v a l u a t i o n s . C C A n e g a t i v e v a l u e f o r ERR c a u s e s an e s t i m a t e o f t h e C a b s o l u t e e r r o r i n ANS to be r e t u r n e d i n ERR . Note t h a t C ERR must be a v a r i a b l e ( n o t a c o n s t a n t ) i n t h i s c a s e . C Note a l s o t h a t t h e u s e r must r e s e t t h e v a l u e o f ERR C b e f o r e making any more c a l l s t h a t us e t h e v a r i a b l e ERR . C C Output−−∗ ERR, ANS a r e double p r e c i s i o n ∗ C ERR − w i l l be an e s t i m a t e o f t h e a b s o l u t e e r r o r i n ANS i f t h e C i n p u t v a l u e o f ERR was n e g a t i v e . (ERR i s unchanged i f C t h e i n p u t v a l u e o f ERR was non−n e g a t i v e . ) The e s t i m a t e d C e r r o r i s s o l e l y f o r i n f o r m a t i o n to t h e u s e r and s h o u l d C n o t be u s e d a s a c o r r e c t i o n to t h e computed i n t e g r a l . C ANS − computed v a l u e o f i n t e g r a l C IERR− a s t a t u s c o d e C −−Normal c o d e s C 1 ANS most l i k e l y meets r e q u e s t e d e r r o r t o l e r a n c e , C o r A=B . C −1 A and B a r e t o o n e a r l y e q u a l to a l l o w n o r m a l C i n t e g r a t i o n . ANS i s s e t to z e r o . C −−Abnormal c o d e C 2 ANS p r o b a b l y d o e s n o t meet r e q u e s t e d e r r o r t o l e r a n c e . C C∗∗∗REFERENCES (NONE) C∗∗∗ROUTINES CALLED D1MACH, I1MACH , XERMSG C∗∗∗ REVISION HISTORY (YYMMDD) C 810223 DATE WRITTEN C 890531 Changed a l l s p e c i f i c i n t r i n s i c s to g e n e r i c . (WRB) C 890911 Removed u n n e c e s s a r y i n t r i n s i c s . (WRB) C 890911 REVISION DATE from V e r s i o n 3 . 2 C 891214 P r o l o g u e c o n v e r t e d to V e r s i o n 4 . 0 format . (BAB) C 900315 CALLs to XERROR changed to CALLs to XERMSG . (THJ) C 900326 Removed d u p l i c a t e i n f o r m a t i o n from DESCRIPTION s e c t i o n . C (WRB) C∗∗∗END PROLOGUE DGAUS8 37 Listagem 4.4: Prólogo da rotina DGAUS8 do SLATEC (dgaus8.f). Se o pacote slatec4linux.tgz (ver apêndice 6) estiver correctamente instalado, este prólogo pode ser visto usando o comando man do unix, man dgaus8. A biblioteca SLATEC tem em qpdoc uma introdução a várias rotinas de integração. Para a ver basta executar o comando man qpdoc. Olhemos agora para um programa em C que chama esta rotina: /∗ Example o f c a l l o f a s l a t e c r o u t i n e t h a t t a k e s a f u n c t i o n name a s argument J .M. B . L o p e s d o s S a n t o s F i s i c a C o m p u t a c i o n a l Maio 2001 2 4 6 8 ∗/ #i n c l u d e < s t d i o . h> #i n c l u d e <math . h> 10 14 t y p e d e f double d d f o r t r a n ( double ∗ ) ; /∗ d d f o r t r a n d e c l a r e s a f u n c t i o n r e tur ni ng double taking a p oin t er to double a s argument ∗/ 16 /∗ Here i s t h e s l a t e c r o u t i n e . 18 v o i d d g a u s 8 ( d d f o r t r a n ∗ , double ∗ , double ∗ , double ∗ , double ∗ , i n t ∗ ) ; 12 20 22 24 26 28 30 32 main ( ) { d d f o r t r a n ∗ pfunc ; /∗ p f u n c i s a p o i n t e r t o a d d f o r t r a n f u n c t i o n ∗/ double f u n c ( double ∗ ) ; /∗ i n t e g r a n d ∗/ double a = 0 . 0 ; /∗ i n t e g r a l l i m i t s ∗/ double b = 1 . 0 ; double e r r o =1.E−7; /∗ t o l e r a t e d e r r o r ∗/ double i n t e g ; /∗ t o s t o r e i n t e g r a l v a l u e ∗/ int I e rr ; /∗ f l a g f o r e r r o r c o n d i t i o n ∗/ p f u n c =&f u n c ; /∗ p f u n c p o i n t s t o f u n c ∗/ d g a u s 8 ( p f u n c , &a , &b , &e r r o , &i n t e g , & I e r r ) ; /∗ t h e s l a t e c r o u t i n e ∗/ p r i n t f ( ”%e \ t %e \n” , i n t e g , e r r o ) ; } 38 double f u n c ( double ∗ x ) { double y = ∗ x ; return y∗y ; } 40 /∗ 34 36 Check d g a u s 8 d o c u m e n t a t i o n ∗/ /∗ t h e i n t e g r a n d ∗/ End d g a u s 8 . c To c o m p i l e s i m p l y do : g c c −c dgaus8 . c 42 38 4.3. Documentação da Biblioteca SLATEC g77 d g a u s 8 . o − l s l a t e c −l l a p a c k 44 ∗/ Listagem 4.5: Exemplo de programa em C que chama a rotina DGAUS8 do SLATEC (calldgaus8.c). A função dgaus8 tem como primeiro argumento o nome de uma função. Em C teremos que passar um apontador para uma função. A declaração typedef double ddfortran(double *); define um novo tipo, ddfortran, que corresponde a uma função de dupla precisão com argumento que é um apontador para variável double. A instrução ddfortran *pfunc; define pfunc como apontador para uma função e com pfunc =&func; pfunc fica a apontar para a função func. pfunc é o primeiro argumento de dgaus8. Os restantes argumentos são apontadores para diferentes variáveis, tal como foi descrito no exemplo anterior. Para compilar este exemplo podemos compilar o programa em C $ gcc -c dgaus8.c e depois “linká-lo” com a biblioteca SLATEC (que pode necessitar da LAPACK). $ g77 dgaus8.o -lslatec -llapack Eventualmente, podemos ter que indicar o PATH da biblioteca libslatec. Por exemplo, se estiver instalada em /usr/local/lib $ g77 dgaus8.o -L/usr/local/lib -lslatec -llapack 4.3. Documentação da Biblioteca SLATEC A instalação da biblioteca SLATEC exige dois pacotes, slatec4linux.tgz e slatec src.tgz, ambos disponı́veis no site da netlib [1]. As instruções para instalação estão no apêndice 6. A documentação fica instalada em man pages. O comando 39 $ man nome de rotina mostra a documentação da rotina cujo nome for o indicado. No site netlib existe um ficheiro toc com uma listagem das mais de 1400 rotinas deta biblioteca. Estando online, um recurso extremamente útil encontra-se em http://gams.nist.gov um ı́ndice e repositório de software matemático e estatı́stico, com excelentes facilidades de pesquisa e óptima documentação. Aı́ podemos encontar informação sobre bibliotecas de distribuição gratuita e comerciais para virtualmente todos os problemas de cálculo cientı́fico. 4.4. Endereços úteis [1] http://www.netlib.org Repositório de software para cálculo cientı́fico. [2] http://gams.nist.gov indı́ce cruzado e repositório de software matemático e estatı́stico. Excelente facilidades de procura. Estruturado com a classificação gams (Guide to availbale mathematical software). [3] http://www.physics.orst.edu/∼bertrand/C slatec html/begin.html Um site com um tutorial de utilização da biblioteca slatec a partir de C. 40 Parte III. A Biblioteca gsl por Eduardo Castro e Vitor M. Pereira (em Construção) 41 5. Tópicos preliminares 5.1. O que é a gsl A GNU Scientific Library (gsl ) reune um conjunto de rotinas para cálculo numérico — a maior parte delas bastante testadas ao longo de muitos anos — que foram re-escritas de raı́z em C , e com especial preocupação na interface entre a biblioteca e o programa/programador, garantindo assim a sua utilização não só em ambiente C , mas em muitas outras linguagens de alto nı́vel (o Python, por exemplo). A biblioteca abrange um vasto número de tópicos em análise numérica, tendo disponı́veis rotinas nas seguintes àreas: Complex Numbers Roots of Polynomials Special Functions Vectors and Matrices Permutations Combinations Sorting BLAS Support Linear Algebra CBLAS Library Fast Fourier Transforms Eigensystems Random Numbers Quadrature Random Distributions Quasi-Random Sequences Histograms Statistics Monte Carlo Integration N-Tuples Differential Equations Simulated Annealing Numerical Differentiation Interpolation Series Acceleration Chebyshev Approximations Root-Finding Discrete Hankel Transforms Least-Squares Fitting Minimization IEEE Floating-Point Physical Constants Wavelets O manual da gsl (info gsl) está organizado justamente de acordo com cada um destes temas contendo vários exemplos de utilização. 5.2. Utilização básica Começemos por um exemplo bastante simples. Uma das secções da gsl fornece vérias ferramentas relativas a funções especiais. Consideremos o código seguinte que calcula a função de Bessel J0 (x) para x = 5: 2 /∗ ∗ F i l e ’ g s l −b e s s e l . c ’ ∗ D emonstracao de u t i l i z a c a o da g s l 43 5. Tópicos preliminares 4 6 8 ∗ ∗ −− V i t o r M. P e r e i r a ∗/ #i n c l u d e < s t d i o . h> #i n c l u d e < g s l / g s l s f b e s s e l . h> 10 12 14 16 i n t main { double double printf return } ( void ) x = 5.0; y = gsl sf bessel J0 (x ); ( ” J0(%g ) = %.18 e \n” , x , y ) ; 0; Listagem 5.1: Utilização básica da gsl (gsl-bessel.c) A instrução #include <gsl/gsl sf bessel.h> trata de carregar o header da gsl onde as funções de Bessel estão declaradas. Neste #include foi passado, não só o nome do header, como também uma parte do seu caminho. Isto acontece porque, no meu sistema, os headers da gsl encontram-se todos no directório /usr/include/gsl/ e, logo, como o gcc em princı́pio só procura headers em /usr/include/, é necessário dar o resto do caminho. A função gsl sf bessel J0() é uma das funções oferecidas pela gsl , e o seu protótipo é simplesmente1 double gsl_sf_bessel_J0 (double X) Para compilar este programa fazemos: $ gcc -Wall gsl-bessel.c -lgsl -lgslcblas -o gsl-bessel (5.1) Esta instrução compila o nosso programa gsl-bessel.c e procede à linkagem com a gsl . Como já sabemos desde a secção 3, a opção -lgsl pede ao compilador que faça o link com uma biblioteca chamada libgsl.a (ou libgsl.so). Numa instalação convencional, ela estará em /usr/lib/. Caso contrário, pode ser necessário acrescentar a opção -Lcaminho com o caminho para onde esta biblioteca estiver instalada. Mas vemos que existe outra biblioteca, chamada libgslblas.a, a ser linkada com o nosso programa. Bom, na verdade não é com o nosso programa mas sim com a gsl . Ou seja, a libgsl.a depende desta outra biblioteca gslcblas que proporciona um conjunto de rotinas de álgebra linear usadas pela gsl 2 , daı́ que esta tenha de entrar também na fase do link. Este nosso programa devolve então o resultado $ ./gsl-bessel J0(5) = -1.775967713143382920e-01 (5.2) (5.3) , para esta função particular. 1 Consulte-se o manual da gsl em info gsl ’Special Functions’ ’Bessel Functions’ ’Regular Cylindrical Bessel Functions’. 2 Basicamente, a gslcblas é a conhecida BLAS re-escrita para a gsl 44 5.3. Geradores de Números Aleatórios 5.3. Geradores de Números Aleatórios A gsl fornece uma extensa colecção de geradores de números pseudo-aleatórios os quais podem ser acedidos de uma forma universal, incluindo a escolha do gerador durante o tempo de execução, o que permite mudar de gerador facilmente sem ter de recompilar o programa. As funções nessárias para este recurso são definidas no header gsl rng.h. Esta generalidade tem um preço que, neste caso, é o processo de inicialização e definição do gerador de números aleatórios. Mais do que nos outros, neste caso é melhor ver um exemplo: 1 3 5 /∗ ∗ F i l e ’ g s l −random . c ’ ∗ Exemplo de u t i l i z a c a o de g e r a d o r de numeros a l e a t o r i o s e n t r e [ 0 , 1 [ ∗ ∗ −− V i t o r M. P e r e i r a ∗/ 7 9 11 13 #i n c l u d e < s t d i o . h> #i n c l u d e < g s l / g s l r n g . h> i n t main ( v o i d ) { c o n s t g s l r n g t y p e ∗T ; gsl rng ∗r ; // Tipo de g e r a d o r // P o i n t e r p a r a novo g e r a d o r 15 17 int i , n = 10; double u ; 21 // Le a s v a r i a v e i s de a m b i e n t e GSL RNG TYPE e GSL RNG SEED , s e // d e f i n i d a s , e u s a o s s e u s v a l o r e s p a r a d e f i n i r o g e r a d o r e // s e m e n t e a u s a r : 23 gsl rng env setup (); 19 25 T = gsl rng default ; r = g s l r n g a l l o c (T ) ; // D e f i n e o t i p o de g e r a d o r a u s a r // A l o c a memoria p a r a o g e r a d o r 27 // I m p r i m e o t i p o de g e r a d o r u s a d o : 29 p r i n t f ( ” E i s 10 numeros g e r a d o s p e l o g e r a d o r ’% s ’ \ n” , g s l r n g n a m e ( r ) ) ; 31 // C a l c u l a e i m p r i m e 10 numeros a l e a t o r i o s : 33 35 f o r ( i = 0 ; i < n ; i ++) { u = gsl rng uniform ( r ); 37 // D e v o l v e numero a l e a t o r i o e n t r e // [ 0 , 1 [ u s a n d o o g e r a d o r ’ r ’ p r i n t f ( ” %.5 f \n” , u ) ; 39 } 41 // L i b e r t a a memoria a s s o c i a d a ao g e r a d o r 43 gsl rng free ( r ); 45 5. Tópicos preliminares return 0; 45 } Listagem 5.2: Números aleatórios com a gsl (gsl-random.c) Este código pode ser compilado com $ gcc -Wall gsl-random.c -lgsl -lgslcblas -o gsl-random (5.4) e executado, obtendo-se: $ ./gsl-random Eis 10 numeros gerados pelo gerador ’mt19937’ 0.99974 0.16291 0.28262 0.94720 0.23166 0.48497 0.95748 0.74431 0.54004 0.73995 Esta sequência de números foi gerada pelo gerador gsl rng mt19937, aquele que é escolhido por omissão quando se definiu T = gsl rng default;. Claro que poderı́amos ter usado um outro qualquer, entre os muitos que a gsl disponibiliza. Por exemplo, se quiséssemos usar o gerador ranlux bastaria, em vez de T = gsl rng default;, definir T = gsl rng ranlux; no código fonte. Mas a gsl faz melhor do que isso! Ao usar T = gsl rng default; podemos escolher o gerador durante a execução, sem alterar o código fonte ne recompilá-lo. Isso é feito por intermédio de uma variável de ambiente chamada GSL RNG TYPE que, pode ser definida pelo utilizador antes de executar o programa. Esta variável deve conter o gerador de números aleatórios a usar. Por exemplo o comando seguinte faz isso: $ GSL_RNG_TYPE="ranlux" ./gsl-random Eis 10 numeros gerados pelo gerador ’ranlux’ 0.53982 0.76155 0.06030 0.79600 0.30631 0.08278 0.66542 0.46075 0.92574 0.61915 46 5.4. Funções como argumentos Vemos que o gerador mudou (e, logo, muda a sequência também), sem ser necessário tocar no código fonte do programa. Do mesmo modo, é possı́vel escolher a semente durante o tempo de execução através da variável de ambiente GSL RNG SEED. Por exemplo: $ GSL_RNG_SEED=12345 ./gsl-random Eis 10 numeros gerados pelo gerador ’mt19937’ 0.92962 0.89015 0.31638 0.13071 0.18392 0.03976 0.20456 0.82644 0.56773 0.53208 gera uma sequência obviamente diferente da gerada acima com o mt19937. 5.4. Funções como argumentos No uso de bibliotecas numéricas cujas rotinas realizam tarefas como procurar um mı́nimo, determinar uma raı́z ou calcular um integral é frequente receberem como argumentos funções. Vejamos um exemplo. A seguinte função tem dois argumentos. O segundo é uma variável double. O primeiro é uma função de argumento double que devolve double também. double iterate function(double func(double), double x) { return func(func(x)); } A sua declaração poderia tomar a seguinte forma: double iterate function(double func(double), double x); A seguinte chamada devolveria o valor f(f(x)) iterate function(f, x) Em C o nome de uma função é, de facto, um apontador para a função. Isso significa que a definição de iterate function também poderia ser feita como se segue: 47 5. Tópicos preliminares double iterate function(double (*func)(double), double x) { return (*func)((*func)(x)); } uma vez que (*func) é a própria função para onde aponta o apontador func. Os parêntesis são necessários pois enquanto double (*func)(double x); declara uma função double de argumento double, a declaração double *func(double x); afirma que func é uma função de argumento double que devolve um apontador para double. A declaração de iterate function pode ser feita omitindo o nome do parâmetro formal, como fizemos para o segundo argumento double iterate funcion(double (*)(double), double); A chamada da função pode tomar as duas formas iterate function(f,x); /* forma 1*/ iterate function (&f,x); /* forma 2 */ 5.5. Funções de número de argumentos variável O mecanismo do parágrafo anterior tem uma defeito grave. Suponhamos que queriamos iterar uma função com dois argumentos: double f(double x, double y); Neste caso a função que definimos iterate function não pode ser usada pois o seu primeiro argumento não é compatı́vel com esta declaração. Os autores da biblioteca GSL resolvem este problema recorrendo a uma definição um pouco mais complicada das funções, mas que permite usar as rotinas, sem alterações, para funções com qualquer número de argumentos. Esta definição usa o conceito de estrutura, struct, e se o leitor não está familiarizado com o seu uso aconselha-se a leitura do capı́tulo 9 de [7]. 48 5.5. Funções de número de argumentos variável Este conceito permite construir tipos derivados de dados mais complexos que os nativos do C, e constitui uma ferramenta essencial da linguagem em qualquer tarefa menos trivial de programação. Quando realizamos uma tarefa como procurar uma raı́z ou integrar a função singularizamos um dos seus argumentos e tratamos os outros como parâmetros. Na biblioteca GSL usamos uma struct (estrutura) com dois elementos. O primeiro elemento desta estrutura é a função, que tem dois argumentos: double (*function)(double x, void *params) Note-se que o segundo argumento desta função é um apontador para void. Na definição concreta de uma função este apontador apontará para a lista de parâmetros. Por isso o seu tipo é void, o que permitirá sem conflitos fazer um cast (conversão explı́cita3 ) e apontá-lo para qualquer tipo de variável. O segundo elemento da estrutura é precisamente este apontador, ao qual teremos que aceder. Assim a construção do tipo gsl function fica: struct gsl function struct{ double (*function)(double x, void *params); void *params; }; typedef struct gsl function struct gsl function; O uso de typedef permite simplificar declarações posteriores. Por exemplo: gsl function F, *FF; declara F como sendo uma estrutura acima descrita e FF um apontador para uma tal estrutura. Vejamos agora como podemos definir uma instância de uma função gsl function. Eis o código para definir uma função de uma variável e dois parâmetros: /* Template para par de parametros */ struct pair{ double a1, a2; }; /* Funcao de uma variavel, com dois parametros Note-se o template generico usado em gsl function */ double f(double x, void *params); 3 Se i é uma variável do tipo int, então (double) i converte o valor de i fazendo com que a expressão seja do tipo double. No entanto a variável i permanece inalterada. 49 5. Tópicos preliminares double f(double x, void * params) { struct pair *p = (struct pair *) params; /* lado direita ha um cast */ double a = (p -> a1); double b = (p -> a2); return a*exp(-b*x); } Escolhemos agrupar os dois parâmetros num par. A declaração da função segue o formato usado em gsl function. Só assim poderemos usar esta função como um dos membros da estrutura (struct) que é uma gsl function. Na primeira linha do código da função é inicializado o apontador p como apontador para pair, e passa a apontar para a mesma variável que o argumento da função, params. Como este é um apontador para void é feito um cast. Vemos aqui a razão de usar um apontador para void em gsl funcion. Qualquer cast é legı́timo. As linhas seguintes inicializam os dois parâmetros temporários com os valores para que aponta o argumento params. Uma chamada a esta função poderia ter a forma: double x = 1.; /* Decalracao e inicializacao da estrutura pd */ struct pair pd = {0.5, 1.0}; void *pp = &pd; f1 = f(x, pp); 5.6. A função gsl function Tendo visto como podemos incluir numa única declaração funções com número arbitrário de parâmetros vejamos agora como inicializar uma gsl function. /* F e uma gsl function e FF um apontador para uma gsl function */ gsl function F, *FF; /* f e pd definidos previamente */ F.function = &f; F.params = &pd; /* FF aponta para F. E mais eficiente passar apontadores do que estruturas complexas */ FF = &F; Como calcular o valor de uma gsl function? Eis dois processos equivalentes, um usando F e o outro FF. Recorde-se que os membros de uma struct gsl function são function e params. 50 5.6. A função gsl function f2 = (F.function)(x, (F.params)); f3 = (FF -> function)(x, (FF -> params)); A biblioteca GSL define uma macro com dois argumentos GSL FN EVAL(F,x) em que F é um apontador para uma gsl function e x o valor da variável onde a função deve ser calculada. A macro é: #define GSL FN EVAL(F,x) (*((F) -> fuction))(x,(F) -> params) Note-se que esta última expressão é perfeitamente equivalente à usada na atribuição de f3. 51 5. Tópicos preliminares 52 6. Exemplos 6.1. Minimização de Funções Para ilustrar o uso desta biblioteca vamos agora apresentar um exemplo completo. Primeiro o problema. 6.1.1. Funcional de energia livre Na aproximação de campo médio o estudo de um ferromagneto resume-se à determinação dos mı́nimos de um funcional de energia livre relativamente ao parâmetro de ordem m, a magnetização [8]. Além de m, o funcional depende da temperatura (T ) e do campo (h). Tem a forma, em unidades apropriadas, 1 f (m, T, h) = − m2 − hm − T s(m) (6.1) 2 com a entropia s(m) dada por 1−m 1+m 1+m 1−m − s(m) = − log log 2 2 2 2 Eis o código destas funções. Note-se que usamos a forma da declaração das funções gsl para o funcional de campo médio: 73 75 77 79 81 83 double e n t r o p y ( double m) { i f ( f a b s (m) == 1 . ) return 0 . ; else r e t u r n −0.5∗(1.+m) ∗ l o g ( 0 . 5 ∗ ( 1 . +m)) −0.5∗(1. −m) ∗ l o g ( 0 . 5 ∗ ( 1 . −m) ) ; } double f m e a n f i e l d ( double m, v o i d ∗ params ) { double temp , f i e l d ; s t r u c t p a i r ∗p = ( s t r u c t p a i r ∗ ) params ; 85 87 temp = ( p −> temp ) ; f i e l d = ( p −> f i e l d ) ; r e t u r n −0.5∗m∗m − f i e l d ∗m − temp ∗ e n t r o p y (m) ; 53 6. Exemplos 0.8 T 2.0 1.8 1.6 1.4 1.2 1.0 0.8 0.6 0.4 0.2 0.0 0.6 f(m) 0.4 0.2 0 -0.2 -0.4 -1 0 -0.5 1 0.5 m Figura 6.1.: Funcional de energia livre em função da magnetização, para diferentes temperaturas em campo nulo (h = 0). 89 } Listagem 6.1: Definição da entropia (minimization.c). e energia livre para uma dada magnetização As funções de minimização da biblioteca usarão como argumento um apontador para uma gsl function, aqui designada por F: gsl function F; F.function = &f mean field; F.params = &pd; Antes de prosseguir com a construção do programa para minimizar o funcional de energia livre convém ter uma imagem do seu comportamento. Consideremos o caso h = 0. Na fig. 6.1 está representado o funcional de energia livre, subtraı́do do seu valor a magnetização nula f (m, T, 0) − f (0, T, 0), em função de m para vários valores de T . 6.1.2. Inicialização 1 #i n c l u d e < s t d i o . h> #i n c l u d e < s t d l i b . h> 54 6.1. Minimização de Funções 3 5 7 9 11 13 #i n c l u d e <math . h> #i n c l u d e < g s l / g s l e r r n o . h> #i n c l u d e < g s l / g s l m i n . h> #d e f i n e MAX ITER 10000 #d e f i n e TMIN 0 . 1 #d e f i n e TMAX 1 . 5 struct pair { double temp , f i e l d ; }; 15 17 19 21 23 25 27 29 double e n t r o p y ( double m) ; double f m e a n f i e l d ( double m, v o i d ∗ params ) ; i n t main ( ) { int i , i t e r = 0 , status ; c o n s t g s l m i n f m i n i m i z e r t y p e ∗T ; gsl min fminimizer ∗s ; double m = 0 . , temp ; double a = −1. , b = 1 . ; s t r u c t p a i r pd ; gsl function F; F . f u n c t i o n = &f m e a n f i e l d ; F . params = &pd ; 31 33 T = gsl min fminimizer brent ; s = g s l m i n f m i n i m i z e r a l l o c (T ) ; 35 pd . f i e l d = 0 . ; 37 39 41 43 45 47 49 51 53 55 f o r ( i = 0 ; i <= 1 0 0 ; ++i ) { temp = TMIN + i ∗ (TMAX − TMIN ) / 1 0 0 . ; i f ( temp >= 1 . ) m = 0.; else m = 1 . − 1 . e −8; a = −1.; b = 1.; pd . temp = temp ; g s l m i n f m i n i m i z e r s e t ( s , &F , m, a , b ) ; do { ++i t e r ; status = gsl min fminimizer iterate ( s ); m = gsl min fminimizer x minimum ( s ); a = gsl min fminimizer x lower ( s ); b = gsl min fminimizer x upper ( s ); 55 6. Exemplos 1.0 0.8 m(T) 0.6 0.4 0.2 0.0 0.0 1.0 0.5 1.5 T Figura 6.2.: Magnetização m do sistema em função da temperatura T , em campo nulo (h = 0), obtida por minimização do funcional de energia livre f (m, T ) usando as funções de minimização da biblioteca GSL. s t a t u s = g s l m i n t e s t i n t e r v a l ( a , b , 1 . e −4 , 0 . 0 ) ; } w h i l e ( s t a t u s == GSL CONTINUE && i t e r < MAX ITER ) ; // p r i n t f (”%d\n ” , i t e r ) ; i f ( i t e r == MAX ITER ) { p r i n t f ( ” F o i a t i n g i d o o numero maximo de i t e r a c o e s \n” ) ; exit (1); } p r i n t f ( ”%f \ t%f \n” , pd . temp , m) ; 57 59 61 63 65 } gsl min fminimizer free (s ); 67 69 return 0; 71 } Listagem 6.2: Programa principal (minimization.c). 56 Parte IV. Outros Tópicos por Vı́tor M. Pereira 57 7. Argumentos ao main 7.1. argv e argc Em computação é muito frequente a situação em que onde a execução de um programa depende de determinados parâmetros que deverão ser passados, por exemplo, pelo utilizador. Um exemplo clássico é quando se espera que o programa escreva algo para determinado ficheiro, cujo nome deve ser definido durante a execução. Uma das formas de implementar esse tipo de necessidade consiste em usar os recursos de leitura do C , como sejam, as funções scanf(), getchar(), etc. Uma outra forma de o fazer de modo bastante mais prático é passar esses parâmetros como argumentos ao main()1 . Claro que, como o main() é chamado pelo sistema operativo, a única forma de lhe passar argumentos é através do sistema operativo, ou, na prática, da shell. Passar um argumento ao main() de um programa cujo executável se chama prog é tão simples como $ ./prog arg1 arg2 arg3 (7.1) . No gcc , por exemplo, todas as opções de compilação e ficheiros de input são argumentos que a shell passa ao main() do gcc . Para usar esta funcionalidade, necessário declarar o main() com uma instrução do tipo: int main(int argc, char *argv[]); , em vez do habitual int main(void); . Deste modo, ficamos com o main() a admitir dois argumentos: argc (argument count ) é um inteiro contendo o número de argumentos passados ao programa; argv (argument vector ) é um array de apontadores para string, contendo todos os argumentos pela mesma ordem que foram passados. Estes argumentos são automaticamente inicializados pelo sistema de modo a que, quando o programa arranca se verificam as seguintes condições: • argc é igual ou maior a um; 1 Apesar de ter um estatuto especial dentro do programa, o main é uma função de C , equivalente a qualquer outra. Em particular, também admite argumentos – um número varável deles, mais ou menos como o printf(). 59 7. Argumentos ao main • argv[argc] é o apontador NULL; • argv[0] a argv[argc-1] são apontadores para strings; • argv[0] é uma string contendo o nome do executável do programa. O primeiro elemento de argv é, portanto, especial e, além disso, argc vai ser sempre igual ao número de argumentos passados ao programa, mais um. Vejamos um exemplo de um programa cuja única tarefa é escrever todos os argumentos passados ao main(): 2 4 6 8 /∗ ∗ F i l e ’ show args . c ’ ∗ I m p r i m e t o d o s o s a r g u m e n t o s p a s s a d o s na l i n h a de comando ∗ ∗ −− V i t o r M. P e r e i r a ∗/ #i n c l u d e < s t d i o . h> #i n c l u d e < s t d l i b . h> 10 12 14 16 i n t main ( i n t a r g c , c h a r ∗∗ a r g v ) { int i ; f o r ( i =0; i <a r g c ; ++i ) p r i n t f ( ” a r g v [%d ] = %s \n” , i , a r g v [ i ] ) ; e x i t ( EXIT SUCCESS ) ; } Listagem 7.1: Argumentos ao main() (show args.c). Este programa funciona de uma forma muito simples: $ ./show_args primeiro 20 terceiro 4 penultimo 6 argv[0] = ./show_args argv[1] = primeiro argv[2] = 20 argv[3] = terceiro argv[4] = 4 argv[5] = penultimo argv[6] = 6 , e lá está: o argv[0] contém o nome do nosso executável, e os restantes elementos os argumentos propriamente ditos. É importante sublinhar que os argumentos são guardados em argv[] como strings. Isto significa que, se quisermos usar o seu valor numérico, não é possı́vel fazê-lo directamente, sendo necessário recorrer às funções de conversão de string para inteiro (atoi()), double (atof()), etc2 . Um 2 Para 60 mais detalhes destas e outras funções relevantes neste contexto consultar o manual de stdlib.h e string.h 7.2. Variáveis de ambiente exemplo de uma aplicação que usa os valores numéricos dos argumentos é dado pelo problema 4 da folha de problemas “Exercı́cios de linguagem C ”: 1 3 5 /∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ F i s i c a Computacional E x e r c i c i o s de Linguagem C V i t o r M. P e r e i r a ( Fev 2 0 0 6 ) ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗/ 7 /∗ 4 . Imprima a soma de d o i s numeros p a s s a d o s na l i n h a de comandos . Exemplo : . / soma 2 3 2+3=5 9 11 13 15 17 19 21 ∗/ #i n c l u d e < s t d i o . h> #i n c l u d e < s t d l i b . h> #i n c l u d e < a s s e r t . h> i n t main ( i n t a r g c , c h a r ∗ a r g v [ ] ) { int i i ; double sum = 0 . 0 ; 23 /∗ So v a l e a pena p r o s s e g u i r s e h o u v e r p e l o menos 2 t e r m o s : ∗/ 25 a s s e r t ( argc > 2); 27 p r i n t f ( ” \ n Here ’ s y o u r r e s u l t : \ n\n” ) ; 29 f o r ( i i = 1 ; i i < a r g c ; ++ i i ) { sum += a t o f ( a r g v [ i i ] ) ; p r i n t f ( ” %g +” , a t o f ( a r g v [ i i ] ) ) ; } 31 33 p r i n t f ( ” \ b\b = %g \n” , sum ) ; return 0; 35 37 } Listagem 7.2: Problema 4 da folha “Exercı́cios de linguagem C ” (show args.c). 7.2. Variáveis de ambiente Finalmente, existe também com frequência a necessidade de comunicar com um programa de forma semi-permantente, sem ter o utilizador de estar sempre a definir o input. Claro que se pode sempre criar um ficheiro de configuração que será lida pelo programa, e esse é o modo mais 61 7. Argumentos ao main adequado de proceder sempre que a quantidade de informação a passar ao programa é grande. Para situações intermédias, as variáveis de ambiente proporcionam mais uma solução prática. As variáves de ambiente são constantemente usadas por várias aplicações para acederem, por exemplo, ao directório actual, ou ao login do utilizador. A utilização desta funcionalidade é totalmente análoga à discutida acima para o caso de argc e argv[], sendo que neste caso, a declaração do main() deverá ser algo do tipo: int main (int argc, char *argv[], char *envp[]) , onde o terceiro argumento, envp[], é um array de apontadores para string. Cada elemento conterá a string de uma dada variável de ambiente, na forma NOME=Valor. Esta forma é útil3 quando não conhecemos as variáveis de ambiente disponı́veis. Mas se, pelo contrário, quisermos aceder, definir ou alterar o valor de uma variável em particular, então o modo mais expedito consiste em usar as funções getenv(), putenv(), setenv(), etc. proporcionadas pelo stdlib.h. 2 4 6 8 /∗ ∗ F ile ’ environ . c ’ ∗ D emonstracao da u t i l i z a c a o de v a r i a v e i s de a m b i e n t e ∗ ∗ −− V i t o r M. P e r e i r a ∗/ #i n c l u d e < s t d i o . h> #i n c l u d e < s t d l i b . h> 10 12 i n t main ( i n t a r g c , c h a r ∗ a r g v [ ] , c h a r ∗ envp [ ] ) { c h a r ∗ u s e r , ∗home , ∗ h o s t ; 14 p r i n t f ( ” T e r c e i r a v a r i a v e l de a m b i e n t e : %s \n\n” , envp [ 3 ] ) ; 16 home = g e t e n v ( ”HOME” ) ; h o s t = g e t e n v ( ”HOSTNAME” ) ; u s e r = g e t e n v ( ”USER” ) ; 18 20 p r i n t f ( ” Ola %s , b e n v i d o a maquina %s . \ n” , u s e r , h o s t ) ; p r i n t f ( ”A s u a home e s t a em %s . \ n” , home ) ; 22 return 0; 24 } Listagem 7.3: Exemplo com variáveis de ambiente (environ.c). A listagem acima mostra um exmplo de aplicação que recorre inicialmente ao vector de variáveis de ambiente, imprimindo uma delas (a terceira, neste caso), e depois, acede ao conteúdo de outras 3 variáveis especı́ficas. No meu sistema, este programa devolve o seguinte: $ ./environ 3 Esta utilização do main() com mais de dois argumentos não é standard. Trata-se de uma extensão do gcc . No entanto, o uso de getenv(), etc. descrito abaixo é standard. 62 7.2. Variáveis de ambiente Terceira variavel de ambiente: HOSTNAME=nome.fc.up.pt Ola vpereira, benvido a maquina nome.fc.up.pt. A sua home esta em /home/vpereira. 63 7. Argumentos ao main 64 A. Lista de Comandos Lista e descrição sumária dos programas mais relevante referidos neste documento. A mior parte deles constituem os chamados GNU binnary utils (binutils). Informação detalhada pode ser sempre obtida nas páginas do manual correspondente, ou através do info binutils. Abaixo deixamos apenas a descrição tal como devolvida pelo whatis. ar as g77 gcc ldd ld make nm wc Create and maintain library archives The portable GNU assembler GNU project Fortran 77 compiler GNU project C and C++ compiler Print shared library dependencies The GNU linker GNU make utility to maintain groups of programs List symbols from object files Print the number of newlines, words, and bytes in files 65 A. Lista de Comandos 66 Bibliografia [1] Peter Prinz and Ulla Kirch-Prinz, C Pocket Reference, O’Reilly, 2002. [2] Brian J. Gough, An Introduction to GCC - for the GNU compilers gcc and g++, Network Theory Ltd, 2004. [3] Richard Stalman, The GNU make Manual (info make), Version 3.80, 2002. [4] The GNU C Manual (info gcc), Version 4.1.0, 2006. [5] The GNU Scientific Library Reference Manual (info gsl), Version 1.7, 2006. [6] The GNU binary utilities (info binutils), Version 2.16.91.0.6, 2006. [7] Al Kelley and Ira Pohl, A Book on C: programming in C, Addison-Wesley, 4th ed., 2001. [8] P. M. Chaikin and T. C. Lubensky, Principles of condensed matter physics, Cambridge University Press, 1995. 67