Aula 02

Fazer download em pdf ou txt
Fazer download em pdf ou txt
Você está na página 1de 34

IMD0030

LINGUAGEM DE PROGRAMAÇÃO I
Modularização e Compilação
(material baseado nas notas de aula do Prof. Silvio Sampaio e Prof.
César Rennó-Costa)
Compilação
O compilador g++
Introdução
• Antes de escrever e executar programas, é preciso entender em maior detalhe todo o
processo de construção de um programa em C++
Passo 1: Defina o problema a ser
resolvido
Passo 4: Compile os arquivos de Embora costumemos
programa (.h .cpp) compilar e ligar ao mesmo
tempo, é importante
entender estas duas fases
Passo 2: Projete a solução separadamente.

Passo 5: Ligar (usando o linker) os A separação entre


arquivos objeto (.o) estas fases é útil em
projetos complexos e
que envolvam muitos
Passo 3: Escreva um programa arquivos.
que implemente a solução
Passo 6: Teste e depure o seu
programa
Introdução Ligar (linker): g++ -o prog main.o func.o util.o Uma biblioteca (library) é
uma coleção de arquivos
objeto pré-compilados que
Arquivos objeto (.o) permitem a reutilização de
• Compilar X Ligar código entre diferentes
projetos.

Compilar: g++ -c main.cpp func.cpp util.cpp


Runtime
Support
Arquivos fonte (.cpp/.h)
Biblioteca
(.lib, .a, .so)

Compilador Arquivo Executável

Compilar: g++ -c main.cpp func.cpp util.cpp


Arquivo objeto (.o) Ligar: g++ -o prog main.o func.o util.o
Compilar e ligar: g++ -o prog main.cpp func.cpp util.cpp
O compilador
• Um compilador é um programa que, a partir de um código escrito em uma linguagem, o
código fonte, cria um programa semanticamente equivalente porém escrito em outra
linguagem, código objeto
• Um compilador é um dos dois tipos mais gerais de tradutores, sendo que o segundo tipo
que a ele deve ser comparado é um interpretador
o Programas interpretados são geralmente mais lentos do que os compilados, mas são também
geralmente mais flexíveis, já que podem interagir com o ambiente mais facilmente (frequentemente
linguagens interpretadas são chamadas também de script)
― Um interpretador, no momento da execução do programa, traduz cada instrução do programa e a executa em seguida
o C++ é uma linguagem compilada
• Normalmente, o código fonte é escrito em uma linguagem de programação de alto nível,
com grande capacidade de abstração, e o código objeto é escrito em uma linguagem de
baixo nível, como uma sequência de instruções a ser executada pelo processador
Opções do g++
O compilador g++
• O que acontece quando usamos o g++ para criar nosso
programa?
o Ex: g++ main.cpp func.cpp util.cpp
• Resposta: O compilador g++ cria o programa em duas fases
o Fase 1, Compilação: Os arquivos fonte (.cpp) são compilados e main.cpp func.cpp util.cpp
geram os arquivos objeto (.o)
o Fase 2, Ligação (Linking): Os arquivos objeto (.o) são ligados para
criar um arquivo executável (em código de máquina). Nesta fase,
códigos de bibliotecas são também ligados.
main.o func.o util.o
• Dica: Caso não seja indicado o nome do arquivo executável
com o uso da opção -o nome, será criado o arquivo a.out
o O exemplo g++ -o prog main.cpp func.cpp util.cpp cria um
executável de nome prog a.out bibliote
o A ordem da opção não importa. Ex: g++ main.cpp func.cpp ca
util.cpp -o prog tem o mesmo efeito do exemplo anterior.
O compilador g++
• Sintaxe geral: g++ <option flags> <file list>
o Option flags são as opções usadas para alterar o comportamento padrão do compilador
o Por exemplo, a forma mais simples de compilar seus arquivos fonte seria usar o comando g++ *.cpp
― Isso geraria um comportamento padrão do compilador, entre outras coisas, geraria um executável com o nome a.out

• Opções mais comuns para o compilador


o -c : indica ao compilador para compilar os arquivos fonte (.cpp), mas não liga-los
― A separação da compilação e ligação será útil na compilação de projetos usando makefile)
o -o <nome> : especifica o nome do arquivo executável a ser criado a partir dos arquivos objeto (.o) já
pré-compilados
o -g : insere informações de depuração a serem usadas com depuradores compatíveis com o GDB (será
visto mais à frente)
O compilador g++
• Opções mais comuns para o compilador (Cont.)
o -Wall : diz ao compilador para indicar com um aviso (warning) qualquer instrução que possa levar a um
erro
― Por exemplo, ao habilitar esta opção, o compilador irá avisar sobre a instrução: if (var = 5) { .. }

