Skip to content
Snippets Groups Projects

% Base III % Inspirés des slides de F. Glück % 2 octobre 2019

Types complexes: struct{.C} (1/N)

Généralités

  • Plusieurs variables qu'on aimerait regrouper dans un seul type: struct{.C}.

    struct complex { // déclaration
        double re;
        double im;
    };
    
    struct complex num; // déclaration de num
  • Les champs sont accessible avec le sélecteur ".{.C}".

    num.re = 1.0;
    num.im = -2.0;

Types complexes: struct{.C} (2/N)

Simplifications

  • typedef{.C} permet de définir un nouveau type.

    typedef unsinged int uint;
    typedef struct complex complex_t;
    typedef struct complex {
        double re, im;
    } complex_t;
  • L'initialisation peut aussi se faire avec

    complex_t num = {1.0, -2.0}; // re = 1.0, im = -2.0
    complex_t num = {.im = 1.0, .re = -2.0};
    complex_t num = {.im = 1.0}; // argl! .re non initialisé 
    complex_t num2 = num; // copie

Types complexes: struct{.C} (3/N)

Pointeurs

  • Comme pour tout type, on peut avoir des pointeurs vers un struct{.C}.

  • Les champs sont accessible avec le sélecteur ->{.C}

    complex_t *num; // on crée un pointeur
    num->re = 1.0;  // seg fault...
    num->im = -1.0; // mémoire pas allouée.

La représentation mémoire de complex_t.

Allocation dynamique de mémoire (1/N)

  • La fonction malloc{.C} permet d'allouer dynamiquement (pendant l'exécution du programme) une zone de mémoire contiguë.

    #include <stdlib.h>
    void *malloc(size_t size);
  • size{.C} est la taille de la zone mémoire en octets.

  • Retourne un pointeur sur la zone mémoire ou NULL{.C} en cas d'échec: toujours vérifier que la valeur retournée est != NULL{.C}.

Allocation dynamique de mémoire (2/N)

  • Avec l'exemple de tout à l'heure:

    complex_t *num = malloc(sizeof(complex_t));
    num->re = 1.0;  // maintenant ...
    num->im = -1.0; // ça marche.
  • La zone mémoire n'est pas initialisée.

  • La mémoire doit être désallouée explicitement

    \Rightarrow
    fuites mémoires.

La représentation mémoire de complex_t et fuites.

Allocation dynamique de mémoire (3/N)

  • La fonction free(){.C} permet de libérer une zone préalablement allouée avec malloc(){.C}.

    #include <stdlib.h>
    void free(void *ptr);
  • Pour chaque malloc(){.C} doit correspondre exactement un free(){.C}.

  • Si la mémoire n'est pas libérée: fuite mémoire (l'ordinateur plante quand il y a plus de mémoire).

  • Si la mémoire est libérée deux fois: seg fault.

  • Pour éviter les mauvaises surprises mettre ptr{.C} à NULL{.C}.

Allocation dynamique de mémoire (4/N)

Tableaux dynamiques

  • Pour allouer un espace mémoire de 50 entiers:

    int *p = malloc(50 * sizeof(int));
  • Cette espace peut alors être utilisé comme un tableau de 50 entiers:

    for (int i = 0; i < 50; ++i) {
        p[i] = 0;
    }

Arithmétique de pointeurs

  • On peut parcourir la mémoire différemment qu'avec l'indexation

    int *p = malloc(50 * sizeof(int));
    // initialize somehow
    double a = p[7];
    double b = *(p + 7); // on avance de 7 "double"
    p[0] == *p; // le pointeur est le premier élément

Allocation dynamique de mémoire (5/N)

Arithmétique de pointeurs

L'arithmétique des pointeurs.

Allocation dynamique de mémoire (6/N)

Pointeur de pointeur

  • Tout comme une valeur a une adresse, un pointeur a lui-même une adresse:

    int a = 2;
    int *b = &a;
    int **c = &b;
  • Chaque *{.C} ou &{.C} rajoute une indirection.

Allocation dynamique de mémoire (7/N)

Pointeur de pointeur

L'arithmétique des pointeurs.

Allocation dynamique de mémoire (8/N)

  • Avec malloc(), on peut allouer dynamiquement des tableaux de pointeurs:

    int **p = malloc(50 * sizeof(int*));
    for (int i = 0; i < 50; ++i) {
        p[i] = malloc(70 * sizeof(int));
    }
    int a = p[5][8]; // on indexe dans chaque dimension
  • Ceci est une matrice (un tableau de tableau).

Prototypes de fonctions (1/N)

