diff --git a/base_3.md b/base_3.md index 0bef38e4aa447f3ae8b304a2ee0915f9ae3c225f..f6ed7c37e981af2385461b2bab53d64076c80f3d 100644 --- a/base_3.md +++ b/base_3.md @@ -1,268 +1,8 @@ % 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) - -{#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) - -{#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) { {#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**. --> -{#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) -{#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 - - -{#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 {#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 {#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. +```