o A opção -Wall é uma combinação de um largo conjunto de opções de verificação do


tipo -W, todas juntas. Tipicamente incluem:
― variáveis declaradas, mas não utilizadas
― variáveis possivelmente não inicializadas quando usadas pela primeira vez
― padronização dos tipos de retorno
― falta de colchetes ou parênteses em certos contextos que tornam uma instrução ambígua, etc.
O compilador g++
• Opções mais comuns para o compilador (Cont.)
o -I<dir> (“i” maiúscula): adiciona o diretório <dir> na lista de diretórios para a busca de arquivos incluídos
(através do uso da diretiva #include), ou seja, indica ao compilador uma fonte extra de arquivos
cabeçalho (.h)
o -L<dir> : adiciona o diretório <dir> na lista de diretórios para a busca de bibliotecas (.a)
o -l<libname> (“L” minúscula) : faz com o que o compilador procure pela biblioteca indicada por libname
no caso de nomes não resolvidos (unresolved names) durante a fase de ligação
o -ansi : garante que o código compilado esteja em conformidade com o padrão ANSI C
o -pedantic : torna a compilação ainda mais exigente no que diz respeito à obediência à padronização
ANSI C

• Para consultar ao conjunto completo de diretivas do compilador GNU gcc/g++, consulte:


o https://gcc.gnu.org/onlinedocs/
Recomendações para a disciplina
• Para os exercícios e projetos, utilizem sempre as diretivas -Wall e tratem todos os
warnings
• Igualmente, recomendamos sempre o uso das diretivas -ansi e -pedantic para garantir
que o seu programa está escrito de acordo com a padronização
• As diretivas -g e -O0 permitem o uso do depurador, em caso de erro
• Com isso, um exemplo de compilação em linha de comando seria:
o g++ -o programa -Wall -ansi -pedantic -O0 -g main.cpp
Formato da mensagem de erro no g++
• Um exemplo de erro comum ao compilar programas com o g++ é o de comentário não
finalizado
o arquivo.cpp:22:1: unterminated comment
• Cada parte da mensagem de erro é separada por “dois-pontos”
o A primeira parte (programa.cpp) é o nome do arquivo fonte no qual o erro ocorreu
o A segunda parte (22) indica o número da linha na qual o erro foi detectado
o A terceira parte (1) indica o número da coluna na qual o erro foi detectado
― Esta é a única parte que não está presente em todas as mensagens de erro
o A quarta parte (unterminated comment) é uma mensagem resumida que descreve
o que provavelmente gerou o erro
Formato da mensagem de erro no g++
• Mensagens de Warning no g++ seguem o mesmo formato básico de um erro, apesar de
que um arquivo fonte apenas com warnings continuará a ser compilado
o Exceto no caso do uso da diretiva -Wall que transforma warnings em erros

• Vale ressaltar que, quando o g++ indica o número da linha, isso não necessariamente
indica a linha do erro, mas sim a linha na qual o erro foi detectado
o Com isso, encontrar o ponto exato do erro pode exigir uma inspeção de outras partes do código
o Geralmente, o erro está próximo à linha no qual foi detectado
Exemplo

g++ -o programa -Wall -ansi -pedantic -O0 -g main.cpp


Exemplo
Relembrando
• Assim como em C, main é a primeira função (em C++ é chamado de método principal) a
ser executada por qualquer programa em C++, mesmo que tenha outras funções escritas
antes dela
• Declaração
o Mais simples:
int main() -- ou int main(void)
o Completa, no caso de o programa receber argumentos via linha de comando:
int main(int argc, char* argv[])
― cin >> minhaVariavel

― minhaVariavel = atoi(argv[1]); // para float atof


Modularização
Dividir para conquistar
Modularização
• Estratégia para a construção de software complexo a partir de pequenas partes distintas,
cada uma contendo responsabilidades específicas
o Dividir para conquistar: dividir uma solução complexa em pequenas tarefas, cada uma sendo
resolvida individualmente
• Módulo: conjunto de instruções em um programa que possui responsabilidade bem
definida e é o mais independente possível em relação ao resto do programa
Por que modularizar?
Facilitar a vida do programador em termos de
• Organização do programa e das instruções que o compõem
o O código torna-se mais fácil de gerenciar
• Leitura do código produzido
o O trabalho em equipe se torna mais fácil
• Futura manutenção do código
o Os módulos podem ser testados individualmente e de forma independente uns dos outros
o Eventuais alterações são feitas em pontos específicos do programa, nos módulos
• Eventual reutilização do código
o Os módulos são independentes uns dos outros, então podem ser reusados de forma mais fácil em diferentes
programas e/ou por outros programadores (inclusive na forma de bibliotecas)
Por que modularizar?
• Código modular permite ser desenvolvido e testado uma só vez, embora possa ser
usado em várias partes de um programa
• Permite a criação de bibliotecas que podem ser usadas em diversos programas e por
diversos programadores
• Permite economizar memória, dado que o módulo utilizado é armazenado uma única
vez ainda que seja utilizado em diferentes partes do programa
• Permite ocultar código, uma vez que apenas a estrutura do código fica disponível para
outros programadores
Tipos de modularização
• Modularização interna
o Divisão do código contido em um arquivo em múltiplas funções
o Cada função executa um conjunto de instruções bem definido, representando uma tarefa específica
dentro do programa
• Modularização externa
o Divisão do programa em múltiplos arquivos, cada um podendo conter um conjunto de funções e
outros elementos (tipos, constantes, variáveis globais, etc.)
o Programas mais complexos em C++ são tipicamente organizados na forma de
arquivos de cabeçalho e arquivos de corpo
Modularização interna
• Um programa não precisa ser composto por uma única, grande função principal (main)
• Estratégia: dividir para conquistar
o Pensar na solução do problema e como ela pode ser dividida em partes menores

o Implementar funções, cada uma realizando uma tarefa específica e bem definida

o Implementar trechos de código que se repetem no programa como corpo de funções, chamadas em
substituição a tais trechos que se encontravam repetidos
Um exemplo
#include <iostream> switch(opcao) {
using std::cin; case 1:
using std::cout; conv = temp * 1.8 + 32;
using std::endl; cout << temp << "ºC = " << conv << "ºF" << endl;
break;
int main() { case 2:
int opcao; conv = (temp - 32) / 1.8;
cout << temp << "ºF = " << conv << "ºC" << endl;
cout << "Conversor de temperatura" << endl; break;
cout << "(1) Celsius -> Fahrenheit" << endl; default:
cout << "(2) Fahrenheit -> Celsius" << endl; cout << "Opcao invalida" << endl;
cout << "Digite sua opcao: "; }
cin >> opcao;
return 0;
float temp; }
cout << "Digite a temperatura: ";
cin >> temp;
float conv;
Modularizando em funções...
#include <iostream>
float temp;
using std::cin;
cout << "Digite a temperatura: ";
using std::cout;
cin >> temp;
using std::endl;
float conv;
float celsius2fahrenheit(float temp) {
switch(opcao) {
return temp * 1.8 + 32;
}
case 1:
conv = celsius2fahrenheit(temp);
float fahrenheit2celsius(float temp) {
cout << temp << "ºC = " << conv << "ºF" << endl;
return (temp - 32) / 1.8;
break;
}
case 2:
conv = fahrenheit2celsius(temp);
int main() {
cout << temp << "ºF = " << conv << "ºC" << endl;
int opcao;
break;
default:
cout << "Conversor de temperatura" << endl;
cout << "(1) Celsius -> Fahrenheit" << endl;
cout << "Opcao invalida" << endl;
cout << "(2) Fahrenheit -> Celsius" << endl; }
cout << "Digite sua opcao: ";
cin >> opcao; return 0;
}
Modularização externa
• Por convenção da linguagem C++, a organização do código de um programa pode ser feita
da seguinte maneira:
o Arquivos de cabeçalho (.h) contêm declarações de estruturas, tipos, variáveis globais, protótipos de
funções, constantes, etc. e não podem conter a função principal do programa (main)
o Arquivos de corpo (.cpp) implementam ou fazem chamadas ao que é definido nos arquivos de
cabeçalho
Voltando ao exemplo anterior...
#ifndef CONV_H
#define CONV_H O arquivo de cabeçalho conv.h
// Conversao de temperatura em escala Celsius para Fahrenheit
contém os protótipos das funções
float celsius2fahrenheit(float temp); que realizam as respectivas
// Conversao de temperatura em escala Fahrenheit para Celsius conversões de temperatura, uma
float fahrenheit2celsius(float temp);
para cada tipo
#endif

