Skip to content
Snippets Groups Projects
Unverified Commit bb1a2240 authored by orestis.malaspin's avatar orestis.malaspin
Browse files

base 4 is now arrays and make

parent bd622ee8
No related branches found
No related tags found
No related merge requests found
% Base III
% Base IV
% Inspirés des slides de F. Glück
% 2 octobre 2019
% 7 octobre 2020
# Les tableaux (1/6)
......@@ -168,461 +168,190 @@ int main(void) {
<!-- que retourne sizeof(tab[]) -->
# Représentation des variables en mémoire (1/N)
# Introduction à `make`
## La mémoire
## A quoi ça sert?
* La mémoire est:
- ... un ensemble de bits,
- ... accessible via des adresses,
- Automatiser le processus de conversion d'un type de fichier à un autre, en *gérant les dépendances*.
- Effectue la conversion des fichiers qui ont changé uniquement.
- Utilisé pour la compilation:
- Création du code objet à partir des sources.
- Création de l'exécutable à partir du code objet.
- Tout "gros" projet utilise `make` (pas uniquement en `C`).
+------+----------+----------+------+----------+------+------+
| bits | 00110101 | 10010000 | .... | 00110011 | .... | .... |
+======+==========+==========+======+==========+======+======+
| addr | 2000 | 2001 | .... | 4000 | .... | .... |
+------+----------+----------+------+----------+------+------+
# Utilisation de `make`
- ... gérée par le système d'exploitation.
- ... séparée en deux parties: **la pile** et **le tas**.
Le programme `make` exécutera la série d'instruction se trouvant dans un `Makefile` (ou `makefile` ou `GNUmakefile`).
## Une variable
## Le `Makefile`
* Une variable, `type a = valeur`{.C}, possède:
- un type (`char`{.C}, `int`{.C}, ...),
- un contenu (une séquence de bits qui encode `valeur`{.C}),
- une adresse mémoire (accessible via `&a`{.C}),
- une portée.
- Contient une liste de *règles* et *dépendances*.
- Règles et dépendances construisent des *cibles*.
- Ici utilisé pour compiler une série de fichiers sources
# Représentation des variables en mémoire (2/N)
![Les variables en mémoire.](figs/memory.svg){#fig:memory width=100%}
# Les pointeurs (1/N)
- Un pointeur est une adresse mémoire.
```C
type *id;
```
- Pour interpréter le contenu de ce qu'il pointe, il doit être typé.
- Un pointeur n'est rien d'autre qu'un entier (64bit sur x86-64, soit 8 octets).
- Un pointeur peut être **déréférencé**: on accède à la valeur située à l'adresse mémoire sur laquelle il pointe.
```C
char *c; // déclaration pointeur de char
*c = 'a'; // assign. 'a' à valeur pointée par c
c = 1000; // on modifie l'adresse pointée par c
char d = *c; // on lit la valeur pointée par c. UB!
```
- `NULL`{.C} (ou `0`{.C}) est la seule adresse **toujours** invalide.
# Les pointeurs (2/N)
![Les pointeurs, le déréférencement, et la mémoire.](figs/memory_deref.svg){#fig:memory width=100%}
# Les pointeurs (3/N)
- Permettent d'accéder à une valeur avec une indirection.
```C
int a = 2;
int *b = &a;
*b = 7; // on met 7 dans la case pointée par b
// ici a == 7 aussi
a = -2; // ici *b == -2 aussi
```
- Permettent d'avoir plusieurs chemins d'accès à une valeur.
- Lire **et** écrire en même temps dans un bout de mémoire devient possible: **danger**.
# Quiz: Les pointeurs
## [Quiz: Les pointeurs](https://cyberlearn.hes-so.ch/mod/evoting/view.php?id=1038526)
# La fonction `sizeof()` (1/N)
- La fonction `sizeof()`{.C} permet de connaître la taille en octets:
- d'une valeur,
- d'un type,
- d'une variable.
- Soit `int a = 2`{.C}, sur l'architecture x86_64 que vaut:
- `sizeof(a)`{.C}?
- `sizeof(&a)`{.C}?
- Soit `char b = 2`{.C},
- `sizeof(b)`{.C}?
- `sizeof(&b)`{.C}?
# La fonction `sizeof()` (2/N)
- Réponses:
- `sizeof(a) == 4`{.C}, `int`{.C} entier 32 bits.
- `sizeof(&a) == 8`{.C}, une adresse est de 64 bits.
- `sizeof(b) == 1`{.C}, `char`{.C} entier 8 bits.
- `sizeof(&b) == 8`{.C}, une adresse est de 64 bits.
# Types complexes: `struct`{.C} (1/N)
## Généralités
- Plusieurs variables qu'on aimerait regrouper dans un seul type: `struct`{.C}.
```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}".
```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.
```C
typedef unsinged int uint;
typedef struct complex complex_t;
typedef struct complex {
double re, im;
} complex_t;
```
- L'initialisation peut aussi se faire avec
```C
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}
```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`.](figs/pointer_struct.svg){#fig:compilation width=100%}
# 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ë.
```C
#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:
```C
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**.
<!-- - Toujours garder un pointeur sur la mémoire allouée sinon **pointeur pendouillant**. -->
![La représentation mémoire de `complex_t` et fuites.](figs/pointer_struct_ok.svg){#fig:compilation width=100%}
# Allocation dynamique de mémoire (3/N)
- La fonction `free()`{.C} permet de libérer une zone préalablement allouée avec `malloc()`{.C}.
```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:
```C
int *p = malloc(50 * sizeof(int));
```
- Cette espace peut alors être utilisé comme un tableau de 50 entiers:
```C
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
```C
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
$ gcc -c example.c # + plein d'options..
$ gcc -o example exemple.o # + plein d'options
```
# Allocation dynamique de mémoire (5/N)
## Arithmétique de pointeurs
:::::::::::::: {.columns}
![L'arithmétique des pointeurs.](figs/pointer_arithmetics.svg){#fig:compilation width=100%}
::: {.column width="55%"}
# Allocation dynamique de mémoire (6/N)
## `Makefile`
## Pointeur de pointeur
```bash
example: example.o
gcc -o example example.o
- Tout comme une valeur a une adresse, un pointeur a lui-même une adresse:
exmaple.o: exmaple.c example.h
gcc -c example.c
```
:::
::: {.column width="45%"}
```C
int a = 2;
int *b = &a;
int **c = &b;
```
- Chaque `*`{.C} ou `&`{.C} rajoute une indirection.
## Terminal
# Allocation dynamique de mémoire (7/N)
```bash
$ make
gcc -c example.c
gcc -o example example.o
```
:::
::::::::::::::
## Pointeur de pointeur
# Syntaxe d'un `Makefile` (1/4)
![Un exemple simple de `Makefile`.](figs/ex_makefile.svg){#fig:ex_makefile width=100%}
![L'arithmétique des pointeurs.](figs/double_pointeur.svg){#fig:compilation height=100%}
# Syntaxe d'un `Makefile` (2/4)
# Allocation dynamique de mémoire (8/N)
![La cible.](figs/ex_makefile_cible.svg){#fig:ex_makefile_cible width=100%}
- Avec `malloc()`, on peut allouer dynamiquement des tableaux de pointeurs:
# Syntaxe d'un `Makefile` (3/4)
```C
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
```
![Les dépendances.](figs/ex_makefile_dep.svg){#fig:ex_makefile_dep width=100%}
- Ceci est une matrice (un tableau de tableau).
# Syntaxe d'un `Makefile` (4/4)
# Prototypes de fonctions (1/N)
![La règle.](figs/ex_makefile_regle.svg){#fig:ex_makefile_regle width=100%}
## Principes généraux de programmation
# Principe de fonctionnement
- 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.
1. `make` cherche le fichier `Makefile`, `makefile` ou `GNUmakefile` dans le répertoire courant.
2. Par défaut exécute la première cible, ou celle donnée en argument.
3. Décide si une cible doit être régénérée en comparant la date de modification (on recompile que ce qui a été modifié).
4. Regarde si les dépendances doivent être régénérées:
- Oui: prend la première dépendance comme cible et recommence à 3.
- Non: exécute la règle.
## Mais pourquoi?
`make` a un comportement **récursif**.
- Lisibilité.
- Raisonnement sur le code.
- Débogage.
# Exemple avancé
## Exemple
:::::::::::::: {.columns}
- Libraire `stdio.h`: `printf()`{.C}, `scanf()`{.C}, ...
::: {.column width="55%"}
# Prototypes de fonctions (2/N)
## `Makefile`
- Prototypes de fonctions nécessaires quand:
```bash
hello: hello.o main.o
gcc hello.o main.o -o hello
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`.
hello.o: hello.c hello.h
gcc -Wall -Wextra -c hello.c
# Prototypes de fonctions (3/N)
main.o: main.c
gcc -Wall -Wextra -c main.c
## Fichier header
clean:
rm -f *.o hello
- 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}:
rebuild: clean hello
```
:::
::: {.column width="45%"}
```C
#include <stdio.h> // libraire dans LD_LIBRARY_PATH
#include "chemin/du/prototypes.h"// chemin explicite
```
## Un graph complexe
# Génération d'un exécutable (1/N)
![`Makefile` complexe.](figs/complex_makefile.svg){#fig:complex_makefile width=100%}
## Un seul fichier source
:::
::::::::::::::
![Étapes de génération.](figs/compilation.svg){#fig:compilation width=100%}
# Factorisation
# Génération d'un exécutable (2/N)
:::::::::::::: {.columns}
## Un seul fichier source
::: {.column width="55%"}
## Ancien `Makefile`
```bash
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
hello: hello.o main.o
gcc hello.o main.o -o hello
![Étapes de génération, plusieurs fichiers.](figs/compilation_plusieurs.svg){#fig:compilation_plusieurs width=100%}
hello.o: hello.c hello.h
gcc -Wall -Wextra -c hello.c
# Génération d'un exécutable (4/N)
main.o: main.c
gcc -Wall -Wextra -c main.c
::: Main
clean:
rm -f *.o hello
## `main.c`
```C
#include <stdio.h>
#include "sum.h"
int main() {
int tab[] = {1, 2, 3, 4};
printf("sum: %d\n", sum(tab, 4));
return 0;
}
rebuild: clean hello
```
:::
:::::::::::::: {.columns}
::: {.column width="45%"}
## `sum.h`
## Nouveau `Makefile`
```C
#ifndef _SUM_H_
#define _SUM_H_
```bash
CC=gcc -Wall -Wextra
int sum(int tab[], int n);
hello: hello.o main.o
$(CC) $^ -o $@
#endif
```
:::
::: {.column width="55%"}
hello.o: hello.c hello.h
$(CC) -c $<
## `sum.c`
main.o: main.c
$(CC) -c $<
```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;
}
clean:
rm -f *.o hello
rebuild: clean hello
```
:::
:::
::::::::::::::
# Variables
# 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`
## Variables utilisateur
- Déclaration
```bash
$ gcc -Wall -Wextra -std=c11 -c main.c
$ gcc -Wall -Wextra -std=c11 -c sum.c
id = valeur
id = valeur1 valeur2 valeur3
```
- Édition des liens
- Utilisation
```bash
$ 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:
```C
#define PI 3.14159
#define _SUM_H_
```
- Permet de définir une macro.
```C
#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"
```C
#include <file.h> // LD_LIBRARY_PATH
#include "other_file.h" // local path
$(id)
```
- Les inclusions multiples peuvent poser problème: définitions multiples. Les headers commencent par:
- Déclaration à la ligne de commande
```C
#ifndef _VAR_
#define _VAR_
/*
commentaires
*/
#endif
```bash
make CFLAGS="-O3 -Wall"
```
## Variables internes
- `$@` : la cible
- `$^` : la liste des dépendances
- `$<` : la première dépendance
- `$*` : le nom de la cible sans extension
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment