16. Compilação e Organização de Arquivos 16.1 Compilador X Interpretador • Um código fonte pode ser compilado ou interpretado. • Compiladores e interpretadores tratam o código de maneira diferente. • Interpretador: • Lê o código fonte, uma linha por vez, executando a instrução dessa linha. • Deve estar presente toda vez que se quiser executar o programa. Ex.: Basic • Como a cada execução, o código é examinado linha por linha, o processo é mais lento. • Compilador: • Lê o programa inteiro e o converte para código objeto. • Código objeto é um código binário, também conhecido como código de máquina. Fica armazenado em um arquivo e pode ser executado pelo processador. • O código compilado, após ser ligado, pode ser executado diretamente, sem necessitar de nova compilação e sem depender do compilador. 1
ANEXO I 16.1 Compilador X Interpretador
• Um código fonte pode ser compilado ou interpretado. •
Compiladores e interpretadores tratam o código de maneira
diferente.
• Interpretador: • Lê o código fonte, uma linha por vez, executando
a
instrução dessa linha. • Deve estar presente toda vez que se quiser
executar o
programa. Ex.: Basic • Como a cada execução, o código é examinado
linha por
linha, o processo é mais lento.
• Compilador: • Lê o programa inteiro e o converte para código
objeto. • Código objeto é um código binário, também conhecido
como código de máquina. Fica armazenado em um arquivo e pode ser
executado pelo processador.
• O código compilado, após ser ligado, pode ser executado
diretamente, sem necessitar de nova compilação e sem depender do
compilador.
1
• Pré-processamento e ligação-edição são tarefas
executadas por dois programas, o pré-processador e o ligador
respectivamente
• Em geral os compiladores mais novos contêm essas duas ferramentas
acopladas.
1.6.21 - Pré-processamento
O pré-processamento é feito antes da compilação e consiste em: •
Retirada de comentários • Inclusão de arquivos de cabeçalhos
(headers) • Inclusão de macros no meio do código. • Algumas
otimizações:
• Funções pequenas do mesmo arquivo são inseridas no meio do
código, pois a chamada de funções é um processo caro
• Valores constantes calculados dentro de um loop são movidos para
fora.
2
16.2.2 - Compilação
• As instruções são convertidas para código de máquina (código
objeto ; extensão .o)
• É feita a verificação da sintaxe do código, erros de sintaxe são
detectados nessa fase.
• Ligações com outras funções e variáveis externas não é feita. •
As rotinas (bibliotecas) do sistema necessárias para executar
o programa ainda não são incluídas.
• Obs.: Bibliotecas do sistema: • Fornecem suporte em tempo de
execução. • Permitem acesso a serviços do SO, ex.: I/O • Permitem
acesso às rotinas de ponto flutuante.
• Durante a compilação de um programa, ocorrem os eventos de tempo
de compilação. Exemplos: Avisar que uma variável não foi
declarada.
• Código relocável: • Pode ser executado em qualquer região da
memória
disponível. • Contém informações de quantos bytes, a partir do
inicio
do arquivo, vai precisar para executar suas instruções. • A
produção de código relocável facilita o processo de
ligação/edição.
3
16.2.3 - Ligação/edição
• Feita pelo ligador ou ainda linker ou loader. • O ligador é
responsável por ligar (linkar) as várias partes do
código objeto: • códigos objeto gerados pelo programador •
bibliotecas padrão da linguagem • rotinas (bibliotecas) do
sistema.
• Após a ligação/edição é gerado o código executável. • Durante a
ligação/edição são atribuídos os endereços das
instruções de “jump” (desvio) e “call” (chamadas), ou seja,
resolvem-se as referências externas.
Exemplo: arquivo1.c arquivo2.c
int count; /*declaraçoes*/ #include <stdio.h> void
display(void); extern int count; /* decl */ /*programa principal*/
/*função display*/ void main(void) void display(void) { { count =
10; printf(“%d \n”, count); display(); } }
• Na compilação são criados dois códigos objetos: arquivo1.o e
arquivo2.o.
• A localização da função display() no arquivo 1 fica em aberto
após a compilação. Idem para a variável externa count no
arquivo
• Essas localizações (endereços) serão resolvidas durante a fase de
ligação.
4
• O ligador “avisa” se algum símbolo está indefinido. Exemplos: •
Uso de uma função que não foi definida. • Associação errada de
bibliotecas.
• Os arquivos de biblioteca padrão (*.a) contêm um conjunto de
códigos objetos (.o).
• Arquivos objetos são incluídos totalmente em um código
executável.
• Arquivos de biblioteca são parcialmente incluídos. Somente as
partes (*.o) que são utilizadas pelo programa são incluídas.
16.3 - Compilação separada
• Um programa pode estar contido em muitos arquivos.
• Se apenas uma parte do código é alterada, somente o arquivo
correspondente necessita ser recompilado.
• A atualização e manutenção do programa é bem mais fácil e
rápida.
• Porém existe o trabalho inicial de organizar o programa em
diversos arquivos.
5
16.4 - Execução e mapa de memória
• Durante a execução de um programa, ocorrem os eventos de tempo de
execução.
Ex.: erro de divisão por zero, causando a interrupção do
programa.
• Um compilador também oferece rotinas para gerenciar o ambiente
durante o tempo de execução.
• Durante a execução são criadas e utilizadas 4 regiões distintas
na memória com finalidades específicas: • Região de armazenamento
de código. • Região de armazenamento das variáveis globais. •
Pilha:
Armazena informações relacionadas com funções, por exemplo: •
endereço de retorno das chamadas às funções • argumentos de funções
• variáveis locais
• Heap: • Região de alocação dinâmica. • Armazena dados criados em
tempo de execução, ex.: listas
encadeadas, árvores. • A disposição física dessas 4 regiões varia
em função do tipo da CPU e
do compilador.
• Colisão entre pilha e heap: • Funções recursivas são aquelas que
chamam a si próprias. • A cada chamada, um novo conjunto de
variáveis locais é criado e
armazenado na pilha. • Um grande número de chamadas recursivas
podem “estourar” a
pilha e causar o problema de colisão entre pilha e heap. • Isto
geralmente ocorre devido à erros de elaboração do código
fonte.
6
16.5 - Ambientes integrados de programação
• O processo de programar consiste basicamente em: • Criar o
programa fonte através de um editor. • Compilar (pre-processamento,
compilação e ligação/edição). • Fazer depuração através de um
debbuger. • Executar o programa.
• Alguns compiladores, por exemplo: Turbo C e Borland C/C++,
fornecem um ambiente integrado de programação contendo: • editor de
texto • make: programa para organizar a compilação separada
de
arquivos dependentes • compilador • debugger: permite acompanhar a
execução do programa passo a
passo, verificando o valor de variáveis, endereços e
expressões.
• Outros compiladores, por exemplo: gcc e cc do Unix, executam
somente a tarefa principal de compilar
• No entanto existem alguns utilitários destinados a executar as
outras tarefas. Alguns desses utilitários são: • vi e emacs:
editores de texto. • make: funciona de modo semelhante ao make dos
ambientes
integrados • xxgdb, dbxtool: auxiliam a depuração.
7
• Programas muito extensos devem ser colocados em diferentes
arquivos.
• Vantagens: • É mais fácil trabalhar com arquivos pequenos. •
Permite reutilização. • Se um arquivo com código fonte é
modificado, somente
este é recompilado. • Os programadores do Unix utilizam uma maneira
padrão para
particionar o código.
17.1 - Extensões
• Arquivos de programas C podem ter as seguintes extensões: • .c ou
.C ⇒ código C • .cc ⇒ código C++ • .h ou .H ⇒ arquivos de cabeçalho
(headers) em C ou C++
• Arquivos em código objeto têm a extensão .o • Arquivos de
bibliotecas (vários .o) têm extensão .a • Arquivos executáveis têm
extensão .exe (DOS) ou não sem
extensão.
8
17.2 - Arquivos fonte
17.2.1 - Disposição das informações As informações de um arquivo
fonte devem ser dispostas na seguinte ordem:
• Comentário inicial contendo: • descrição e objetivo geral do
arquivo • nome dos autores • data e outras informações
importantes
• Inclusão de headers de bibliotecas na seguinte ordem: •
bibliotecas padrão • bibliotecas criadas pelo programador. •
Exemplo:
#include <stdio.h> #include <mybiblio.h>
• Definição de macros e tipos na seguinte ordem: • macros
constantes (simples) • macros com argumentos • tipos (typedef)
simples • tipos estruturados (typedef struct) • tipos enumerados
(typedef enum)
• Declaração de variáveis na seguinte ordem: • externas • não
estáticas • globais • estáticas globais
• Declaração de funções na seguinte ordem: • ordem alfabética se
tiverem utilidade independente • agrupadas por utilidades
afins
• Definição das funções
17.2.2 - Arquivos header (extensão .h)
• Os arquivos header são destinados a conter: • definições de
macros e tipos, conforme o item 17.2.1 • declaração de variáveis e
funções (protótipos), conforme
o item 17.2.1 • outros arquivos header (“include
#<outros.h>“)
• Arquivos headers são incluídos, exatamente como estão escritos,
no local onde é encontrada a palavra reservada #include ...
• Pode-se fazer a inclusão de um arquivo header em um outro arquivo
de duas formas:
#include <nome.h> nome.h será procurado no diretório padrão
de bibliotecas
ou #include “nome.h”
nome.h será procurado no diretório corrente
• Arquivos header contêm informações para uma finalidade
específica, por exemplo: • <string.h> para manipular strings
• <stdio.h> para manipular E/S • <math.h> para funções
matemáticas
• headers estão associados a uma determinada biblioteca, ou seja,
contêm definições e declarações que serão utilizadas pela
mesma
• Uma biblioteca pode conter mais de um header associado.
10
• Exemplos: • stdio.h, string.h e outros são headers da biblioteca
libc.a • O tipo FILE está definido em stdio.h • As funções de
manipulação de arquivos (e outras) estão
declaradas em stdio.h • O tipo das funções de manipulação de
aquivos que
utilizam o tipo FILE, estão definidas em libs.a
• Podem ser vistos como a interface de uma biblioteca, isto é,
através dos headers pode-se verificar quais são os parâmetros das
funções definidas na biblioteca correspondente.
• Lista de protótipos das funções: • São os tipos dos parâmetros e
do valor devolvido pela
função • O padrão Ansi C, exige que esses tipos estejam
explicitados. • Durante a compilação é feita a verificação de
tipos, ou
seja, é verificado se os parâmetros passados (quando a função é
chamada) correspondem aos argumentos esperados (protótipos).
11
Exemplo: Seja o seguinte programa: #include <stdio.h>
#include <string.h> char s1[ ] = “Alo, “; char s2[ ] = “
estou aqui!”; void main(void) { int p; p = strcat(s1, s2); printf(“
%s \n “, p); }
• No header string.h existe a seguinte declaração para a função
strcat():
char *strcat(char *str1, const char *str2); • char * antes da
função indica o tipo de valor retornado pela
função, ou seja, ponteiro para caracter. • No programa acima, o
valor retornado é atribuído a p, que foi
declarada como int. • O compilador no padrão Ansi C vai acusar um
erro ao fazer a
verificação de tipos, pois p deveria ter sido declarado como char *
ao invés de int.
• Ao se criar headers, deve-se colocar no mesmo definições e
declarações com uma finalidade comum.
• Alguns códigos são dependentes do hardware, como por exemplo,
funções para manipular janelas, etc. Deve-se colocar código
dependente do hardware em arquivos separados do código
independente. Assim facilita a adaptação, só se mexe no arquivo que
contém código dependente.
12
17.2.3 - Arquivos fonte (extensão .c)
• Contém as definições das funções feitas no header correspondente,
bem como utiliza os tipos e variáveis especificados no mesmo.
• Outros tipos, variáveis e funções também podem ser declarados
e/ou definidos nos arquivos .c
• Fontes e headers correspondentes possuem o mesmo nome, muda
apenas a extensão.
Exemplo:
nome.h nome. c
• Um arquivo fonte .c não necessariamente precisa ter um header
associado.
• Um arquivo fonte .c pode incluir vários headers.
13
pilha.h pilha.c
14
char letra; int num; } TipoItem;
typedef struct { TipoItem item; Elemento *prox; } Elemento;
/*declarações de funções */ void empilha( ..........); TipoItem
desempilha(.........); void FPVazia(..........);
#include <pilha.h> void empilha(......) { ....... }
17.2.4 - Arquivos em código objeto
• Os arquivos .c estão associados aos headers correspondentes, logo
tem-se que: arquivo.h + arquivo.c = arquivo.o
• Bibliotecas são arquivos que contêm módulos reutilizáveis
pré-compilados que serão usados por desenvolvedores de
aplicações.
• Bibliotecas: • possuem extensão .a • coleções de códigos objetos
(extensão .o) • Exemplos:
libm.a contém o código objeto das funções declaradas em math.h
libX11.a contém o código objeto das funções declaradas em
X11.h
• Arquivos objetos: incluídos totalmente em um código executável. •
As funções das bibliotecas C padrão estão contidas em bibliotecas
(se
fossem em arquivos objetos, os programas seriam enormes).
• Elas podem ser : estática (static) ou compartilhada (shared). •
Bibliotecas estáticas fazem parte do corpo do programa
principal,
liberando-o assim do ambiente de configuração do sistema. •
Bibliotecas compartilhadas ligam-se ao programa principal
dinamicamente, ou seja, um módulo só será ligado ao programa
principal se for solicitado. Com isso, bibliotecas compartilhadas
consomem menos recursos do sistema operacional além de facilitarem
a substituição de módulos defeituosos sem a necessidade de compilar
novamente todos os outros módulos ou sistemas envolvidos.
• archive (ar): programa que junta vários arquivos objetos (.o) em
uma biblioteca (.a)
15
• Exemplo1: gerando uma biblioteca estática chamada libmods.a que
inclui os dois arquivos objetos modulo1.o e modulo2.o
> gcc -c modulo1.c modulo2.c // compila e gera o arquivo.o
> ar -rcu libmods.a modulo1.o modulo2.o // gerando a biblioteca
> ranlib libmods.a
> gcc -L. seuprog.c -lmods // compilando o programa com a sua
biblitoeca.
Se você deseja disponibilizar a biblioteca para outras pessoas,
incluindo você mesmo, crie um diretório para guardar suas
bibliotecas (por exemplo, /usr/local/lib) e copie a biblioteca
gerada para lá. Para compilar programas use:
> gcc -L/usr/local/lib seuprog.c -lmods
Exemplo 2: gerando uma biblioteca compartilhada "libmods.so"
===================
> gcc -fPIC -c modulo1.c modulo2.c // até aqui a biblioteca é
estática
> gcc -shared -o libmods.so modulo1.o modulo2.o > chmod a+rx
libmods.so
> gcc -L. seuprog.c -lmods // compilando o programa com a sua
biblitoeca.
crie um diretório para guardar suas bibliotecas (por exemplo,
/usr/local/lib) e copie a biblioteca gerada para lá. Para compilar
programas use: gcc -L/usr/local/lib seuprog.c -lmods
16
Veja que o nome da biblioteca se incia com a prefixo lib.
O Linux usa o seguinte padrão para bibliotecas
compartilhadas:
libaaa.so.b.c.ddd
libaaa.so é o nome da biblioteca; b é um número que indicará a
maior versão; c é um número que indicará a menor versão; ddd é um
número que informa a "release".
17
18 - Utilitários do Unix
18.1 Compiladores C • cc ⇒ compila código em C • gcc (Gnu C++)⇒
compila código em C e cm C++
• É melhor usar um compilador para C++ pois a verificação de tipos
é feita com mais rigor, facilitando assim o trabalho do
programador.
• Exemplos de utilização: • gcc nome.c ⇒ gera o arquivo executável
de
nome ‘a.out’ • gcc nome.c -o saida ⇒ gera o arquivo executável
de
nome ‘saida’ • gcc -c nome.c ⇒ gera o arquivo em código
objeto
de nome ‘nome.o’ • gcc -lm ....... ⇒ incluir a biblioteca libm.a
na
compilação
• gcc -Aa (ou -Ansi) ⇒compila em Ansi, isso se o “default” nao for
Ansi.
• Mais detalhes: man gcc, man cc
18
18.2 - Make
• Make é um programa que mantém e atualiza programas e arquivos
dependentes
• Os mesmos comandos utilizados em linha de comando podem ser
utilizados dentro do arquivo de entrada do make
• Vantagem: • Se uma função de um arquivo é modificada, somente
este
arquivo é recompilado. • Funcionamento básico:
• Para cada arquivo é verificado se a data de atualização é
posterior a data de compilação.
• Se for posterior, compila novamente; caso contrário, não.
18.2.1 - Utilização • Para utilizar o make, deve-se construir um
arquivo textual de
controle chamado makefile ou Makefile e a partir da linha de
comando digitar make:
> make
• make procura no diretório corrente o arquivo de nome makefile ou
Makefile e executa ações segundo as instruções desse arquivo.
> make -f Mymakefile • Neste caso o arquivo Mymakefile é
executado e não o
default
19
18.2.2 - Formato do arquivo de controle
• Um arquivo makefile tem as seguintes partes: • macros
(definições): são opcionais, isto é, um arquivo
makefile pode não conter definições • regras: parte obrigatória •
comentários: também são opcionais
• <destino> depende de <dependencias> e é obtido
através de <comandos> é o nome da regra, e é chamado de
target.
• makefile contém a descrição textual de como os <destinos>
são obtidos em função das dependencias.
• As várias dependencias são separadas por um ou mais espaços em
branco ou por <TAB>s
• Os comandos devem estar separados do inicio da linha por um ou
mais <TAB>s (obrigatoriamente)
• Cada linha será executada numa shell independente, linhas
consecutivas devem ser unidas pelo caracter de continuação de linha
\
• O programa make verifica recursivamente todas as dependências
para cada destino, verificando se o mesmo deve ou não ser
atualizado.
20
Definições
Regras
teste.c teste.h
arvore.h arvore.c main.c
arvore: arvore.o main.o gcc -lm -o arvore arvore.o main.o
arvore.o: arvore.c arvore.h gcc -c arvore.c
main.o: main.c arvore.h gcc -c main.c
#include <math.h> #include <arvore.h> etc....
#include<arvore.h>
pilha.c pilha.h lista.c lista.h fila.c fila.h main.c
makefile
• Comentários: # • Exemplos:
# um comentário no inicio da linha XX = 5 # comentário no fim
• Continuação de linha: \ • Exemplos:
22
main.o: main.c pilha.h lista.h fila.h gcc -c main.c
lislig:pilha.o fila.o lista.o main.o gcc pilha.o fila.o lista.o
main.o -o lislig
#include <pilha.h> #include <lista.h> #include
<fila.h> etc....
Exemplo: /* arquivo frase.h colocado em um diretório de include */
#include <stdio.h> int tamanho (char *); void inverte1 (char
*); void inverte2 (char *);
/* arquivo inverte1.c */ void inverte1 (char *frase) { int i; char
* pfrase = frase; while (*pfrase != '\0') putchar (*pfrase++);
putchar ('\n'); while (--pfrase >= frase) putchar (*pfrase);
putchar ('\n'); }
/* arquivo inverte2.c */ #include "include/frase.h" void inverte2
(char *frase) { int i; for (i=0; i<= (tamanho(frase)-1); i++)
putchar (*(frase+i)); putchar ('\n'); for (i=tamanho(frase)-1;
i>=0; i--) putchar (frase[i]); putchar ('\n'); }
23
/* arquivo tamanho.c */ int tamanho (char *str) { int i; for ( i=0;
str[i]!='\0'; i++); return(i+1); }
/* arquivo main.c */ #include "include/frase.h" main () { char
frase[] = "oi, como vai?"; printf ("%d\n", tamanho(frase));
inverte1(frase); inverte2(frase); }
Makefile 1 frase: main.o tamanho.o inverte1.o inverte2.o
gcc -ofrase main.o tamanho.o inverte1.o inverte2.o main.o: main.c
include/frase.h
gcc -c main.c tamanho.o: tamanho.c include/frase.h
gcc -c tamanho.c inverte1.o: inverte1.c include/frase.h
gcc -c inverte1.c inverte2.o: inverte2.c include/frase.h
gcc -c inverte2.c
# o all é geralmente utilizado para marcar a primeira regra
all: inicial frase clean
# no caso de inicial e clean o nome da regra (ou target) # estão
associados a uma acao a ser executada
inicial: @echo "Iniciando"
main.o: main.c include/frase.h gcc -c main.c
tamanho.o: tamanho.c include/frase.h gcc -c tamanho.c
inverte1.o: inverte1.c include/frase.h gcc -c inverte1.c
inverte2.o: inverte2.c include/frase.h gcc -c inverte2.c
#remove arquivos objetos clean:
@echo "Finalizando" @rm -rf *.o #@ faz com que o comando não
apareça
25
• Macros: Servem para definir caminhos, opções de compilação,
DIRETORIO = usr/prof/silvia/ED/arvores LIBD = lib/obj LIBFLAGS =
-lm -lg
Para se utilizar as macros definidas, deve-se colocar o símbolo $
antes.
#Makefile 3
# definindo variaveis # @ faz o comando não aparecer na tela.
CC=@gcc INC=./include/ OBJ=main.o tamanho.o inverte1.o
inverte2.o
all: inicial frase clean
main.o: $(INC)frase.h main.c CC) -c main.c
tamanho.o: $(INC)frase.h tamanho.c $(CC) -c tamanho.c
inverte1.o: $(INC)frase.h inverte1.c $(CC) -c inverte1.c
inverte2.o: $(INC)frase.h inverte2.c $(CC) -c inverte2.c
#remove arquivos objetos clean:
26
• Substituição de sufixos: $(var: string1 = string2) • no valor de
var, todas as ocorrências de string1,
serão substituídas pela string2 • string1 é um sufixo de var. •
Exemplo1:
Sejam as seguintes definições: ARQH = pilha.h lista.h fila.h
e ARQO = $(ARQH:h=o)
• Exemplo2: Sejam as seguintes definições:
XX = a.c b.c c.c e
YY = $(XX:.c=_x.o) então $(YY) equivale a
a_x.o b_x.o c_x.o
#Makefile 4
# definindo variaveis CC=@gcc INC=./include/ SRC=$(wildcard *.c)
#SRC = todos os arquivos .c do diretorio
all: inicial frase clean inicial:
@echo "Iniciando"
#expande SRC e substitui os.c por arquivos de mesmo nome.o frase:
$(SRC:.c=.o)
$(CC) -ofrase $(SRC:.c=.o)
#remove arquivos objetos clean:
28
• Variável dinâmica (internas): # S@ nome da regra corrente # S^
dependência anterior # S< pega o primeiro valor da lista de
dependências anterior. # $? lista de dependências mais recentes que
a regra. # $* nome do arquivo sem sufixo
$@ • Toma o valor do destino (regra) corrente. • Exemplo: Os
seguintes arquivos makefile são
equivalentes:
• Substituição de padrões: $(var: op % os = np % ns)
• no valor de var, todas as ocorrências do prefixo op, serão
substituídas por np
• todas as ocorrências do sufixo os serão substituídas por
ns.
• Exemplo: Sejam as seguintes definições:
XX = xx_a.c xx_b.c xx_c.c e
YY = $(XX:xx_%c = %o) então $(YY) equivale à
a.o b.o c.o
xxx: yyy.c zzz.c gcc -o $@ yyy.c zzz.c
#Makefile 5
# definindo variaveis CC=@gcc INC=./include/ SRC=$(wildcard *.c)
#SRC = todos os arquivos .c do diretorio
all: inicial frase clean inicial:
@echo "Iniciando"
#expande SRC e substitui os.c por arquivos de mesmo nome.o
#variaves internas # S@ nome da regra == frase # S^ dependencia
anterior == $(SRC:.c=.o) # o comando abaixo pode ser então
reescrito # frase: $(SRC:.c=.o) # $(CC) -ofrase $(SRC:.c=.o)
frase: $(SRC:.c=.o) $(CC) -o $@ $^
main.o: $(INC)frase.h main.c $(CC) -c main.c
tamanho.o: $(INC)frase.h tamanho.c $(CC) -c tamanho.c
inverte1.o: $(INC)frase.h inverte1.c $(CC) -c inverte1.c
inverte2.o: $(INC)frase.h inverte2.c $(CC) -c inverte2.c
clean: @echo "Finalizando" @rm -rf *.o
30
#Makefile 6
# definindo variaveis CC=gcc INC=./include/ SRC=$(wildcard *.c)
#SRC = todos os arquivos .c do diretorio
all: inicial frase clean inicial:
@echo "Iniciando" #expande SRC e substitui os.c por arquivos de
mesmo nome.o
#variaves internas # S@ nome da regra == frase # S^ dependencia
anteriror == $(SRC:.c=.o) # S< pega o primeiro valor da lista de
dependencias anterior. # $? Lista de dependências mais recentes que
a regra. # $* Nome do arquivo sem sufixo # o comando abaixo pode
ser então reescrito # frase: $(SRC:.c=.o) # $(CC) -ofrase
$(SRC:.c=.o)
frase: $(SRC:.c=.o) $(CC) -o $@ $^
# % indica um padrão, fazer para todo .o e .c %.o: %.c
$(INC)frase.h
$(CC) -c $< -o $@
31
16.5 - Ambientes integrados de programação
17 - Organização de arquivos no UNIX
17.1 - Extensões
18 - Utilitários do Unix