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!

    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 seus 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, cada um deles é traduzido para um arquivo objeto pelo compilador e depois o ligador junta esses dois objetos. 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 c = 100;
      int i = 10000;
      char v[] = "abcde";
    
      printf("valor de c: %d na memória: ",c);
      dump(&c, sizeof(c));
    
      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?