Você está na página 1de 14

Sistemas Operativos

Criação de um Projecto no Visual Studio Code e Linguagem C

1. Criação de um projeto C no IDE Visual Studio Code


Na sequência do que foi falado a respeito de ambientes de desenvolvimento utilizaremos nesta
disciplina o Visual Studio Code. Vamos então começar por criar uma pasta onde ficarão os
ficheiros desta aula. Para tal é necessário correr os seguintes comandos num terminal no Ubuntu.
O “mkdir aula3” cria uma pasta com o nome “aula3”, “cd aula” para entrar na pasta “aula3”, e
finalmente “code .” para abrir a pasta atual no VScode.

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 1


O VScode utiliza extensões para fornecer funcionalidades adicionais de suporte ao
desenvolvimento. No nosso caso, vamos querer instalar as extensões “C/C++ Extension Pack”,
“C/C++ Project Generator”, e “Makefile Tools”, como na figura seguinte.

No próximo passo vamos criar um projeto C que servirá de base para esta aula. Queremos utilizar
a extensão que instalámos, para tal precisamos de abrir a “Command Pallete” do VScode,
clicando na opção visível na imagem.

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 2


De seguida abre-se uma caixa de diálogo no topo da janela do VScode. Esta “Command Palette”
é utilizada para procurar e executar funções do VScode, incluindo as extensões instaladas. Para
criar um projeto C devem procurar por “C project” e selecionar a opção demarcada na imagem
seguinte “Create C project”.

Em seguida abre um explorador de ficheiros na pasta “aula3”. Devem clicar no “open” sem
navegar para outra pasta, de forma a criar o projeto na pasta atual. Devem agora ter um conjunto
de pastas na vossa diretoria, com o seguinte aspecto. Utilizem as setas horizontais para expandir
as pastas e abrir o ficheiro “main.c”.

2. Ambiente de Desenvolvimento em C usando linha de comandos


Para escrever programas e os executar, é necessário utilizar um conjunto de ferramentas que no
seu conjunto constituem um ambiente de desenvolvimento.
O ambiente de desenvolvimento C em Linux pode ser extremamente simples, sendo no mínimo
constituído pelas aplicações que a seguir se descrevem.

Editor de Texto
É a aplicação que irá permitir a escrita do programa. Inúmeros editores de texto estão disponíveis
em Linux, desde os mais simples (e.g., vim, nano) até aos mais complexos (e.g., emacs, visual
studio code) passando pelos mais clássicos (e.g., gedit), que fazem parte das aplicações
disponíveis na barra de comandos do ambiente gráfico.

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 3


No nosso caso, já estamos a utilizar o Visual Studio Code para assegurar a edição dos ficheiros.

Compilador C
Uma vez criado o programa em C, será necessário transformá-lo num programa executável. Para
que isto seja possível é utilizado o compilador C (gcc) que irá realizar uma série de operações
sobre o programa (englobando o pré-processamento, a verificação do código, a compilação, a
assemblagem e a edição de ligações), para finalmente consigamos obter a imagem executável.
Para realizar esta operação deverá ser executado o comando (por exemplo, na pasta do projeto
que criaram anteriormente):
$ gcc main.c

Se não for especificado o nome do ficheiro executável, este recebe o nome por defeito que é
a.out. Para definir o nome do programa executável utiliza-se a opção:
$ gcc main.c -o main

É também aconselhado utilizar a opção -Wall que, durante o processo de compilação, irá mostrar
warnings relativos a eventuais problemas que possam existir no código. De salientar que mesmo
que estes warnings sejam lançados, a não ser que exista algum erro, que o código irá ser
compilado na mesma. Estes avisos servem para dirigir o programador a determinadas partes do
código que eventualmente poderiam ser escritas de uma forma mais correta ou poderem
eventualmente gerar resultados que não são os esperados.
$ gcc main.c -o main -Wall

Execução e Depuração
A execução de um programa é realizada através da invocação do seu nome na linha de
comandos. Assim para executar o programa acima criado:
$ ./main

A utilização do “./” em frente do nome é necessária para que o bash procure o nome na pasta em
que foi criado o executável.
No caso do programa apresentar erros na sua execução que sejam difíceis de encontrar, pode-se
recorrer ao GNU Debugger, uma aplicação que permite realizar a execução do programa de forma
controlada.
Para tal é necessário compilar o programa com a opção -g de forma a que o executável guarde
informação adicional sobre o programa (tabela de símbolos, dentre outros):
$ gcc main.c -g -o main

