Séances de soutien Projet informatique 2A Francois.Portet@imag.fr Nicolas.Castagne@imag.fr Ces transparents s’inspirent largement des cours 1A PET (M. Desvignes) et PMP (R Bressoux, E Moisan, N Castagne) Merci à eux 1 1
III. Variables & Opérateurs III.1. Introduction III.2. Variables III.3. Types numériques et opérateurs III.4. Autres types III.5. Types pointeur et notion d’adresse III.6. Constantes nommées
III.1. Introduction En C, on a en permanence besoin de stocker des données, des valeurs. Pour cela on utilise des variables. Une variable est caractérisée par : - son type : contient-elle un entier ? un réel ? un caractère ? … Le C est fortement typé. Toute variable a un type. Le type de la variable va déterminer la façon dont elle sera codée par l’ordinateur et la place (en octets) qu’elle occupera en mémoire. - son nom : c’est une étiquette qui nous permettra de manipuler cette variable. L’étiquette correspond à l’adresse de la variable (voir ci après). - sa valeur : c’est la valeur effective de la variable. C’est l’information manipulée. - sa adresse mémoire : l’endroit de la mémoire de l’ordinateur où la variable est stockée, choisie par le système - sa portée ou durée de vie : l’endroit dans le code où on a le droit de l’utiliser. => Entre les deux accolades du bloc dans laquelle elle est déclarée
Type Étiquette/Nom Valeur Mémoire centrale Adresse III.1. Introduction Type Étiquette/Nom Valeur Mémoire centrale Adresse un octet 1 1 1 1 0 x 2 2 f f 7 2 type1 maVariable1 « ceci » 1 1 1 1 0 x 2 2 f f 7 3 1 1 1 1 0 x 2 2 f f 7 4 type2 maVariable2 « cela » 1 1 1 1 0 x 2 2 f f 7 5 1 1 1 0 x 2 2 f f 765 un bit Ce qui nous intéresse… Ce qui est géré par le processeur mais … que l’on ne peut « totalement » ignorer.
III.1. Introduction Le C est un langage dit fortement typé. Toute variable doit : 1) être déclarée : quelle est son type ? Quelle est son nom ? Informations capitales pour le compilateur qui doit savoir comment stocker la variable et comment coder sa valeur. 2) être initialisée quelle est sa valeur initiale ? - soit en dur dans le code source, - soit par lecture au clavier ou depuis le disque dur. Ensuite et seulement ensuite, il est possible : 3) de la manipuler : opérations arithmétiques par exemple. 4) de sortir les résultats à l’écran ou sur le disque dur.
III.2. Variables II.2. Variables III.2.1. Définition d’une variable en C syntaxe : type nom = valeurInitiale; // Attention au point virgule « ; » ! type est un mot clé réservé du langage qui permet d’indiquer si on a affaire à une variable entière, réelle, … (Cf. paragraphe II.3.) nom est le nom que vous choisissez pour la variable : - les mots clés (main, ... ) du langage sont évidemment interdits. - ne choisir que des noms explicites. Jamais de truc, toto, bidule !!! - sont autorisés les lettres ( a…z A…Z ), les chiffres (0…9) et l’underscore _ - interdit de commencer par un chiffre - jamais de caractère espace ! exemples de noms possibles : NombreJours _MaVariable exemples de noms interdits : 2Truc âge // â interdit
III.2. Variables III.2.4. Rappel - structure d’un programme simple /* ******************************************************* Commentaire général ******************************************************* */ // Entête #include <stdlib.h> #include <stdio.h> // Fonction principale int main( ) { // Définition des variables float test = 0.; // Initialisation et/ou saisie clavier (ou depuis fichier) scanf("%f\n", &test); // Opérations test = test + 9.5 ; // Affichage des résultats (écran ou sur fichier) printf("valeur de test %f\n", test); }
2 ou 4 octets selon le processeur III.3. Types numériques et opérateurs III.3.1. Types entiers Tout langage de programmation permet de manipuler des variables entières qui reflètent les entiers naturels ou relatifs. Exemples : int i = 9 ; // définition d’une variable nommée i de type int long n, p ; // définitions de 2 variables nommées n et p de type long Type C Stockage Borne Inf Borne Sup char 1 octets 255 short 2 octets -32 768 +32 767 int 2 ou 4 octets selon le processeur long 4 octets -2 147 483 648 2 147 483 647 unsigned short 65 535 unsigned int unsigned long 4 294 967 295 Les entiers sont bornés.
! III.3. Types numériques et opérateurs III.3.1. Opérations sur les entiers Affectation : opérateur = Opérations arithmétiques : + - * / % / % : quotient et modulo // 5 / 2 donne 2 et 5 % 2 donne 1. Opérations de comparaison : Cf. chap Conditions booléennes. Exemples : int i, j, k ; i = 1 ; j = 2 ; k = i + j ; Remarque : il faut savoir ce que l’on fait quand on mélange des choux et des carottes : short i ; long unLong ; unLong = 32767 ; i = unLong + 1 ; // i = ??? !
III.3. Types numériques et opérateurs III.3.2. Types réels (flottant) Tout langage de programmation permet de manipuler des variables réelles. Ces types correspondent à des nombres dits à virgule flottante, ou « flottants », qui permettent d’approcher les nombres réels. En C, il existe des flottants simple précision et des flottants double précision Exemples : float x, y, z ; // définition de 3 variables de type float x = 2.3 ; // attention au . y = 2.5e+2 ; // y vaut 250. e+2 ou e2. Type C Stockage Borne Inf Borne Sup float 4 octets ±1,4.10 -45 ±3,4.10 38 double 8 octets ±4,3.10 -324 ±1,7.10 308 Les flottants sont bornés et discrétisés.
! III.3. Types numériques et opérateurs III.3.2. Opérations sur les réels (flottant) Affectation : opérateur = Opérations arithmétiques : + - * / // 5 * 1.0 / 2 donne 2.5 Opérations de comparaison : Cf. chap Conditions booléennes. Remarque : il faut savoir ce que l’on fait quand on mélange des choux et des carottes : int i ; float x ; x = 2.5 ; i = x ; // sommes nous sûrs de ce qui se passe ? !
! float x = 0.0001 ; // en fait, x « vaut » 0.0000999999997845 III.3. Types numériques et opérateurs III.3.2. Opérations sur les réels (flottant) Les flottants sont bornés et discrétisés. … car il est impossible de représenter l’infinité des nombres réels dans un univers fini Conséquences : la Troncature : il existe toujours une erreur de calcul exemple float x = 0.0001 ; // en fait, x « vaut » 0.0000999999997845 L’addition et la multiplication ne sont plus exactement associatives (3.11 * 2.30) * 1.50 = 10.729500000000000 3.11 * (2.30 * 1.50) = 10.729499999999998 La multiplication n’est plus distributive 3.11 * (2.30 + 1.50) n’est pas égal à 3.11 * 2.30 + 3.11 * 1.50 ! Attention : les « erreurs » risquent de se cumuler… Conclusion sur l’arithmétique flottante Les flottants ont un domaine de variation limités (mais plus grand que les entiers) Les calculs flottants ont une précision limitée (suffisante dans la plupart des cas) !
i = i - 1 ; ⇔ i-- ; x = x+y ; ⇔ x += y ; x = x*y ; ⇔ x *= y ; III.3. Types numériques et opérateurs III.3.3. Compléments sur les types entier et flottant Gadgets utiles : i = i +1 ; ⇔ i++ ; i = i - 1 ; ⇔ i-- ; x = x+y ; ⇔ x += y ; x = x*y ; ⇔ x *= y ;
! III.3. Types numériques et opérateurs III.3.3. Compléments sur les types entier et flottant ! Différence entre division entière et division flottante … dépend des types des opérandes en jeu ! float x; x = 5 / 2; // division entière => 5/2 vaut 2 et x « vaut » 2.0 x = 5. / 2. ; // division flottante => x « vaut » 2.5 int i = 5; x = i / 2; // division entière => x « vaut » 2.0 x = i / 2.; // division flottante => x « vaut » 2.5 x = ( (float) i ) / 2; // division flottante => x « vaut » 2.5 Conversion de type (« cast ») ( (float) i ) est de type float … et vaut « à peu près » 5.
II.3.4. Opérations sur les caractères III.4. Autres types III.4.1. Type caractère On appelle caractère tout caractère alphanumérique. Tous les caractères accessibles au clavier et affichables à l’écran sont possibles. Exemple : char c ; c = ′a ′ ; // valeur ′a ′ de type caractère ( entourée par ′ et ′ ) // affectée à la variable c ; c vaut donc ′a′ Remarque sur l’encodage ASCI… Affectation : opérateur = Opérations de comparaison : Cf. chap Conditions booléennes. Remarque : Ne pas confondre un caractère seul ( ′a ′ ) et une chaîne de caractères comme par exemple ( "Ceci est une chaine de caracteres" ). II.3.4. Opérations sur les caractères
III.4. Autres types III.4.1 Type booléen ??? Dans les langages où il existe, le type booléen permet de manipuler des variables qui peuvent prendre seulement deux valeurs (True ou False) et d’effectuer des calculs logiques. Il n’existe de pas de type booléen en C. En C, toute variable entière ≠ 0 « peut » être considérée True. toute variable entière = 0 « peut » être considérée False. (Cf. chap V Conditions booléennes).
III.5. Types pointeur et notion d’adresse III.5.1. Notion d’adresse Rappel : une variable est caractérisée par : Un type : entier, réel, etc… Un nom (une étiquette permettant de la manipuler dans le programme) Une valeur Une durée de vie (ou portée) : de l’endroit où elle est déclarée à la fin du bloc : } De plus une variable occupe un espace dans la mémoire, celui où sa valeur est stockée À partir de « l’adresse de la variable » int a;
III.5. Types pointeur et notion d’adresse III.5.2. Opérateur d’adresse & L’opérateur d’adresse & permet de trouver l’adresse mémoire d’une variable #include <stdio.h> main() { int a; a=5; printf("Bonjour %d\n", a); printf("La valeur de a est %d \n", a); printf("L’adresse de a est %p \n", &a); } Création (allocation) de « a » à une adresse choisie par l’ordinateur Attention : Valeur de a quelconque ! Affectation de « a » Ecriture de la valeur 5 à l’adresse de « a » en mémoire Destruction de « a » à la fin du « bloc » Affichage de l’adresse de la variable a
III.5. Types pointeur et notion d’adresse III.5.3. Type pointeur Une adresse n’est pas un entier, ni un réel, ni un caractère, … Si on veut la stocker dans une variable, il faut une variable de type pointeur. Type pointeur : il est repéré par * lors de la déclaration d’un pointeur. Exemple : int i = 2 ; // définition d’une variable i de type entier (int) int * p_i = NULL ;// définition d’une variable p_i //de type « pointeur vers un entier » (int * ) // La valeur de p_i est l’adresse d’un entier. // On initialise le pointeur à NULL adresse invalide p_i = & i ; // affectation de l’adresse de i a la vairable p_i. // on dit que » p_i pointe vers i » ! Une variable pointeur est une variable comme les autres… Elle a un nom, un type (pointeur vers type_X), une valeur (qui est l’adresse d’autre chose)… et bien sûr une adresse ! On peut créer des variables pointeur vers tous les types (int, float…)… et même des pointeurs (eg int ** pp_i; )
III.5. Types pointeur et notion d’adresse III.5.3. Type pointeur Pour accéder à la valeur qui est à une adresse donnée, on utilise l’opérateur de déréférencement * Exemple : int i = 2 ; // définition d’une variable i de type entier (int) int * p_i = NULL ;// définition d’une variable p_i de type « pointeur vers entier » p_i = & i ; // affectation de l’adresse de i a la vairable p_i. // on dit que » p_i pointe vers i » ! *p_i = 3 ; // on affecte 3 à l’adresse pointée => à « i » ! printf("adresse de i %p %p\n", p_i) ; // affiche adresse de i &i valeur de p_i ! printf("valeur de i : %d \n", *p_i ) ; // on affiche valeur pointée (la valeur de i)
<type> maVariable « ceci » III.5. Types pointeur et notion d’adresse III.5.4. Résumé <type> maVariable « ceci » <type> * p_maVariable = & maVariable Valeur Adresse maVariable &maVariable *p_maVariable p_maVariable
III.5. Types pointeur et notion d’adresse III.5.4. Exemple Exemple : que valent x, y, z et *ptr ? pour répondre, représentez la mémoire ! // … de nombreux usages à discuter ultérieurement…
III.5. Types pointeur et notion d’adresse Notion de pointeur Un pointeur est une variable dont la valeur est une adresse mémoire Déclaration et syntaxe : float * unPtr = NULL ; // unPtr est un « pointeur vers une variable flottante » // Comme toute variable, unPtr a un type (float*), un nom, // et une valeur stockée quelque part en mémoire (à l’adresse de unPtr) // La valeur de unPtr est une adresse, celle d’un float. // *unPtr est la valeur qui est stockée à cette adresse Exemple : que valent x, y, z et *z ? pour répondre, représentez la mémoire ! // … de nombreux usages à discuter ultérieurement…
IV. Entrée / sortie sur le Terminal IV.1. Notion d’entrée / sortie IV.2. Afficher avec printf IV.3. Lire avec scanf IV.4. Synthèse des formats « % » IV.5. Compléments divers
IV.1. Notion d’entrée / sortie Opération permettant la communication entre le programme et l’extérieur. Sortie : exportation d’une valeur vers l’extérieur du programme, à travers un périphérique d’entrée : écran, fichier, réseau, carte son… Sortie dans le terminal (« sortie standard ») : avec printf, puts, etc. Voir les man (man printf) Entrée : introduction d’une valeur à l’intérieur du programme, depuis un périphérique d’entrée : clavier, fichier, réseau, carte d’acquisition… Entrée depuis le clavier (« entrée standard ») : avec scanf, getc, etc. Voir les man (man scanf)
Entrées / sortie printf scanf Ou sont les 7 erreurs ?? Main() { int i ; float x double y; double *py; scanf("%d", i); // on demande la valeur de i à l'utilisateur printf(" valeur de i : %f \n", i); py = &y; scanf("coucou %d %d\n", x, py); // on demande la valeur de x et y à l'utilisateur printf(" valeur de x et y : %f %f \n", x, py); } 26 26 26
IV.5 compléments divers Bibliothèque de fonctions mathématiques : #include <math.h> // sqrt, ln, log10, pow, sin, cos, … Voir man math Les arguments et les valeurs renvoyées par ces fonctions sont de type double. Exemple : double x, y, z ; x = 3.0 ; y = 2.0 ; z = pow ( x , y ) ; // puissance
V. Structures de contrôles V.1. Introduction V.2. Conditions booléennes V.3. Structures alternatives V.4. Structures répétitives
V.1. Introduction De nombreux problèmes algorithmiques nécessitent : - soit de faire des choix : selon le résultat d’un choix on pourra exécuter un bloc d’instructions ou un autre (exemple : suivant que le discriminant positif OU négatif, faire…). structures alternatives. - soit de répéter de nombreuses fois le même bloc d’instructions : structures répétitives. Dans les deux cas, nous aurons à manipuler des conditions booléennes : - conditions du choix alternatif, - conditions d’arrêt de la répétition.
= = != > < >= <= V.2. Conditions booléenne V.2.1. Opérateurs de comparaison Une condition est une expression booléenne qui peut être : - soit vraie (Vrai, True). En C, tout entier différent de 0 est « vrai » ; - soit fausse (Faux, False). En C, la valeur entière 0 en C est « faux ». Exemple : les comparaisons entre valeurs comparables : a égal b ? a différent de b ? a supérieur à b ? Opérateurs de comparaison en C : pour les entiers, réels, caractères = = != > < >= <= a = = b teste l’égalité entre a et b ; a != b teste la différence entre a et b ; a = b affecte b à a (et renvoie la valeur de b) . « = » n’est donc pas un opérateur de comparaison !!! ! Piège !
! V.2. Conditions booléenne V.2.2. Opérateurs logiques Opérateurs logiques : 2 expressions booléennes A et B peuvent être associées à l’aide des opérateurs logiques habituels : ET OU NON. A ET B A OU B NON A Opérateurs logiques en C : && || ! Exemples : ( ( 1 = = 2 ) && ( 3 < 5 ) ) est Faux ( ( 1 = = 2 ) || ( 3 < 5 ) ) est Vrai ! ( (1 = = 2) || (5 > 2 )) est Faux Vrai Faux B Vrai Faux A Vrai Faux NON A B A A ! Attention aux priorités entre Opérateurs. Utiliser des ( ) si besoin est.
V.3. Structures alternatives V.3.1. Si … sinon Algorithmique En langage C Condition VRAI FAUX Bloc n°1 Bloc n°2 Bloc suite Si (Condition) Alors faire Bloc n°1 Sinon faire Bloc n°2 Fin Si Bloc suite if ( Condition ) { // Bloc n°1 ; } else // Bloc n°2 ; // Bloc suite ; Un bloc est un ensemble d’instructions regroupées entre { ... } Conventions de codage : pensez à indentez vos structures pour la lisibilité !!!
V.3. Structures alternatives V.3.1. Si … sinon Exemple #include <stdio.h> main() { int a,b; printf("Entrer 2 entiers a et b, separes par un espace\n") ; scanf("%d %d", &a, &b) ; if (a>b) { printf("a (%d) est plus grand que b (%d)\n", a, b); } else { printf("a (%d) est plus petit que b (%d) – ou égal\n", a, b); } printf("fin du programme\n");
V.3. Structures alternatives V.3.2. Si … Le bloc Sinon est facultatif : Condition VRAI FAUX Bloc suite Algorithmique C Si (Condition) Alors faire Bloc n°1 Fin Si Bloc suite if ( Condition ) { // Bloc n°1 ; } // Bloc suite ; Bloc n°1
V.3. Structures alternatives V.3.3. Si imbriqués Algorithmique En langage C if ( Condition1 ) { if ( Condition2) Bloc n°1 ; } else Bloc n°2 ; Bloc n°3 ; Bloc suite ; if ( Condition1 ) { if ( Condition2) // Bloc n°1 ; } else // Bloc n°2 ; // Bloc n°3 ; // Bloc suite; Si ( Condition1 Alors faire Si ( Condition2 ) Alors faire Bloc n°1 Sinon faire Bloc n°2 Fin Si Bloc n°3 Bloc suite Indentation : Qui est plus facile à lire ? À comprendre ? À débugger ?
V.3. Structures alternatives V.3.4. Si … sinon si … Algorithmique C if ( Condition1 ) { Bloc n°1 ; } else if ( Condition2 ) Bloc n°2 ; Bloc n°3 ; Bloc suite ; if ( Condition1 ) { // Bloc n°1 ; } else if ( Condition2 ) // Bloc n°2 ; else // Bloc n°3 ; // Bloc suite ; Si ( Condition1 ) Alors faire Bloc n°1 Sinon Si ( Condition2 ) Alors faire Bloc n°2 Sinon faire Bloc n°3 Fin Si Bloc suite Écriture plus compacte. Surtout lorsque le nombre de cas augmente !!!
// Bloc n°2 ; V.3. Structures alternatives V.3.5. Choix parmi … Quand on a un choix parmi plusieurs valeurs, une syntaxe spécifique existe en C : switch ( x ) { case valeur1: // Bloc n°1 ; break ; case valeur2: // Bloc n°2 ; default: // Bloc n°3 } // Bloc suite ; if ( x = = valeur1 ) { // Bloc n°1; } else if (x = = valeur2) // Bloc n°2 ; else // Bloc n°3 ; // Bloc suite ; La section default est facultative. Les instructions break sont importantes… sinon l’exécution est surprenante ! x doit être un entier ou un caractère.
V.3. Structures alternatives V.3.5. Choix parmi … Exemple #include <stdio.h> main() { char c ; int prix = 0; printf("\t 1 : Vin rouge\n"); printf("\t 2 : Eau\n"); printf("\t 3 : Rien\n"); printf("\t -----> "); scanf("%c", &c); printf("Vous avez choisi : "); switch (c ) { case '1' : printf("Du rouge\n"); prix=100; break; case '2' : printf("De l’eau\n"); prix=10; break; case '3' : printf("rien\n"); prix=0; break; default : printf("Choix non valide\n"); break; } printf("Pour un prix de %d euros\n", prix);
V.4. Structures répétitives V.4.1. Introduction Les structures répétitives, encore appelées boucles, sont utilisées lorsque l’on a besoin de faire exécuter plusieurs fois de suite une opération. Il existe trois type de boucles : - répéter jusqu’à ce qu’une condition soit atteinte, - répéter tant qu’une condition est vérifiée, - répéter n fois avec n connu à l’avance.
! V.4. Structures répétitives V.4.2. Boucle tant que … faire Condition VRAI FAUX Bloc Bloc suite Algorithmique C Tant que (Condition) Faire Bloc Fin Tant que Bloc suite while ( Condition ) { // Bloc ; } // Bloc suite ; Le bloc est répété tant que la condition est vraie. Si la condition n’est jamais vraie, on ne rentre jamais dans le bloc. Si la condition est toujours vraie, on ne sort jamais de la boucle !!! On parle alors de « boucle infinie »… une cause de bug que vous rencontrerez ! !
V.4. Structures répétitives V.4.2. Boucle tant que … faire Exemple #include <stdio.h> #define MAX 10 main() { int i = 1; int accumulateur = 0; while ( i <= MAX) { accumulateur += i; i ++; } printf("la somme des %d premiers entiers vaut %d\n", MAX, accumulateur);
! V.4. Structures répétitives V.4.3. Boucle Répéter … tant que Algorithmique C Bloc do { // Bloc ; } while ( condition ) ; // Bloc suite ; Faire Bloc Tant que ( Condition ) Bloc suite Condition FAUX VRAI Bloc suite Le bloc est répété tant que la condition est vraie. Le bloc est toujours répété au moins une première fois. Si la condition est toujours vraie, on ne sort jamais de la boucle !!! !
V.4. Structures répétitives V.4.3. Boucle Répéter … tant que Exemple #include <stdio.h> main() { int choix ; do { printf("Choisir parmi : \n"); printf("\t 1 : Vin rouge \n"); printf("\t 2 : Eau \n"); printf("\t 3 : Rien \n"); scanf("%d", &choix); } while ( choix<=0 || choix>3 ) ; printf("Votre choix est : %d\n", choix); }
V.4. Structures répétitives V.4.4. Boucle Pour Quand le nombre de répétition est prévu, on peut utiliser une boucle « pour ». Algorithmique C Initialisation Pour i de début à fin par pas de incr Faire Bloc Fin Pour Bloc suite for( init ; cond ; incr ) { Bloc ; } Bloc suite ; VRAI Condition FAUX Bloc Incrémentation int i; for( i = 1 ; i <= 101 ; i = i + 2 ) { printf("%d \n », i); } Bloc suite Initialisation Condition de continuation Incrémentation Habituellement, les compteurs sont des entiers nommés i, j, k, …
V.5. Savoir tracer un programme Le cas des boucles Lorsqu’on programme, il faut être capable de se « substituer à l’ordinateur ». C’est ce qu’on appelle « tracer » un programme. Cela est particulièrement vrai lorsqu’on manipule une boucle ou la mémoire (pointeurs, tableaux, etc.). Avec les boucles, trois catégories de questions à se poser sont : - est-ce que la boucle se termine bien ? Ou est-ce une boucle infinie ? Ou est « l’incrément » dans la boucle ? Est-ce que la condition et bien modifiée par la boucle, et la condition d’arrêt va-t-elle être atteinte ? - que se passe-t-il lors des premiers passages dans la boucle ? Que vaut l’incrément ? Qu’est-ce que fait le programme ? Conseil : toujours « tracer » les deux premiers passages dans la boucle - que se passe-t-il lors des derniers passage dans la boucle (avant dernier, dernier…) ? Que vaut l’incrément ? Qu’est-ce que fait le programme ? Conseil : toujours « tracer » les deux derniers passages dans la boucle
V.5. « Tracer » un programme Le cas des boucles Que font ces exemples ? #include <stdio.h> main() { int i=0; while (i<100) { printf("%d ",i); i += 2 ; } printf("\n"); #include <stdio.h> main() { int i=5; while (i<100) { printf("%d\n",i); i = i -1 ; } printf("\n"); #include <stdio.h> main() { int i=5; do { printf("%d\n",i); i -= 1 ; } while (i<0) ; printf("\n"); }
V.5. « Tracer » un programme Le cas des boucles Que fait cet exemple ? Remarque : une boucle pour est toujours équivalente à une boucle while… main() { int i, n ; // deux entiers int s=0; // accumulateur //saisie printf("n ?\n"); scanf("%d", &n); // calcul for (i = 1 ; i <= n ; i++) { s = s + i; } //affichage printf("n %d - s %d \n", n, s);
VIII. Fonctions et procédures VIII.1. Introduction VIII.2. Le mécanisme de base VIII.3. Passage par valeur et « passage par adresse » VIII.4. Notion de contrat de fonction, prototype d’une fonction
VIII.1. Introduction VIII.1.1 En maths … Une fonction mathématique retourne une valeur de sortie (un résultat) établie à partir de différents paramètres d’entrée (des arguments) : #include <math.h> x, y, z réels double x, y, z ; i entier int i ; z ← | x | z = fabs(x) ; associe un réel à un réel, z ← arctg ( x ) z = atan(x) ; associe un réel à un réel, i ← ⌊ x ⌋ i = floor(x) ; associe un entier à un réel, z ← xy z = pow(x, y) ; associe un réel à deux réels.
VIII.1. Introduction VIII.1.2 En programmation… En programmation, une fonction prend en entrée des données et renvoie des résultats après avoir exécuté différentes instructions. Une fonction ou procédure apparaît comme : - un bloc d’instructions regroupées sous un nom particulier, - qui pourra retourner une unique valeur de sortie (un résultat) - qui pourra admettre 0, 1, 2, … paramètres d’entrée (des arguments). Les avantages sont nombreux : - réutilisation du même bloc d’instructions (sur des valeurs différentes), - réutilisation dans d’autres programmes (bibliothèques). - décomposition du problème en fonctions qui effectuent des actions simples : - conception du programme plus facile, structuration - lisibilité du code accrue, - débuggage facilité.
VIII.2. Le mécanisme de base VIII.2.1. Exemple #include "stdio.h" // bibliothèque de fonctions d’entrées/sorties float discr ( float x , float y , float z) { float discriminant ; // bloc d’instructions (entre {…} ) de la discriminant = y * y – 4 * x * z ; // fonction nommée discr // return discriminant ; // } int main( ) float a = 4. , b = 3. , c = -1.5 , d , e ; d = discr ( a , b , c ) ; e = discr ( a , 3. , 9.) ; return 0 ; 2/ retour 1/ appel de la fonction discr Le main est la fonction qui est automatiquement appelée lors de l’exécution : c’est le point d’entrée du programme.
VIII.2. Le mécanisme de base VIII.2.2. Déclaration d’une fonction La liste des arguments (type et nom) en entrée de la fonction. Les arguments sont des variables ! Cette liste peut être vide. Le nom de la fonction float discr ( float x , float y , float z ) { float discriminant ; discriminant = y * y – 4 * x * z ; return discriminant ; } Variable locale Le type retourné. C’est le type de la valeur retournée. Le type peut être void (aucune valeur retournée) Corps de la fonction. Bloc d’instructions qui seront exécutées à chaque appel de la fonction. Le résultat renvoyé par la fonction. Ici, c’est la valeur de la variable locale discriminant
VIII.2. Le mécanisme de base VI.2.3. Appel de fonction Appel de la fonction discr avec les valeurs de a, b et c. On suppose la fonction discr déclarée par ailleurs. La valeur retournée par discr est affectée à d. int main(int argc, int ** argv) { float a = 4. , b = 3. , c = -1.5 , res ; res = discr ( a , b , c ) ; printf( " discr de %f %f %f vaut %f\n", a, b, c, res); printf( " discr de %f %f %f vaut %f\n", a, 3., 9., discr ( a , 3. , 9 ) ); return 0 ; }
Représentation de la mémoire VIII.2. Le mécanisme de base VIII.2.4. ce qui se passe… Avant l’appel de fonction float discr ( float a , float y , float z) { float discriminant ; discriminant = y * y – 4 * a * z ; return discriminant ; } int main( ) float a = 4. , b = 3. , d ; d = discr ( a , b , -1.5 ) ; return 0 ; Représentation de la mémoire a = 4 b = 3 d = ???
VIII.2. Le mécanisme de base VIII.2.4. ce qui se passe… Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables float discr ( float a , float y , float z) { float discriminant ; discriminant = y * y – 4 * a * z ; return discriminant ; } int main( ) float a = 4. , b = 3. , d ; d = discr ( a , b , -1.5 ) ; return 0 ; a, y et z sont les « paramètres » discriminant est une « variable locale » a = 4 y = 3 z = -1,5 discriminant = ??? a = 4 b = 3 d = ???
VIII.2. Le mécanisme de base VIII.2.4. ce qui se passe… Appel de la fonction. Ca se passe comme d‘habitude dans le « main » float discr ( float a , float y , float z) { float discriminant ; discriminant = y * y – 4 * a * z ; return discriminant ; } int main( ) float a = 4. , b = 3. , d ; d = discr ( a , b , -1.5 ) ; return 0 ; a = 4 y = 3 z = -1,5 discriminant = 33 a = 4 b = 3 d = ???
VIII.2. Le mécanisme de base VIII.2.6. return Lorsque l’on rencontre une instruction return, l’exécution de la fonction est arrêtée. On reprend l’exécution des instructions qui suivent l’appel de la fonction. Une fonction peut posséder plusieurs instructions return. int maximum( int a , int b) { if ( a > b ) return a ; else return b ; }
VIII.2. Le mécanisme de base VIII.2.7. Type void Une fonction peut ne pas renvoyer de résultat : son type de retour est noté void. Une telle fonction est appelée une procédure. #include "stdio.h » void afficheMax( int a , int b ) { // procédure de type void if ( a > b ) { printf("a est le max\n”); return ; } printf(”b est le max\n”); int main( ) { afficheMax(3 , 5 ) ; // pas de valeur à récupérer ! return 0 ; Remarque : une procédure peut utiliser des instructions return, qui alors ne retourne rien.
VIII.3. Passage « par valeur » et « par adresse » VIII.3.1. Le problème Écrire une fonction qui échange les valeurs de deux variables a et b de type int. #include <stdio.h> void echange( int a , int b ) { int temp ; temp = b ; b = a ; a = temp ; } int main( ) int a = 1 , b = 2 ; echange( a, b ) ; printf("a: %d et b: %d\n", a, b); return 0 ; Solution naïve : Que valent a et b ?
VIII.3. Passage « par valeur » et « par adresse » VIII.3.1. Le problème Ceci ne fonctionne pas car a et b (du main) et a et b (de echange) sont des variables différentes bien qu’étant des homonymes ! En fait, c’est comme si on avait écrit le programme suivant : #include <stdio.h> void echange( int n , int m ) { int temp ; temp = n ; n = m ; m = temp ; } int main( ) int a = 1 , b = 2 ; echange( b , a ) ; printf("a: %d et b: %d\n", a, b); return 0 ;
! VIII.3. Passage « par valeur » et « par adresse » VIII.3.1. Le problème Reprenons le déroulement du programme : 1) on définit a et b dans le main ; on leur affecte les valeurs 1 et 2. 2) on appelle la fonction echange avec les VALEURS de a et b. 3) les paramètres m et n reçoivent les valeurs 1 et 2. 4) la fonction echange échange les valeurs de m et n : les paramètres m et n locaux à la fonction valent 2 et 1. 5) on revient dans le main … a et b valent toujours 1 et 2 !!! ! Passage par valeur.
VIII.3. Passage « par valeur » et « par adresse » VIII.4.3. La solution Les pointeurs viennent à notre secours : #include <stdio.h> void echange( int * p_a , int * p_b ) { int temp ; temp = *p_b ; *p_b = *p_a ; *p_a = temp ; } int main( ) int a = 1 , b = 2 ; echange( &a , &b ) ; printf("a: %d et b: %d\n", a, b); return 0 ;
! VIII.3. Passage « par valeur » et « par adresse » VIII.3.2. La solution Reprenons le déroulement du programme : 1) on définit a et b dans le main ; on leur affecte les valeurs 1 et 2. 2) on appelle la fonction echange avec les ADRESSES de a et b. 3) p_a et p_b reçoivent les ADRESSES de a et b. 4) la fonction echange échange les valeurs des cases mémoire pointées par p_a et p_b. 5) on revient dans le main … a et b valent maintenant 2 et 1 ! ! Passage par adresse.
VIII.3. Passage « par valeur » et « par adresse » VIII.3.3. Exemple Appel de la fonction : copie des valeurs des paramètres effectifs dans de nouvelles variables valeurs adresses void echange( int * p_a , int * p_b ) { int temp ; temp = *p_b ; *p_b = *p_a ; *p_a = temp ; } int main( ) int a = 1 , b = 2 ; echange( &a , &b ) ; return 0 ; 0xbffff388 p_a = 0xbffff3b0 0xbffff3bc *p_b est la même case mémoire que la variable « b » du main => On affecte 1 à cette variable (c’est à dire la valeur de *p_b) p_b =0xbfffff3b4 0xbffff3c0 temp = 2 0xbffff3b0 a = 1 0xbffff3b4 b = 1
Passage de paramètres (comment cela fonctionne t il?) en assembleur Prenez le programme echange.c et obtenez le code assembleur par : $ clang -S echange.c -o echange.s Ouvrez le avec gedit : $gedit echange.s & Ouvrez le code assembleur commenté sur le site web → on voit qu’en réalité le passage de paramètres se fait dans la fonction appelante et dans la fonction appelée à travers la pile
! VIII.4. Passage par valeur et par adresse VIII.3.4. Résumé Une variable “a” définie dans une fonction n’est utilisable que dans cette fonction. On dit que a est locale à la fonction. Une variable “a” définie dans une autre fonction est une variable homonyme mais différente de la première ; elle occupera une autre case mémoire lors de l’appel de la fonction. Pour modifier la valeur d’une variable lorsqu’on appelle une procédure, il faut utiliser le mécanisme du passage “par adresse” avec des pointeurs … Faire attention à la manipulation des & et des * !!! Remarque : les termes “passage par valeur” et “passage par adresse” sont en fait des abus de langage… En C, on passe toujours “par valeur”, valeurs qui valent parfois “des adresses”.. !
VIII.4. Notion de contrat de fonction VIII.4.1. Introduction Avant même d’écrire le corps d’une fonction, il faut être très sur le « contrat » qu’elle doit remplir, c’est à dire : Son rôle (ce qu’elle fait) Son prototype. Le « prototype » ou la « signature » est constitué de : nom de la fonction, type de retour, type et nom des paramètres Chacun des paramètres est soit : IN : utilisé uniquement en entrée. Passé par valeur en C. OUT : utilisé uniquement en sortie. Passé par pointeur en C. IN/OUT : utilisé en entrée et en sortie. Passé par pointeur en C. Le champ d’utilisation de la fonction, et en particulier l’état que doit respecter le système avant l’appel de la fonction : préconditions L’état que dans lequel sera le système après l’appel de la fonction Les cas d’erreur, et la façon dont la fonction réagit en cas d’erreur.
VIII.4. Notion de contrat de fonction VIII.4.2. Exemple Le « contrat de fonction » peut être formalisé à l’aide de commentaire. Exemple : définir une fonction résolvant dans une équation du second degré. Le « contrat » d’une telle fonction pourrait s’écrire : Prototype Contrat
VIII.4. Notion de contrat de fonction VIII.4.3. Utilisation du prototype comme promesse d’existence Pour que le compilateur puisse vérifier les appels de fonctions, la fonction doit être définie ou son prototype déclaré avant le premier appel de la fonction. #include <stdio.h> //commentaire précisant « contrat » void echange( int * p_a , int * p_b) ; // Prototype de la fonction int main( ) { int a = 1 , b = 2 ; echange( &a , & b ) ; // Appel de la fonction return 0 ; } void echange( int * p_a , int * p_b) // Définition de la fonction int temp ; temp = *p_b ; *p_b = *p_a ; *p_a = temp ;
VIII.4. Notion de contrat de fonction VIII.4.4 Structure générale d’un programme /* ******************************************************* Titre ******************************************************* */ // Entête (#include, #define, etc.) … // Prototypes des fonctions et procédures, accompagné de commentaires pour les « contrats » int main( ) { // fonction principale … // Définition des variables du main … // Appels des fonctions et procédures // Dorénavant le main sera être réduit à son minimum : // pour l’essentiel, définition de variables et appels de fonctions et procédures. return 0 ; } // Définition des fonctions // fonction exemple <type retour> exemple(<liste des arguments>) { … // Définition des variables locales de la fonction exemple … // Instructions de la fonction exemple return … ; // valeur de retour
Tableaux 75 75 75
Tableaux 76 76 Tableaux Adresse Tableaux Collection de variables de même type, rangées continûment en mémoire Déclaration : spécifier le type des éléments le nom du tableau le nombre des éléments Exemples : float c[100]; /* tableau de 100 réels */ int tab[10]; /* tableau de 10 entiers*/ Exemple : tab1.c main() { int i; int tab[10]; /* Tableau de 10 entiers */ float c[20]; /* Tableau de 20 réels */ for (i=0; i<10; i++) tab[i]= 2*i; /*Mettre 0,2,4,6..dans les elements*/ for (i=0; i<10; i++) printf("%d ",tab[i]); /* afficher les éléments de t */ } tab tab[0]: 0 &tab[0] tab[1]: 2 &tab[1] tab[2]: 4 &tab[2] tab[9]: 18 &tab[9] … 76 76 76 76
Tableaux 77 Adresse 77 Remarques Accès à un élément du tableau : Remarques Nombre d’éléments constant, non modifiable Le nom du tableau est son adresse (ie l’endroit où il se trouve en mémoire Accès à un élément du tableau : nom_du_tableau[expression entiere] Exemple : tab[i] Comment fait le compilateur : on part du début du tableau, on ajoute i : c’est l’endroit où se trouve notre élément Attention : Pas de vérification sur les indices. Si on demande un élément avant ou après la fin du tableau, c’est une erreur à l’execution du programme mais pas d’erreurs à la compilation &tab[-2] tab &tab[0] &tab[1] tab+1 2 tab+2 4 tab[2] tab+9 18 &tab[9] … &tab[900] 77 77 77 77
Tableaux : accès et débordements Adresse Exemple 2 : tab2.c main() { int i; int tab[10]; for (i=0; i<10; i++) tab[i]= 2*i; puts("Voici les elements : "); /* afficher les éléments de t */ for (i=0; i<5; i++) printf("%d ",tab[i]); puts(""); puts("Voici les adresses : "); /* afficher les adresses */ for (i=0; i<5; i++) printf("%p ",tab+i); puts(""); tab[-2]= 18952; printf("%d ",tab[900]); } &tab[-2] 0xbffff37c 0xbffff384 &tab[0] 0xbffff388 2 &tab[1] 4 &tab[2] 0xbffff38c 18 &tab[9] 0xbffff3a8 0xc0000194 … &tab[900] 78 78 78 78
Tableaux : opérations globales ? Attention : AUCUNE opération globale sur un tableau les opérations et les E/S doivent se faire élément par élément En particulier T1==T2 ne teste pas l’égalité de 2 tableaux T1=T2 ne recopie pas les éléments de T1 dans T2 Exemples : tab3.c main() {int i; double t1[10], t2[10]; /* t1 et t2 sont 2 tableaux de 10 reéls */ for (i=0; i<10; i++) {t1[i]=2*i; t2[i]=log(100*i+1); } printf("Emplacement de t1:%p de t2:%p\n",t1,t2); printf("Emplacement de t1[1]:%p de t2[1]:%p\n",t1+1,t2+1); printf("Valeur de t1[0]:%lf de t2[0]:%lf\n",t1[0],t2[0]); if (t1==t2) printf("t1 et t2 sont au meme endroit\n"); else printf("t1 et t2 ne sont pas au meme endroit\n"); for (i=0; i<10; i++) t2[i]= t1[i]; /*Copie de t2 dans t1: on peut remplacer cette ligne par: memcpy(t2,t1,sizeof(t1)); Mais on ne peut pas utiliser t1=t2; */ for (i=0; i<10; i++) printf(”Valeur de t2[%d] : %lf\n”,i,t1[i]); } 79 79 79 79
Tableaux : fonctions de la libc Les fonctions travaillant sur les zones mémoires : comme un tableau est une zone mémoire continue, on peut utiliser ces fonctions avec les tableaux #include <string.h> void * memmove(void *s1, const void *s2, size_t n); Copie n octets de la zone de mémoire src vers la zone dest. Les deux zones peuvent se chevaucher. void *memcpy (void *dest, const void *src, size_t n); idem, mais les zones ne peuvent pas se chevaucher int memcmp (const void *s1, const void *s2, size_t n); compare les n premiers octets des zones mémoire s1 et s2. void *memset (void *s, int c, size_t n); remplit les n premiers octets de la zone mémoire pointée par s avec l’octet c. void swab (const void * from, void * to, ssize_t n); copie n octets de la zone from dans la zone to, en échangeant les octets adjacents 80 80 80 80
Pointeurs Pointeurs 81 81 81
Pointeurs Variable contenant l’adresse d’un autre objet (variable ou fonction) Adresse : numéro d’une case mémoire Déclaration : type_pointé* identificateur; Exemples : int* p1; /* p1 contient l’adresse d’un entier */ double* p2; /* p2 contient l’adresse d’un réel */ ATTENTION : un pointeur doit toujours être initialisé avant d’être utilisé = Il doit contenir une adresse légale : soit celle d’un objet existant soit celle obtenu par une demande d’allocation dynamique soit NULL, qui est la valeur 0. Il est interdit de lire et écrire à l'adresse 0 Remarque : on affiche la valeur d’un pointeur en hexadecimal par printf("Voici la valeur du pointeur %p\n",p); 82 82 82 82
Pointeurs Pointeurs 83 83 Exemple : p1.c main() { int i=0; int* p1=NULL; /* p1: pointeur sur un entier */ p1 = &i; /*p1 pointe i, ie contient l’adresse de i*/ /* ICI, i ou *p1 sont une seule et meme chose */ *p1 = 5; /* identique à i=5; */ printf("Valeur de i:%d, Adresse de i:%p\n",i,&i); printf("Valeur de p:%p, Valeur pointée:%d\n",p1,*p1); printf("Adresse de p:%p\n", &p1); } Adresse 0xbffff388 p1=0xbffff38c p= 0 &p 0xbffff38c i= 0 i= 5 &i … 83 83 83 83
Opérations sur pointeurs Affectation : donner une valeur au pointeur, celle d’une adresse légitime. p1=&i; /* &i : l'adresse de i */ Indirection : trouver la valeur pointée par p. j = *p1; /* *p1 : ce qu'il y a à l'adresse p1 */ Comparaison : == et != : p1==p2; p1 et p2 regardent ils la meme adresse ? <, >, <=, >= : par exemple, p1<p2 sur un meme tableau, p1 est il avant p2 Arithmétique Adresse + entier ==> adresse : p1 +1 est l’adresse de l’élément suivant p1 Adresse - Adresse ==> entier : p2 -p1 est donc le nombre d’éléments entre les adresses contenues dans p2 et p1. Valide uniquement si p1 et p2 sont de meme type. ATTENTION : les pointeurs étant typés, les opérations se font en nombre d’éléments et non en nombre d’octets 84 84 84 84
Adresse et tableaux 85 85 Nom du tableau : adresse du tableau Accès à un élément t[i] : on part de l‘adresse de début du tableau, on ajoute i et on obtient l’adresse du ième élément. L’élément est obtenu par l’opérateur d’indirection * Conséquences L’élément t[i] s ’écrit aussi *(t+i) L’adresse de t[i] s’écrit &t[i] ou bien (t+i) 85 85 85 85
2 manières différentes d'ecrire l'adresse de t[i] Adresses et tableaux Adresse Exemple : p3.c main() { int tab[10]; int i; for (i=0; i<10; i++) tab[i]= 2*i; puts("Voici l’element d’indice 2 : "); printf("%d %d",tab[2],*(tab+2)); puts(""); puts("Voici les adresses : "); for (i=0; i<5; i++) printf("%p %p",tab+i, &tab[i]); } 0xbffff364 &tab[0] 2 &tab[1] 0xbffff368 4 &tab[2] 2 manières différentes d'ecrire l'adresse de t[i] 0xbffff36c 18 &tab[9] 0xbffff388 … 86 86 86 86
Parcourir un tableau avec un pointeur Adresse Exemple : p4.c main() {int* p=NULL; int i; int tab[5]; for (i=0; i<5; i++) tab[i]=2*i+1; p=tab; while (p<tab+5) { printf(" Pointeur: %p ",,p); printf(" Valeur %d",*p); *p=234; printf("Valeur modifiee %d\n",*p); p++; } 0xbffff384 tab[0]=234 1 &tab[0] 3 &tab[1] 0xbffff388 tab[1]=234 tab[2]=234 5 &tab[2] 0xbffff38c 7 tab[3]=234 &tab[3] 0xbffff390 tab[4]=234 9 &tab[4] 0xbffff394 &i 0xbffff398 i= 5 0xbffff38c 0xbffff394 0xbffff390 0xbffff388 p= 0 0xbffff384 &p 0xbffff39c … 87 87 87 87
Tableaux et fonctions Puisqu'une variable tableau a pour valeur l'adresse du tableau, on utilise un paramètre pointeur - et un second paramètre entier pour le nombre d'éléments : // on peut aussi écrire en C : // int minTab(int tab[], int n) int minTab(int * tab, int n) { int i,vmin; for(vmin=tab[0], i=1; i<n; i++) if(vmin>tab[i]) vmin=tab[i]; return vmin; } // ou : // void resetTab( int tab[], int n, int v) void resetTab(int * tab, int n, int v) { for(i = 0 ; i<n; i++) *(tab+i) = v; // ou tab[i] = v ; } main() {int res,i,n=4, t1[4]; for(i=0;i<4;i++) t1[i]=10+i; res=minTab(t1,n); // on passe l'adresse du début du tableau à la fonction printf("minimum:%d \n",res); resetTab(t1, 4, 0); // on passe l'adresse du début du tableau à la fonction } 88 88 88
IV.2. Afficher avec printf printf() est une fonction de la librairie stdio (standard input-output). #include <stdio.h> Afficher un message : printf("Mon message\n"); Afficher une valeur (par exemple la valeur d’une variable) : int i = 4; float x = 4.9; printf("Mon entier i : %d\n", i ); printf("La somme de %d et %f et %d vaut :\t %f \n », i, x, 9, (i+x+9) ); ‘\n’ aller à la ligne Pour info : ‘\t’ tabulation La valeur à afficher %d : le symbole % indique que la lettre suivante est le type du nombre à afficher Un message optionel
int printf(“format”, valeur1, valeur2, ...); IV.2. Afficher avec printf Syntaxe : int printf(“format”, valeur1, valeur2, ...); format : %[*][largeur] [.precision] type_carac spécifier le type du nombre à afficher (type_carac) par %d : nombre entier %c : un caractère %lf : un réel double précision %f : un réel simple précision %p : une adresse (par exemple la valeur d’un pointeur) … En option, on peut préciser le nombre de chiffres que l’on souhaite pour l’affichage par le nombre largeur et le nombre de chiffre apres la virgule pour un réel par le nombre précision – voir man printf
} printf() : le piège ! #include <stdio.h> main() { double x; IV.2. Afficher avec printf Erreur sur le type des nombres à afficher a est un entier, le format demandé (%lf) est celui des réels idem pour x ==> le programme est syntaxiquement correct, la compilation est correcte, mais à l’exécution du programme, l’affichage sera incohérent ! printf() : le piège ! #include <stdio.h> main() { double x; int a = 6; x = 3.1415927; /* Affichage*/ printf("Le nombre a : %lf \n", a); printf("Le nombre x : %d \n", x); }
#include <stdio.h> main() { double x; int a; IV.3. lire avec scanf scanf() est une fonction de la librairie stdio (standard input-output). #include <stdio.h> main() { double x; int a; printf(“donnez un entier puis un réel\n”); scanf(“%d”, &a); scanf(“%lf”, &x); printf(”Les valeurs sont %d ; %lf \n", x, a); scanf(“%d %lf”, &a, &x); printf(”Les nouvelles valeurs sont %d ; %lf \n", x, a); } %d “on attend un entier et l’adresse d’un entier” ATTENTION : scanf attend des adresses mémoire (endroit ou la valeur lue va être stockée en mémoire), d’ou l’utilisation de l’opérateur d’adresse “&” ! Explication orale… Lecture de plusieurs valeurs à la suite…
int scanf(“format”, adresse_variable1, adresse_variable2, ...); IV.3. lire avec scanf Syntaxe : int scanf(“format”, adresse_variable1, adresse_variable2, ...); format : %type_carac spécifier le type du nombre à lire (type_carac) par %d : nombre entier %c : un caractère %lf : un réel double précision %f : un réel simple précision … Attention conseil : Ne pas mettre de message, ni “\n” dans “format”. Par exemple, éviter: scanf(”bonjour %d”, &unentier); scanf(“%d\n”, &unentier);
#include <stdio.h> main() { double x; int unEntier; IV.3. lire avec scanf scanf() : les pièges ! #include <stdio.h> main() { double x; int unEntier; printf("Entrer un nombre reel \n"); scanf("%d", &x); printf("Valeur de x %lf \n", x); printf("Entrer un nombre entier \n"); scanf("%d", unEntier); printf("La variable a vaut : %d\n", unEntier); } Erreur sur le type de la variable ou stocker la valeur lue => valeur lue incohérente, ou arrêt fatal du programme Erreur oubli de l’opérateur d’adresse & Le programme veut stocker ce qui est lu à l’adresse « valeur de unEntier »… Mais ou est-ce en mémoire ? Valeur lue incohérente, ou arrêt fatal du programme (« segmentation fault accès mémoire non autorisé)