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

modif old base 3

parent b6e98fd5
No related branches found
No related tags found
No related merge requests found
% Base III
% Inspirés des slides de F. Glück
% 2 octobre 2019
% 30 septembre 2020
# Les tableaux (1/6)
## Généralités
- `C` offre uniquement des tableaux statiques
- Un tableau est un "bloc" de mémoire contiguë associé à un nom
- taille fixe déterminée à la déclaration du tableau
- la taille ne peut pas être changée.
- Pas d’assignation de tableaux.
- Un tableau déclaré dans une fonction ou un bloc est détruit à la sortie de celle/celui-ci
- $\Rightarrow$ Un tableau local à une fonction ne doit **jamais être retourné** (aussi valable pour toute variable locale)!
- Les éléments d’un tableau sont accédés avec `[i]`{.C} où `i`{.C} est l’index de l’élément.
- Le premier élément du tableau à l’index `0`{.C}!
- Lorsqu’un tableau est déclaré, la taille de celui-ci doit toujours être spécifiée, sauf s’il est initialisé lors de sa déclaration.
# Les tableaux (2/6)
## Exemple
```C
float tab1[5]; // tableau de floats à 5 éléments
// ses valeurs sont indéfinies
int tab2[] = {1, 2, 3}; // tableau de 3 entiers,
// taille inférée
int val = tab2[1]; // val vaut 2 à présent
int w = tab1[5]; // index hors des limites du tableau
// comportement indéfini!
// pas d'erreur du compilateur
```
<!-- TODO QUIZ:
```C
int a1[5]; // OK
int a2[] = { 1, 2, 3 }; // OK
int a3[4][5]; // OK
int [] a4; // Erreur
int a5[]; // Erreur
int[] function(void) { // Erreur
int array[5]; // OK
return array; // Erreur
}
void foo(int a[]) { // OK
a[3] = 0; // OK
}
void bar(void) {
int a[5]; // OK
foo(a); // OK
a = a5; // Erreur
}
``` -->
<!-- ```C
#include <stdio.h>
int main(void) {
char i;
char a1[] = { 100,200,300,400,500 };
char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
a2[10] = 42;
for (i = 0; i < 5; i++) {
printf("a1[%d] = %d\n", i, a1[i]);
}
return 0;
}
``` -->
# Les tableaux (3/6)
## Itérer sur les éléments d'un tableau
```C
int x[10];
for (int i = 0; i < 10; ++i) {
x[i] = 0;
}
int j = 0;
while (j < 10) {
x[j] = 1;
j += 1;
}
int j = 0;
do {
x[j] = -1;
j += 1;
} while (j < 9)
```
# Les tableaux (4/6)
## Les tableaux comme argument
- Un tableau est le pointeur vers sa première case.
- Pas moyen de connaître sa taille: `sizeof()`{.C} inutile.
- Toujours spécifier la taille d'un tableau passé en argument.
```C
void foo(int tab[]) { // sans taille...
for (int i = 0; i < ?; ++i) {
// on sait pas quoi mettre pour ?
printf("tab[%d] = %d\n", i, tab[i]);
}
}
// n doit venir avant tab, [n] optionnel
void bar(int n, int tab[n]) {
for (int i = 0; i < n; ++i) {
printf("tab[%d] = %d\n", i, tab[i]);
}
}
```
# Les tableaux (5/6)
## Quels sont les bugs dans ce code?
```C
#include <stdio.h>
int main(void) {
char i;
char a1[] = { 100,200,300,400,500 };
char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
a2[10] = 42;
for (i = 0; i < 5; i++) {
printf("a1[%d] = %d\n", i, a1[i]);
}
return 0;
}
```
# Les tableaux (6/6)
## Quels sont les bugs dans ce code?
```C
#include <stdio.h>
int main(void) {
char i;
// 200, .., 500 char overflow
char a1[] = { 100,200,300,400,500 };
char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
a2[10] = 42; // [10] out of bounds
for (i = 0; i < 5; i++) {
printf("a1[%d] = %d\n", i, a1[i]);
}
return 0;
}
```
<!-- TODO quiz: -->
<!-- que retourne sizeof(tab[]) -->
# Représentation des variables en mémoire (1/N)
## La mémoire
* La mémoire est:
- ... un ensemble de bits,
- ... accessible via des adresses,
+------+----------+----------+------+----------+------+------+
| bits | 00110101 | 10010000 | .... | 00110011 | .... | .... |
+======+==========+==========+======+==========+======+======+
| addr | 2000 | 2001 | .... | 4000 | .... | .... |
+------+----------+----------+------+----------+------+------+
- ... gérée par le système d'exploitation.
- ... séparée en deux parties: **la pile** et **le tas**.
## Une variable
* 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.
# 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)
# Types complexes: `struct`{.C} (1/5)
## Généralités
......@@ -283,7 +23,7 @@ int main(void) {
num.im = -2.0;
```
# Types complexes: `struct`{.C} (2/N)
# Types complexes: `struct`{.C} (2/5)
## Simplifications
......@@ -305,7 +45,7 @@ int main(void) {
complex_t num2 = num; // copie
```
# Types complexes: `struct`{.C} (3/N)
# Types complexes: `struct`{.C} (3/5)
## Pointeurs
......@@ -320,116 +60,93 @@ int main(void) {
![La représentation mémoire de `complex_t`.](figs/pointer_struct.svg){#fig:compilation width=100%}
# Types complexes: `struct`{.C} (4/5)
# 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)
## Initialisation
- Avec l'exemple de tout à l'heure:
- Avec le passage par **référence** on peut modifier un struct *en place*.
- Les champs sont accessible avec le sélecteur `->`{.C}
```C
complex_t *num = malloc(sizeof(complex_t));
num->re = 1.0; // maintenant ...
num->im = -1.0; // ça marche.
void complex_init(complex_t *num,
double re, double im)
{
// num a déjà été allouée
num->re = re;
num->im = im;
}
int main() {
complex_t num; // on alloue un complexe
complex_init(&num, 2.0, -1.0); // on l'initialise
}
```
- 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%}
# Types complexes: `struct`{.C} (5/5)
# Allocation dynamique de mémoire (3/N)
## Initialisation version copie
- La fonction `free()`{.C} permet de libérer une zone préalablement allouée avec `malloc()`{.C}.
* On peut allouer un complexe, l'initialiser et le retourner.
* La valeur retournée peut être copiée dans une nouvelle structure.
```C
#include <stdlib.h>
void free(void *ptr);
complex_t complex_create(double re, double im) {
complex_t num;
num.re = re;
num.im = im;
return num;
}
int main() {
// on crée un complexe et on l'initialise
// en copiant le complexe créé par complex_create
// deux allocation et une copie
complex_t num = complex_create(2.0, -1.0);
}
```
- 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)
# Les fonctions (1/2)
## Tableaux dynamiques
## Arguments de fonctions par copie
- Pour allouer un espace mémoire de 50 entiers:
- Les arguments d'une fonction sont toujours passés **par copie**.
- Les arguments d'une fonction ne peuvent **jamais** être modifiés.
```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;
void do_something(complex_t a) { // a: nouvelle variable
// valeur de a est une copie de x
// lorsque la fonction est appelée, ici -1
a.re += 2.0;
a.im -= 2.0;
} // a est détruite
int main() {
complex_t x;
do_something(x); // x est passé en argument
// x sera inchangé
}
```
## 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
```
# Allocation dynamique de mémoire (5/N)
* Que pourrait-on faire pour modifier `x`{.C}?
## Arithmétique de pointeurs
# Les fonctions (2/2)
![L'arithmétique des pointeurs.](figs/pointer_arithmetics.svg){#fig:compilation width=100%}
## Arguments de fonctions par référence
# 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:
- Pour modifier un variable, il faut passer son **adresse mémoire** en argument.
- L'adresse d'une variable, `a`{.C}, est accédée par `&a`{.C}.
- Un **pointeur** vers une variable entière `a` le type, `int *`{.C}.
- La syntaxe `*a`{.C} sert à **déréférencer** le pointeur (à accéder à la mémoire pointée).
```C
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.](figs/double_pointeur.svg){#fig:compilation height=100%}
void do_something(complex_t *a) { // a: un nouveau pointeur
// valeur de a est une copie de d'un pointeur
// les données pointées sont les données originales
# Allocation dynamique de mémoire (8/N)
- Avec `malloc()`, on peut allouer dynamiquement des tableaux de pointeurs:
```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
a->re += 2.0;
a->im -= 2.0;
} // le pointeur a est détruit, *a est toujours là et a été modifié
```
- Ceci est une matrice (un tableau de tableau).
# Prototypes de fonctions (1/N)
# Prototypes de fonctions (1/3)
## Principes généraux de programmation
......@@ -447,8 +164,7 @@ int main(void) {
- Libraire `stdio.h`: `printf()`{.C}, `scanf()`{.C}, ...
# Prototypes de fonctions (2/N)
# Prototypes de fonctions (2/3)
- Prototypes de fonctions nécessaires quand:
1. Utilisation de fonctions dans des fichiers séparés.
......@@ -457,7 +173,7 @@ int main(void) {
- 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)
# Prototypes de fonctions (3/3)
## Fichier header
......@@ -475,13 +191,13 @@ int main(void) {
#include "chemin/du/prototypes.h"// chemin explicite
```
# Génération d'un exécutable (1/N)
# Génération d'un exécutable (1/5)
## Un seul fichier source
![Étapes de génération.](figs/compilation.svg){#fig:compilation width=100%}
# Génération d'un exécutable (2/N)
# Génération d'un exécutable (2/5)
## Un seul fichier source
......@@ -496,13 +212,13 @@ gcc proc.c -o prog
Les différents codes intermédiaires sont effacés.
# Génération d'un exécutable (3/N)
# Génération d'un exécutable (3/5)
## Plusieurs fichiers sources
![Étapes de génération, plusieurs fichiers.](figs/compilation_plusieurs.svg){#fig:compilation_plusieurs width=100%}
# Génération d'un exécutable (4/N)
# Génération d'un exécutable (4/5)
::: Main
......@@ -553,7 +269,7 @@ int sum(int tab[], int n) {
::::::::::::::
# Génération d'un exécutable (4/N)
# Génération d'un exécutable (5/5)
La compilation séparée se fait en plusieurs étapes.
......@@ -576,7 +292,7 @@ La compilation séparée se fait en plusieurs étapes.
$ gcc main.o sum.o -o prog
```
# Préprocesseur (1/N)
# Préprocesseur (1/2)
## Généralités
......@@ -601,7 +317,7 @@ La compilation séparée se fait en plusieurs étapes.
#define NOM_MACRO(arg1, arg2, ...) [code]
```
# Préprocesseur (2/N)
# Préprocesseur (2/2)
## La directive `include`{.C}
......@@ -624,5 +340,124 @@ La compilation séparée se fait en plusieurs étapes.
#endif
```
# Les tableaux (1/2)
## Généralités
- `C` offre uniquement des tableaux statiques
- Un tableau est un "bloc" de mémoire contiguë associé à un nom
- taille fixe déterminée à la déclaration du tableau
- la taille ne peut pas être changée.
- Pas d’assignation de tableaux.
- Un tableau déclaré dans une fonction ou un bloc est détruit à la sortie de celle/celui-ci
- $\Rightarrow$ Un tableau local à une fonction ne doit **jamais être retourné** (aussi valable pour toute variable locale)!
- Les éléments d’un tableau sont accédés avec `[i]`{.C} où `i`{.C} est l’index de l’élément.
- Le premier élément du tableau à l’index `0`{.C}!
- Lorsqu’un tableau est déclaré, la taille de celui-ci doit toujours être spécifiée, sauf s’il est initialisé lors de sa déclaration.
# Les tableaux (2/2)
## Exemple
```C
float tab1[5]; // tableau de floats à 5 éléments
// ses valeurs sont indéfinies
int tab2[] = {1, 2, 3}; // tableau de 3 entiers,
// taille inférée
int val = tab2[1]; // val vaut 2 à présent
int w = tab1[5]; // index hors des limites du tableau
// comportement indéfini!
// pas d'erreur du compilateur
```
# La ligne de commande (1/4)
## Point d'entrée d'un programme
- Le point d'entrée est la fonction `main()`{.C}.
- Elle peut être déclarée de 4 façon différentes:
1. `void main()`{.C}.
2. `int main()`{.C}.
3. `void main(int argc, char **argv)`{.C}.
4. `int main(int argc, char **argv)`{.C}.
- `argc`{.C} est le nombre d'arguments passés à la ligne de commande: **le premier est celui du programme lui-même**.
- `argv`{.C} est un tableau de chaînes de caractères passés sur la ligne de commande.
# La ligne de commande (2/4)
## Exemple d'utilisation
Pour la fonction dans le programme `prog`
```C
int main(int argc, char **argv)
```
Pour l'exécution suivante on a
```bash
$ ./prog -b 50 file.txt
```
```C
argc == 4
argv[0] == "prog"
argv[1] == "-b"
argv[2] == "50"
argv[3] == "file.txt"
```
# La ligne de commande (3/4)
## Conversion des arguments
- Les arguments sont toujours stockés comme des **chaînes de caractère**.
- Peu pratique si on veut manipuler des valeurs numériques.
- Fonctions pour faire des conversions:
```C
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
double atof(const char *nptr);
int snprintf(char *str, size_t size,
const char *format, ...);
// str: buffer, size: taille en octets max à copier,
// format: cf printf(), ret: nombre de char lus
```
# La ligne de commande (4/4)
## Exemple d'utilisation
\footnotesize
```C
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
int main(int argc, char **argv) {
if (argc != 3) {
char *progname = basename(argv[0]);
fprintf(stderr, "usage: %s name age\n", progname);
return EXIT_FAILURE;
}
char *name = argv[1];
int age = atoi(argv[2]);
printf("Hello %s, you are %d years old.\n", name, age);
return EXIT_SUCCESS;
}
```
```bash
$ ./prog Paul 29
Hello Paul, you are 29 years old.
```
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