O debugger é de seguida invocado através do comando:


$ gdb main

Desta forma, o debugger toma o controlo da sessão, ou seja, os comandos inseridos serão agora
por ele interpretados, e não pela bash. Assim, a prompt que aparece agora é o seguinte:
(gdb)

As opções desta ferramenta são múltiplas pelo que convém consultar o manual de utilização,
através da invocação do comando man gdb (ou consultando o manual online).

As opções mais utilizadas são as seguintes:


▪ l: para listar o código fonte do ficheiro
▪ b: para colocar um breakpoint (ponto de paragem)

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 4


▪ r: para lançar a execução do programa
▪ s: para executar o programa passo a passo

Exemplo:
(gdb) l
1 main()
2 {
3 printf("Hello\n");
4 }
(gdb) b 3
Breakpoint 1 at 0x804837c: file hello.c, line 3.
(gdb) r
Starting program: /home/aluno/a123456/hello
Breakpoint 1, main () at hello.c:3
3 printf("Hello\n");
(gdb) s
Hello
4 }

Como se pode ver no exemplo a listagem, inserção de um breakpoint, execução do programa que
pára no breakpoint inserido, e a execução passo a passo da instrução printf. O programa
poderia ser assim executado passo a passo até se descobrir a eventual causa de erro.

Utilização de bibliotecas de funções


Um programa pode utilizar funções definidas externamente tanto noutros ficheiros-fonte como em
módulos objeto já previamente compilados. Vamos agora ver como tratar cada um destes casos.
Ligação de múltiplos ficheiros-fonte
Se na geração de um executável quisermos incluir um segundo ficheiro-fonte, que contém uma
biblioteca com funções de suporte, então é preciso adicioná-lo à lista da seguinte forma:
gcc nome_ficheiro.c nome_biblioteca.c -o nome_executavel

Ligação com bibliotecas em módulos-objeto


Se pretendermos utilizar funções de bibliotecas previamente compiladas, então devemos compilar
o ficheiro-fonte e ligar o módulo-objeto resultante com o módulo-objeto da biblioteca da seguinte
forma:
gcc nome_ficheiro.c -c
gcc nome_ficheiro.o nome_biblioteca.o -o nome_executavel

Ligação com bibliotecas integradas


Existem várias bibliotecas com funções muito úteis que estão habitualmente disponíveis num
ambiente de desenvolvimento em Linux (contidas no diretório /usr/lib32). As que iremos utilizar
são as seguintes:
● libc – contém as funções especificadas no padrão ANSI/ISO C (incluída por omissão)
● libm – contém as funções matemáticas (incluída com -lm)
● librt – biblioteca com extensões de tempo-real (incluída com -lrt)
● libpthread – biblioteca de threads POSIX (incluída com -lpthread)

A biblioteca libc é incluída automaticamente pelo GCC na produção de um executável. Esta


inclui múltiplos conjuntos de funções tais como as definidas nos ficheiros cabeçalho:
● stdio.h

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 5


● stdlib.h
● string.h
● …

As funções printf e scanf têm a sua implementação na biblioteca padrão de C (libc) e podem
ser utilizadas simplesmente através de incluir o ficheiro cabeçalho stdio.h. Já a utilização de
funções de outras bibliotecas, como por exemplo a libm, requerem a indicação ao GCC da
biblioteca a incluir através do parâmetro -l logo seguido da designação da biblioteca. Este
parâmetro pode surgir múltiplas vezes na linha de comando. Um exemplo de utilização de uma
função externa definida numa biblioteca que não é incluída automaticamente é a função que
calcula a potência de um valor:

float powf(float base, float exponent);

O ficheiro fonte que utilizar esta função deve incluir no início o ficheiro math.h (que define apenas
a interface da função). O comando GCC para geração do executável deverá ser:

gcc nome_ficheiro.c -o nome_executavel -lm

Makefiles
Um makefile é o ficheiro que automatiza a compilação de um projecto. Este permite não só a
compilação, mas também a limpeza dos ficheiros binários criados e a sua instalação no sistema
operativo. Permite ainda a criação de variáveis globais que serão tidas em conta na execução do
makefile. A compilação em C é realizada da seguinte forma