conv.h
Arquivos de cabeçalho em C++
• São incluídos no programa através da diretiva de pré-processamento #include seguida do
nome do arquivo de cabeçalho
o A inclusão copia o conteúdo de um arquivo em outro
• A fim de evitar duplicidade de cópias do código a cada nova inclusão, os arquivos de
cabeçalho precisam ser identificados e protegidos contra múltiplas inclusões, pois elas
podem levar a erros de redefinições
o A identificação é feita através da definição de um nome através da diretiva de compilação #define
o A proteção contra múltiplas inclusões é implementada com a definição do bloco #ifndef / #endif
(if not defined / end if) do pré-processador
Pré-processador
• O pré-processador é um programa que examina o código-fonte e executa certas
modificações nele, baseado nas diretivas de compilação
o As diretivas de compilação são comandos que não são compilados, sendo dirigidos ao pré-processador
o O pré-processador é executado pelo compilador antes da compilação propriamente dita
o Diretivas de compilação iniciam por um caractere # (sharp ou hashtag)
• O comando #include é uma diretiva de compilação que diz ao pré-processador para
copiar o conteúdo de um arquivo para outro, através de duas formas:
o Quando usando arquivos das bibliotecas
― #include <iostream> (biblioteca padrão de C++)
o Quando usando arquivos do próprio usuário
― #include "conv.h" (tipos e protótipos de sub-rotinas definidos pelo usuário)
Pré-processador
• O comando #define substitui palavras por valores, atuando como uma constante
o Por exemplo, #define PI 3.14159 permite substituir PI pelo valor 3.14159
• Os comandos #ifdef, #ifndef, #endif permitem evitar que partes do código sejam
inseridas no programa
o Úteis nos arquivos de cabeçalho para evitar duplicidade
o A diretiva de pré-processamento #include não verifica se um arquivo já foi incluído no programa, o
que pode ser feito através da diretiva #ifndef
#ifndef NOME_DO_ARQUIVO_H // Evita a redefinicao dos membros do arquivo
#define NOME_DO_ARQUIVO_H // Inicio da definicao de NOME_DO_ARQUIVO_H

