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 % Inspirés des slides de F. Glück
% 2 octobre 2019 % 7 octobre 2020
# Les tableaux (1/6) # Les tableaux (1/6)
...@@ -168,461 +168,190 @@ int main(void) { ...@@ -168,461 +168,190 @@ int main(void) {
<!-- que retourne sizeof(tab[]) --> <!-- 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: - Automatiser le processus de conversion d'un type de fichier à un autre, en *gérant les dépendances*.
- ... un ensemble de bits, - Effectue la conversion des fichiers qui ont changé uniquement.
- ... accessible via des adresses, - 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`).
+------+----------+----------+------+----------+------+------+ # Utilisation de `make`
| bits | 00110101 | 10010000 | .... | 00110011 | .... | .... |
+======+==========+==========+======+==========+======+======+
| addr | 2000 | 2001 | .... | 4000 | .... | .... |
+------+----------+----------+------+----------+------+------+
- ... gérée par le système d'exploitation. Le programme `make` exécutera la série d'instruction se trouvant dans un `Makefile` (ou `makefile` ou `GNUmakefile`).
- ... séparée en deux parties: **la pile** et **le tas**.
## Une variable ## Le `Makefile`
* Une variable, `type a = valeur`{.C}, possède: - Contient une liste de *règles* et *dépendances*.
- un type (`char`{.C}, `int`{.C}, ...), - Règles et dépendances construisent des *cibles*.
- un contenu (une séquence de bits qui encode `valeur`{.C}), - Ici utilisé pour compiler une série de fichiers sources
- une adresse mémoire (accessible via `&a`{.C}),
- une portée.
# 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;
}
``` ```
$ gcc -c example.c # + plein d'options..
## Arithmétique de pointeurs $ gcc -o example exemple.o # + plein d'options
- 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
``` ```
# Allocation dynamique de mémoire (5/N) :::::::::::::: {.columns}
## Arithmétique de pointeurs
![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 ## Terminal
int a = 2;
int *b = &a;
int **c = &b;
```
- Chaque `*`{.C} ou `&`{.C} rajoute une indirection.
# 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 ![Les dépendances.](figs/ex_makefile_dep.svg){#fig:ex_makefile_dep width=100%}
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). # 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. 1. `make` cherche le fichier `Makefile`, `makefile` ou `GNUmakefile` dans le répertoire courant.
- Modularisation du code $\Rightarrow$ écriture de fonctions. 2. Par défaut exécute la première cible, ou celle donnée en argument.
- Beaucoup de fonctions $\Rightarrow$ regrouper les fonctions dans des fichiers séparés. 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é. # Exemple avancé
- Raisonnement sur le code.
- Débogage.
## 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. hello.o: hello.c hello.h
2. Utilisation de librairies. gcc -Wall -Wextra -c hello.c
- 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) main.o: main.c
gcc -Wall -Wextra -c main.c
## Fichier header clean:
rm -f *.o hello
- Porte l'extension `.h` rebuild: clean hello
- Contient: ```
- définitions des types :::
- prototypes de fonctions ::: {.column width="45%"}
- 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}:
```C ## Un graph complexe
#include <stdio.h> // libraire dans LD_LIBRARY_PATH
#include "chemin/du/prototypes.h"// chemin explicite
```
# 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 ```bash
gcc proc.c -o prog hello: hello.o main.o
``` gcc hello.o main.o -o hello
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.](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` rebuild: clean hello
```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%"} ::: {.column width="45%"}
## `sum.h` ## Nouveau `Makefile`
```C ```bash
#ifndef _SUM_H_ CC=gcc -Wall -Wextra
#define _SUM_H_
int sum(int tab[], int n); hello: hello.o main.o
$(CC) $^ -o $@
#endif hello.o: hello.c hello.h
``` $(CC) -c $<
:::
::: {.column width="55%"}
## `sum.c` main.o: main.c
$(CC) -c $<
```C clean:
#include "sum.h" rm -f *.o hello
int sum(int tab[], int n) {
int s = 0; rebuild: clean hello
for (int i = 0; i < n; i++) {
s += tab[i];
}
return s;
}
``` ```
:::
:::
:::::::::::::: ::::::::::::::
# Variables
# Génération d'un exécutable (4/N) ## Variables utilisateur
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`
- Déclaration
```bash ```bash
$ gcc -Wall -Wextra -std=c11 -c main.c id = valeur
$ gcc -Wall -Wextra -std=c11 -c sum.c id = valeur1 valeur2 valeur3
``` ```
- Édition des liens - Utilisation
```bash ```bash
$ gcc main.o sum.o -o prog $(id)
```
# 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
``` ```
- Les inclusions multiples peuvent poser problème: définitions multiples. Les headers commencent par: - Déclaration à la ligne de commande
```C ```bash
#ifndef _VAR_ make CFLAGS="-O3 -Wall"
#define _VAR_
/*
commentaires
*/
#endif
``` ```
## 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