% Programmation séquentielle en C % Base II - Inspirés des slides de F. Glück % 25 septembre 2019 # Représentation des variables en mémoire (1/N) ## La mémoire - La mémoire est un ensemble de bits. - Elle est accessible via des adresses. +------+----------+----------+------+----------+------+------+ | bits | 00110101 | 10010000 | .... | 00110011 | .... | .... | +======+==========+==========+======+==========+======+======+ | addr | 2000 | 2001 | .... | 4000 | .... | .... | +------+----------+----------+------+----------+------+------+ - Elle est gérée par le système d'exploitation... - et fournie à chaque programme pendant son exécution. - Elle est séparée en deux parties: **la pile** et **le tas**. ## Une variable - Une variable est un identifiant pour une valeur. - 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}). # 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'; // assignation de 'a' dans la valeur pointée par c c = 1000; // on modifie l'adresse pointée par c char d = *c; // on essaie de lire la valeur pointée par c... argl! ``` - `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**. # Les fonctions (1/N) - Les parties indépendantes d'un programme sont mises dans des fonctions. - Syntaxe: ```C type identificateur(paramètres) { // variables optionnelles instructions; return expression; // type donne le type d'expression } ``` - Exemple: ```C int max(int a, int b) { if (a > b) { return a; } else { return b; } } int main() { int c = max(4, 5); } ``` # Les fonctions (2/N) - Il existe un type `void`{.C}, "sans type", en C. - Il peut être utilisé pour signifier qu'une fonction ne retourne rien, ou qu'elle n'a pas d'arguments. - `return`{.C} utilisé pour sortir de la fonction. - Exemple: ```C void show_text(void) { // second void optionnel printf("Aucun argument et pas de retour.\n"); return; // optionnel } void show_text_again() { // c'est pareil printf("Aucun argument et pas de retour.\n"); } ``` # Les fonctions (3/N) ## Prototypes de fonctions - Le prototype donne la **signature** de la fonction, avant qu'on connaisse son implémentation. - L'appel d'une fonction doit être fait **après** la déclaration du prototype. ```C int max(int a, int b); // prototype int max(int a, int b) { // implémentation if (a > b) { return a; } else { return b; } } ``` # Les fonctions (4/N) ## Arguments de fonctions - Les arguments d'une fonction sont toujours passés **par copie**. - Les arguments d'une fonction ne peuvent **jamais** être modifiés. ```C void set_to_two(int a) { // a est une nouvelle variable // la valeur de a une copie de celle passée en argument // lorsque la fonction est appelée, ici -1 a = 2; // la valeur de a est fixée à 2 } // a est détruite int main() { int x = -1; set_to_two(x); // -1 est passé en argument // x vaudra toujours -1 ici } ``` # Les fonctions (5/N) ## Arguments de fonctions: pointeurs - Pour modifier un variable, il faut passer son **adresse mémoire**. - L'adresse d'une variable, `x`{.C}, est accédé par `&x`{.C}. - Un **pointeur** vers une variable entière a le type, `int *x`{.C}. - La sytaxe `*x`{.C} sert à **déréférencer** le pointeur. ```C void set_to_two(int *a) { // a contient une copie de l'adresse de la // variable passée en argument *a = 2; // on accède à la valeur pointée par a, // et on lui assigne 2 } // le pointeur est détruit, pas la valeur pointée int main() { int x = -1; set_to_two(&x); // l'adresse de x est passée // x vaudra 2 ici } ``` <!-- TODO quiz; ```C void set_to_two(int *a) { a = 2; } int main() { int x = -1; set_to_two(&x); } void add_two(int *a) { *a += 2; } int main() { int x = -1; add_two(&x); } void add_two(int a) { a += 2; printf("%d", a); } int main() { int x = -1; add_two(&x); } ``` --> # La fonction `main()` (1/N) ## Généralités - Point d'entrée du programme. - Retourne le code d'erreur du programme: - 0: tout s'est bien passé. - Pas zéro: problème. - La valeur de retour peut être lue par le shell qui a exécuté le programme. - `EXIT_SUCCESS`{.C} et `EXIT_FAILURE`{.C} (de `stdlib.h`) sont des valeurs de retour **portables** de programmes C. # La fonction `main()` (2/N) ## Exemple ```C int main() { // ... if (error) return EXIT_FAILURE; else return EXIT_SUCCESS; } ``` - Le code d'erreur est lu dans le shell avec `$?`{.bash} ```bash $ ./prog $ echo $? 0 # tout s'est bien passé par exemple $ if [ $? -eq 0 ]; then echo "OK" ; else echo "ERROR"; fi ERROR # si tout s'est mal passé ``` # Les tableaux (1/N) ## 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/N) ## 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/N) ## Itérer sur les éléments d'un tableau ```C int x[10]; for (int i = 0; 0 < 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/N) ## Les tableaux comme argument - Un tableau n'est rien d'autre que le pointeur vers sa première case. - Pas moyen de connaître sa taille: `sizeof()`{.C} inutile. - Quand on passe un tableau en argument à une fonction: toujours spécifier sa taille. ```C void foo(int tab[]) { // sans taille... for (int i = 0; i < ?; ++i) { // on sait pas printf("tab[%d] = %d\n", i, tab[i]); } } // avec taille, [n] pas obligatoire void bar(int n, int tab[n]) { // n doit venir avant tab ici for (int i = 0; i < n; ++i) { printf("tab[%d] = %d\n", i, tab[i]); } } ``` <!-- TODO quiz: --> <!-- que retourne sizeof(tab[]) -->