gcc main.c –o main

O ficheiro Makefile segue a seguinte sintaxe:


target: dependencies
[tab] shell command

o que neste caso se traduziria, no mínimo, num Makefile com o seguinte aspeto:
main: main.c
gcc main.c –o main

no nosso caso, quando criámos o projeto no Visual Studio Code já foi criado um Makefile.
Portanto para utilizarmos o Makefile utilizamos o comando make, indicando o target que queremos
compilar, no mesmo caminho (path) onde estão os ficheiros C e o Makefile:
make main

Para visualizar as opções adicionais que estão disponíveis basta digitar man make.

Múltiplas opções
Podemos criar um Makefile com múltiplas opções, tais as que se ilustram de seguida:

all: file file2

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 6


file:
gcc file.c –o file
file2:
gcc file2.c –o file2
clean:
rm –f file file2

De salientar que quando se executa o make sem fornecer nenhum parâmetro, que ele executará
por defeito a opção all, existindo também a opção clean que removerá os ficheiros binários
criados.
Variáveis e comentários
No make existe também a possibilidade de definirmos variáveis e comentários conforme o
exemplo que se segue:
#Um comentário
#CC, CFLAGS and EXECS são variáveis
CC = gcc
CFLAGS = -Wall
EXECS = file file2
all: $(EXECS)
file:
$(CC) $(CFLAGS) file.c –o file
file2:
$(CC) $(CFLAGS) file2.c –o file2
clean:
rm –f $(EXECS)

De notar que é também comum a utilização da variável LDFLAGS com opções para serem
passadas ao linking.
Compilação em múltiplas partes
Neste exemplo que se ilustra de seguida a compilação é realizada primeiro para objectos (*.o) e
só depois é criado o executável.
all: executable

executable: source.o main.o


gcc source.o main.o -o executable

source.o: source.c source.h


gcc -c source.c -o source.o

main.o: main.c
gcc -c main.c -o main.o

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 7


3. Compilação, Execução e Depuração no Visual Studio Code
Com a utilização de um IDE, temos as tarefas que vimos anteriormente facilitadas.
Para compilar o nosso ficheiro devemos navegar para a opção de “Makefile”, selecionar a
definição de “Launch target:[unset]”, o que irá abrir a Command Palette com uma única sugestão
“/home/ubuntu/aula3/output>main()”, como na figura seguinte.

Para compilar o nosso programa clicamos no botão de Build, assinalado na figura seguinte.

Será aberto um terminal onde é corrido o comando “make” para compilar o nosso projeto. O
output deve ter o seguinte aspecto.

Como o compilador “gcc” foi invocado com a flag “-Wall”, recebemos dois avisos devido a
parâmetros de entrada na nossa função “main” que não foram utilizados. No entanto, como não
são erros, o binário foi compilado corretamente.

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 8


Devem confirmar a criação dos seguintes ficheiros no VScode.

O ficheiro “main.o” contém o código objeto do vosso “main.c”, antes de ser efetuado o “linking”, o
ficheiro “main.d” é um ficheiro auxiliar que lista as dependências necessárias para compilar o
“main.o”. Por último o ficheiro “main” é o binário, ou executável, do vosso projeto. Para correr o
nosso programa clicamos no botão “Run”, como na figura seguinte.

O VScode irá abrir outro terminal e executar o comando para lançar o binário “main” do nosso
projeto. Deverá ter o seguinte output.

O programa apenas escreve no “stdout” o string “Hello World!\n”, como pode ser confirmado no
terminal. Podemos utilizar o gdb dentro do IDE, utilizando a opção de debug da figura seguinte.

Se não colocarmos nenhum “breakpoint”, o programa irá executar normalmente. Podemos


acrescentar um breakpoint clicando atrás do número de linha, na linha onde queremos interromper
a execução, como na figura seguinte.

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 9


Se executarmos o programa em modo Debug desta vez, devemos ver as seguintes opções.

Os botões em cima permitem controlar a execução do código, “step over”, “step into”, “step out”,
“continue”, “restart” e “stop”. à esquerda temos a lista de variáveis dentro do scope atual.
Podemos confirmar que “argc=1”, porque o nosso programa foi chamado sem argumentos, e
“*argv” é um string que começa por “/hom”, com o resto omitido devido ao tamanho da janela. Em
baixo destacamos as seguintes janelas.