Principes généraux de programmation

  • Beaucoup de fonctionnalités dans un code
    \Rightarrow
    Modularisation.
  • Modularisation du code
    \Rightarrow
    écriture de fonctions.
  • Beaucoup de fonctions
    \Rightarrow
    regrouper les fonctions dans des fichiers séparés.

Mais pourquoi?

  • Lisibilité.
  • Raisonnement sur le code.
  • Débogage.

Exemple

  • Libraire stdio.h: printf(){.C}, scanf(){.C}, ...

Prototypes de fonctions (2/N)

  • Prototypes de fonctions nécessaires quand:

    1. Utilisation de fonctions dans des fichiers séparés.
    2. Utilisation de librairies.
  • Un prototype indique au compilateur la signature d'une fonction.

  • On met les prototypes des fonctions publiques dans des fichiers headers, extension .h.

  • Les implémentations des fonctions vont dans des fichier .c.

Prototypes de fonctions (3/N)

Fichier header

  • Porte l'extension .h

  • Contient:

    • définitions des types
    • prototypes de fonctions
    • macros
    • directives préprocesseur (cf. plus loin)
  • Utilisé pour décrire l'interface d'une librairie ou d'un module.

  • Un fichier C (extension .c) utilise un header en l'important avec la directive #include{.C}:

    #include <stdio.h> // libraire dans LD_LIBRARY_PATH
    #include "chemin/du/prototypes.h"// chemin explicite

Génération d'un exécutable (1/N)

Un seul fichier source

Étapes de génération.

Génération d'un exécutable (2/N)

Un seul fichier source

gcc proc.c -o prog
  1. **Précompilation: ** gcc appelle cpp, le préprocesseur qui effectue de la substitution de texte (#define, #include, macros, ...) et génère le code C à compiler, portant l'extension .i (prog.i).
  2. **Compilation assembleur: ** gcc compile le code C en code assembleur, portant l'extension .s (prog.s).
  3. **Compilation code objet: ** gcc appelle as, l'assembleur, qui compile le code assembleur en code machine (code objet) portant l'extension .o (prog.o).
  4. **Édition des liens: ** gcc appelle ld, l'éditeur de liens, qui lie le code objet avec les librairies et d'autres codes objet pour produire l'exécutable final (prog).

Les différents codes intermédiaires sont effacés.

Génération d'un exécutable (3/N)

Plusieurs fichiers sources

Étapes de génération, plusieurs fichiers.

Génération d'un exécutable (4/N)

::: Main

main.c

#include <stdio.h>
#include "sum.h"
int main() {
  int tab[] = {1, 2, 3, 4};
  printf("sum: %d\n", sum(tab, 4));
  return 0;
}

:::

:::::::::::::: {.columns}

::: {.column width="45%"}

sum.h

#ifndef _SUM_H_
#define _SUM_H_

int sum(int tab[], int n);

#endif

::: ::: {.column width="55%"}

sum.c

#include "sum.h"
int sum(int tab[], int n) {
  int s = 0;
  for (int i = 0; i < n; i++) {
    s += tab[i];
  }
  return s;
}

:::

::::::::::::::

Génération d'un exécutable (4/N)

La compilation séparée se fait en plusieurs étapes.

Compilation séparée

  1. Générer séparément les fichiers .o avec l'option -c.
  2. Éditer les liens avec l'option -o pour générer l'exécutable.

Exemple

  • Création des fichiers objets, main.o et sum.o

    $ gcc -Wall -Wextra -std=c11 -c main.c
    $ gcc -Wall -Wextra -std=c11 -c sum.c
  • Édition des liens

    $ gcc main.o sum.o -o prog

Préprocesseur (1/N)

Généralités

  • Première étape de la chaîne de compilation.
  • Géré automatiquement par gcc ou clang.
  • Lit et interprète certaines directives:
    1. Les commentaires (//{.C} et /* ... */{.C}).
    2. Les commandes commençant par #{.C}.
  • Le préprocesseur ne compile rien, mais subtitue uniquement du texte.

La directive define{.C}

  • Permet de définir un symbole:

    #define PI 3.14159
    #define _SUM_H_
  • Permet de définir une macro.

    #define NOM_MACRO(arg1, arg2, ...) [code]

Préprocesseur (2/N)

La directive include{.C}

  • Permet d'inclure un fichier.

  • Le contenu du fichier est ajouté à l'endroit du #include{.C}.

  • Inclusion de fichiers "globaux" ou "locaux"

    #include <file.h>       // LD_LIBRARY_PATH
    #include "other_file.h" // local path
  • Les inclusions multiples peuvent poser problème: définitions multiples. Les headers commencent par:

    #ifndef _VAR_
    #define _VAR_
    /*
    commentaires
    */
    #endif