// Codigo do arquivo

#endif // Fim da definicao de NOME_DO_ARQUIVO_H


Pré-processador
• As principais diretivas de compilação são:
o #include (inclusão)
o #define (definição), #undef (remoção de definição)
o #ifdef (if defined), #ifndef (if not defined)
o #if, #else, #elif (if / else / else-if)
o #endif (end if)
• O domínio de diretivas de compilação permite a escrita de um código:
o mais rápido
o mais legível
o mais dinâmico
o mais portável (multiarquitetura)
Arquivos de cabeçalho em C++
• Quando usamos uma biblioteca, não deve ser preciso ler suas milhares de linha de
código para saber o que ela é capaz de fazer
• Solução: os arquivos de cabeçalho devem conter toda a descrição de como usar as
funcionalidades da biblioteca sem se preocupar como elas foram implementadas
o Os arquivos de cabeçalho tornam-se pequenos, simples e explicativos
Arquivos de corpo em C++
• Implementam ou fazem chamadas ao que é definido nos arquivos de cabeçalho
o Mudanças na implementação definida no arquivo de corpo podem ser feitas sem necessariamente
impactar os arquivos de cabeçalho
• Necessário fazer a inclusão do(s) arquivo(s) de cabeçalho por meio da diretiva #include
o #include <iostream> (biblioteca padrão de C++)
o #include "conv.h" (tipos e protótipos de sub-rotinas definidos pelo usuário)
• A inclusão de arquivos de cabeçalho em outros arquivos é efetuada somente uma vez no
programa devido à verificação do identificador feita no próprio arquivo de cabeçalho
• Precisam ser compilados
Voltando ao exemplo anterior...
#include "conv.h"
O arquivo de corpo conv.cpp inclui
// Conversao de temperatura em escala Celsius para Fahrenheit
float celsius2fahrenheit(float temp) {
o arquivo de cabeçalho conv.h e
return temp * 1.8 + 32; contém a implementação das funções
}
que realizam as respectivas
// Conversao de temperatura em escala Fahrenheit para Celsius
float fahrenheit2celsius(float temp) {
conversões de temperatura
return (temp - 32) / 1.8;
}

#include

conv.h conv.cpp
Voltando ao exemplo anterior...
#include <iostream> switch(opcao) {
using std::cin; case 1:
using std::cout; conv = celsius2fahrenheit(temp);
using std::endl; cout << temp << "ºC = " << conv << "ºF" << endl;
break;
#include "conv.h" case 2:
conv = fahrenheit2celsius(temp);
int main() { cout << temp << "ºF = " << conv << "ºC" << endl;
int opcao; break;
default:
cout << "Conversor de temperatura" << endl; cout << "Opcao invalida" << endl;
cout << "(1) Celsius -> Fahrenheit" << endl; } conv.h
cout << "(2) Fahrenheit -> Celsius" << endl;
cout << "Digite sua opcao: "; return 0;
cin >> opcao; }
float temp; #include #include
cout << "Digite a temperatura: ";
cin >> temp;
float conv;
conv.cpp main.cpp

Você também pode gostar