INF1018 - Software Básico

Aquecimento

Nesse laboratório, você vai aprender a compilar programas no ambiente linux/gcc.

  1. Abra um terminal onde você irá interagir com o sistema através de linha de comando.

  2. Na interação por linha de comando, existe o conceito de diretório corrente, que é a pasta de arquivos onde você está "posicionado''. Escreva pwd e dê enter para descobrir quem é o diretório corrente. Use o comando ls para listar o conteúdo desse diretório.

  3. Crie um diretório para seus programas de inf1018. Use o comando mkdir inf1018 para isso. Em seguida, dê cd inf1018 e volte a usar o comando pwd para verificar qual é, agora, o diretório corrente.

  4. Vamos agora trabalhar com o seguinte programa:
    #include <stdio.h>
    #include <stdlib.h>
    
    float foo (float pf) {
      return pf+1;
    }
    
    int main (int argc, char **argv) {
      float f;
      if (argc!=2) {
        printf ("uso do programa: %s <valor float>\n", argv[0]);
        exit(0);
      }
      f = atof(argv[1]);
      printf ("foo(%.2f): %.2f\n", f, foo(f));
      return 0;
    }
    
    1. Compile e execute esse programa. Para compilá-lo, abra um editor de texto (por exemplo, gedit), recorte e cole o código anterior, e salve o programa em um arquivo ex1.c na pasta inf1018. Volte ao terminal e execute o comando abaixo:
      gcc -Wall -o ex1 ex1.c
      Para executar o resultado, use:
      ./ex1 4.0

      Obs: A opção -Wall diz ao gcc para gerar warnings (avisos) e o argumento -o o instrui a colocar o resultado da compilação, o programa executável, no arquivo cujo nome vem a seguir: ex1. Você irá usar isso durante o curso inteiro, então por favor tente entender essa linha de comando.

      Tente executar seu programa com
      ./ex1
      Veja o que acontece. Leia o código e tente entender como o programa funciona. argc e argv são argumentos de main que capturam o que foi digitado na linha de comando. argc é o número de itens digitados e argv um array de strings com cada um dos itens!

      Agora tente executar seu programa com
      ex1
      Veja o que acontece. Por que seu programa não foi encontrado? (caso você não saiba por que, pergunte ao seu professor ou ao monitor...)

    2. Vamos agora dividir nosso programa em dois arquivos. Crie um outro arquivo, labaux.c, e copie para ele a função foo. Substitua essa função, no arquivo ex1.c, por seu cabeçalho:
      float foo (float pf);
      
      e salve esse arquivo como ex2.c. Agora seu programa é composto por 2 arquivos .c. Para criar o executável, executamos o compilador para traduzir cada um deles para um arquivo objeto (um arquivo .o) e depois executamos o ligador para juntar esses dois objetos, criando o arquivo executável.

      Depois de cada passo abaixo, use o comando ls para ver que arquivo foi criado.

      Compilação de labaux.c:

       gcc -Wall -c labaux.c 
      Compilação de ex2.c:
       gcc -Wall -c ex2.c 
      Ligação dos objetos:
       gcc -o ex2 labaux.o ex2.o
      Na realidade, você pode fazer tudo isso de uma vez usando:
      gcc -Wall -o ex2 labaux.c ex2.c
      (avisando ao compilador que o seu programa é composto por esses dois arquivos .c)

      Teste novamente o programa (que agora se chama ex2).

    3. Vamos agora experimentar fazer essa mesma compilação sem o cabeçalho declarado no arquivo que contém a main. Crie um arquivo ex3.c similar a ex2.c mas sem o cabeçalho de foo. Compile usando
      gcc -Wall -o ex3 labaux.c ex3.c
      O compilador gera algumas mensagens precedidas da palavra warning. Essas mensagens são alertas, que o programador deve ler com cuidado, por indicarem possíveis erros, mas que não impedem o compilador de gerar o programa executável. Teste novamente o programa resultante:
      ./ex3 4.0
      O que aconteceu? O que mudou? Como tiramos a informação sobre as funções do arquivo, o compilador não consegue gerar o código corretamente... Vamos entender isso ao longo do curso.

  5. Crie agora um arquivo dump.c com o seguinte conteúdo:
    #include <stdio.h>
    
    void dump (void *p, int n) {
      unsigned char *p1 = p;
      while (n--) {
        printf("%d ", *p1);
        p1++;
      }
    }
    
    Procure entender o que a função dump faz. Repare que ela recebe dois parâmetros: um ponteiro p (um endereço de memória!) e um valor inteiro n.

    Crie também um arquivo ex4.c, que contém algumas chamadas para essa função:

    #include <stdio.h>
    
    void dump (void *p, int n);
    
    int main() {
      char c1 = 1;
      char c2 = '1';
      int i = 1;
      char v[] = "1";
    
      printf("valor de c1: %d -> na memória: ",c1);
      dump(&c1, sizeof(c1));
    
      printf("\nvalor de c2: %d -> na memória: ",c2);
      dump(&c2, sizeof(c2));
    
      printf("\nvalor de i: %d -> na memória: ",i);
      dump(&i, sizeof(i));
    
      printf("\nvalor de v: %s -> na memória: ",v);
      dump(v, sizeof(v));
    
      printf("\n");
      return 0;
    }
    
    Observe as chamadas à função dump. Que argumentos são passados em cada chamada? Lembre que o operador sizeof tem como resultado o tamanho, em bytes, de seu operando. Esse operando pode ser tanto um tipo, como int ou char, como uma variável!

    Compile seu novo programa e o execute, fazendo

    gcc -Wall -o ex4 dump.c ex4.c
    ./ex4
    
    O que você pode concluir sobre o armazenamento das variáveis c e i, e do vetor v?