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