O menu da esquerda mostra o “Call Stack”, e permite também navegar para calls superiores ou
inferiores. O menu “breakpoints” mostra todos os breakpoints e permite desativar cada um,
individualmente. No centro temos um terminal de debug que permite executar comandos do gdb.
Por exemplo, se colocarmos “argv[0]”, o terminal vai imprimir o valor desta variável, que podemos
ver é “/home/ubuntu/aula3/output/main”, ou seja, é o caminho do nosso programa como foi
exemplificado pelo script da aula passada.

Exercícios Fundamentais
Num primeiro exercício vamos criar um programa que calcula a dimensão em volume de um
armário, recebendo 3 valores:
● Largura
● Comprimento
● Altura

O programa é composto pelo seguinte código:

#include <stdio.h>
#include <stdlib.h>

int calcularVolume(int in_largura, int in_comprimento, int in_altura) {


return in_largura * in_comprimento * in_altura;
}

int main (void) {

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 10


int largura, comprimento, altura;

puts("Insira a largura do armario:");


scanf(" %d", &largura);

puts("Insira o comprimento do armario:");


scanf(" %d", &comprimento);

puts("Insira a altura do armario:");


scanf(" %d", &altura);

printf("O volume do armario e %d\n", calcularVolume(largura, comprimento,


altura);

return EXIT_SUCCESS;
}

e poderão compilar e executar o mesmo no Visual Studio Code como vimos anteriormente.

Exercício: Calculadora de Lâmpadas


Após terem feito o primeiro exercício convidamos-vos a criarem uma nova pasta e projeto:

Neste projeto devem criar um programa em C para calcular o número de lâmpadas 60 watts
necessárias para uma determinada divisão. O programa deverá ler um conjunto de informações,
tais como: tipo, largura e comprimento da divisão. O programa termina quando o tipo de divisão
for igual a -1 (menos um). A tabela abaixo mostra, para cada tipo de divisão, a quantidade de
watts por metro quadrado.
Tipo de divisão Potência
(watt/m2)
0 12
1 15
2 18
3 20
4 22

Dica: Use uma estrutura struct para agrupar logicamente as informações de uma divisão (int tipo
de divisao, float largura e float comprimento). Recorra a uma função (float CalculaArea) para
calcular a área da divisão. Os atributos de entrada serão a largura e comprimento da divisão.
Utilize uma função (float Lampada) para calcular a quantidade de lâmpadas necessárias para a
divisão. Os atributos de entrada serão o tipo de divisão e a área (em m2) da divisão.
Observações: Utilize a função ceil(numero) em #include <math.h> para realizar o arredondamento
para cima.

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 11


SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 12
Solução:
#include<stdio.h>
#include<math.h>

float CalculaArea(float largura, float comprimento){


return largura * comprimento;
}

float Lampada(int tipo, float area){


float quantidade;
int potencia;
switch(tipo){
case 0:
potencia = 12;
break;
case 1:
potencia = 15;
break;
case 2:
potencia = 18;
break;
case 3:
potencia = 20;
break;
case 4:
potencia = 22;
break;
default:
potencia = 0; // Tipo inválido
}
quantidade = (area * potencia) / 60;
return quantidade;
}

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 13


int main(){

struct {
int tipo;
float larg, comp;
} divisao;
float nlampadas;
float tamanho;
printf("\n\nDigite o tipo de divisao (0 ate 4):");
scanf("%d", &divisao.tipo);
while(divisao.tipo != -1) {
printf("\n\nDigite a largura do divisao:");
scanf("%f", &divisao.larg);
printf("\n\nDigite o comprimento do divisao:");
scanf("%f", &divisao.comp);
tamanho = CalculaArea(divisao.larg, divisao.comp);
nlampadas = Lampada(divisao.tipo, tamanho);
printf("\n\n Para a area da sua divisao precisara de %f
lampadas",ceil(nlampadas));
printf("\n\nDigite o tipo de divisao (0 ate 4):");
scanf("%d", &divisao.tipo);

}
}

SO-2023/2024-Daniel Silveira, João Madeira, João Craveiro 14

Você também pode gostar