48
Programmation en Langage C Laurent Signac https://deptinfo-ensip.univ-poitiers.fr 1 er février 2017

Programmation en Langage C · 2017-02-22 · —OpenClassrooms(exsiteduzéro) ... Contrairement à Python, C est un langage qui nécessite une phase de compilation et d’édition

Embed Size (px)

Citation preview

Programmation en Langage C

Laurent Signachttps://deptinfo-ensip.univ-poitiers.fr

1er février 2017

Programmation en C

2

Table des matières

I Codage 51 Changement de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Nombres négatifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Virgule flottante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

II Premiers pas 91 Environnement de programmation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Programme minimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Tests, boucles et fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

IIILe C, plus en détails 171 Éléments de syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Opérateurs et expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194 Entrées sorties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 Instructions et expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 Instructions et structures de contrôle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 Fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3210 Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3211 Chaînes de caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3312 Retour sur les fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3513 Stuctures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3814 Énumérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3915 Champs de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4016 Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4017 Allocation dynamique de mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4118 Préprocesseur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

IVExercices 45

3

Programmation en C

Notations

Dans ce support de cours, les programmes sont généralement encadrés et typographiés en utilisant une police machineà écrire.Sauf si une indication contraire est donnée, le langage utilisé est le langage C.

Objectif

L’objectif de ce cours est d’acquérir des bases en programmation C. La partie algorithmique ayant été vue en premièreannée (avec la langage Python), nous nous concentrerons ici sur les possibilités offertes par le C.Par la suite, vous pourrez être amenés à utiliser le C, entre autres :

1. pour des questions d’efficacité des programmes (un programme écrit en C est généralement bien plus rapidequ’un programme écrit en Python, bien qu’il soit souvent plus long à écrire).

2. pour des raisons d’environnement : sur un micro-contrôleur, le C est souvent le seul langage supporté par laplate-forme de développement, etc...

Ressources

Le langage C est suffisamment ancien et répandu pour que les ressources ne manquent pas.Dans les ressources en ligne, nous pouvons citer :

— OpenClassrooms (ex site du zéro) http://fr.openclassrooms.com/informatique/cours/apprenez-a-programmer-en-cEn ce qui concerne les ouvrages, très nombreux, citons par exemple :

— Le langage C, norme Ansi, de B. Kerninghan et D. Ritchie— Programmer en Langage C, de C. Delannoy— Méthodologie de le programmation en C, de A. Braquelaire— Le C en 20h, de E. Berthomier et D. Schang (ce livre est un framabook que vous pouvez télécharger librement).

Licence

Ce travail est mis à disposition sous licence Creative Commons by sa (paternité, partage sous les mÃames conditions).

http://creativecommons.org/licenses/by-sa/3.0

4

Chapitre I

Codage

1 Changement de base

L’écriture d’un nombre en base b est formée à partir de b symboles, appelés chiffres. Les 10 chiffres de la base 10 sontpar exemple : 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9.Si l’écriture d’un nombre en base b est : cncn−1...c1c0 (les ci sont les chiffres), sa valeur sera :

v(cn)× bn + v(cn−1)× bn−1 + ... + v(c1)× b1 + v(c0)× b0

où v(ci) est la valeur associée au chiffre (symbole) ci. La valeur du symbole 9 en base 10 est par exemple neuf.Généralement, pour une base b inférieure à 10, on reprend les même chiffres qu’en base 10, avec les mêmes valeurs(mais on n’utilise que les b premiers chiffres). Pour une base b supérieure à 10, on ajoute aux chiffres ordinaires d’autressymboles, comme des lettres. La base 16 utilise par exemple les 16 chiffres suivantes : 0, 1, 2, ..., 9, A, B, C, D, E, F . Lavaleur de B, par exemple, sera alors onze.Voici quelques exemples d’interprétation des nombres. La base est indiquée en indice à la fin du nombre. En l’absencede cet indication, c’est la base 10 qui est utilisée.

1011010|2 = 1× 26 + 0× 25 + 1× 24 + 1× 23 + 0× 22 + 1× 21 + 0× 20 = 905A|16 = 5× 161 + 10× 160 = 90

Blague...Pourquoi les informaticiens confondent Halloween et Noël ?Parce que OCT31 est égal à DEC25.

Pour passer de la base 10 à la base 2, on peut procéder par divisions entières successives (par 2). Voici commenttrouver l’écriture binaire de 90 :

90 ÷2 = 45 reste 045 ÷2 = 2 reste 122 ÷2 = 11 reste 011 ÷2 = 5 reste 15 ÷2 = 2 reste 12 ÷2 = 1 reste 01 ÷2 = 0 reste 1

La séquence des restes successifs, lue du dernier au premier reste donne : 1011010 qui est l’écriture en base 2 de 90.On peut procéder de même en base 16 (en faisant des divisions par 16). Les restes sont alors entre 0 et 15, ce quicorrespond bien aux valeurs des chiffres de la base 16.On peut coder de la même manière les nombres non entiers. Chaque chiffre placé après la virgule est associé à unepuissance négative de la base :

101, 011|2 = 1× 22 + 0× 21 + 1× 20 + 0× 2−1 + 1× 2−2 + 1× 2−3 = 5 + 0, 25 + 0, 125 = 5, 375

Le passage de la base 10 à la base 2, pour la partie fractionnaire, est réalisé par multiplications successives (on reportela partie fractionnaire de chaque résultat sur la ligne suivante, et les parties entières des résultats obtenus forment

5

Programmation en C

l’écriture en base 2). Voici comment écrire 0, 6875 en base 2 :

0, 6875 ×2 = 1, 3750, 375 ×2 = 0, 750, 75 ×2 = 1, 5

0.5 ×2 = 1

Nous prenons les parties entières des résultats dans l’ordre où elles sont obtenues : 1011. Donc 0, 6875 = 0, 1011|2.

Développement binaires infinisNotons que si l’écriture d’un nombre non entier en base 2 est finie, ce sera aussi le cas en base 10 (car les puissancesnégatives de 2 ont toutes une écriture décimale finie). En revanche, si l’écriture décimale est finie, l’écriture en base2 ne le sera pas forcément (elle sera alors périodique). Par exemple :

0, 2 ×2 = 0, 40, 4 ×2 = 0, 80, 8 ×2 = 1, 60, 6 ×2 = 1, 20, 2 ×2 = ...

En conséquence : 0, 2 = 0, 00110011...

Base 2 et base 16La base 2 et la base 16 sont très liées (car 16 est justement une puissance de 2). Ainsi, à chaque groupe de 4 chiffresbinaires (valeur de 0 à 15) correspond exactement un chiffre hexadécimal. Le passage de la base 2 à la base 16 estdonc très simple :

1011010101|2 = 10︸︷︷︸2

1101︸︷︷︸13

0101︸︷︷︸5

|2 = 2D5|16

Blague...Il y a 10 sortes de gens dans le monde. Ceux qui connaissent le binaire et les autres.

2 Nombres négatifs

Les nombres négatifs sont représentés en binaire en utilisant la méthode du complément à deux, qui permet de réalisersimplement des opérations arithmétiques directement sur le codage.Pour représenter un nombre avec la méthode du complément à 2, on choisit auparavant le nombre de bits maximumqui seront occupés par le code. Les valeurs typiques sont multiples de 8.Coder les nombres en complément à 2 sur 8 bits revient à partager les 256 codes possibles en deux parties. Ceux dontle bit de poids fort est 0 représenteront les nombres positifs de 0 à 127 (codage immédiat en base 2 avec les 7 bitsrestants). Ceux dont le bit de poids fort est 1 représenteront les nombres négatifs de -128 à -1.Dans le cas d’un nombre négatif n, on obtient le complément à 2 de n en calculant la représentation binaire du nombre256 − |n|. Cette représentation est obtenue rapidement en écrivant sur 8 bits la représentation binaire de la valeurabsolue de n, puis en inversant les 0 et les 1, puis en ajoutant 1 :

Exemple de codage en complément à deuxNous souhaitons coder le nombre -66 en complément à deux sur 8 bits. On commence par écrire sa valeur absolueen binaire, sur 8 bits :

01000010

Puis on échange les 0 et les 1 :10111101

Puis on ajoute 1 (attention aux éventuelles retenues) :

10111110

6

Notes de cours, Ensip, Mee2

Le résultat est le codage en complément à 2 sur 8 bits de −66. On remarque que c’est aussi l’écriture binaire de256− 66 = 190.

3 Virgule flottante

Nous avons vu qu’il était possible de représenter les nombres à virgule en binaire. Cependant, l’utilisation très fréquentedes calculs à l’aide de nombres non entiers, et le soucis de coder ces nombres en un minimum de bits a donné naissanceau codage en virgule flottante.Un nombre à virgule flottante est codé par trois nombres binaires appelés signe, mantisse et exposant, qui ont tousune taille maximum finie, en nombre de chiffres. La norme en vigueur pour le codage en virgule flottante est la normeIeee754 que nous allons détailler.Un nombre décimal N à stocker doit d’abord être écrit (en binaire) sous la forme (−1)S × 1.m × 2e. En d’autrestermes, on écrit N en binaire (avec une virgule), puis on place cette virgule juste après le premier 1, et on en déduitl’exposant :

11001.111101 = 1.100111101× 24

0.001101 = 1.101× 2−3

Les nombres à coder sont donc : la partie décimale m, la valeur de l’exposant et le signe (0 pour un nombre positif et1 pour un nombre négatif).Selon que les nombres sont en simple (32 bits) ou double (64 bits) précision, c’est la taille du codage de m, et la tailledu codage de l’exposant qui varient. En simple précision, on utilise 8 bits pour coder l’exposant, et 23 bits pour m.En double précision on utilise 11 bits pour l’exposant et 52 pour m. De plus, l’exposant n’est pas codé directement.On lui ajoute 127 en simple précision et 1023 en double précision avant de le coder (pour obtenir un nombre positif).Ainsi, en simple précision, l’exposant -127 sera codé par 0, l’exposant 0 par 01111111 et l’exposant 128 par 11111111.Dans la suite, nous supposerons que le codage est en simple précision. Les nombre normalisés sont ceux pour lesquelles lecodage de l’exposant (E dans la suite 1) vérifie 0 < E < 255. Le nombre représenté vaut alors N = (−1)S×2E−127×1.m.Les nombres dénormalisés sont ceux pour lesquels E est nul alors que m non. Cette distinction permet de représenterdes nombres plus petits que 2−126 × 1.m en spécifiant qu’un nombre dénormalisé vaut : 2−126 × 0.m. Si E = 0 etm = 0, le code représente 0. Enfin, si E = 255 et m 6= 0, le code ne représente pas un nombre 2 et si E = 255 et m = 0,la valeur codée est (−1)S∞.Le plus petit nombre non nul positif vaut :

2−126 × 0. 0 · · · 0︸ ︷︷ ︸22 fois

1 = 2−149 ≈ 1.4013× 10−45

Le plus grand nombre positif vaut :

2127 × 1. 1 · · · 1︸ ︷︷ ︸23 fois

= 2128 − 2104 ≈ 3.4× 1038

Entre deux nombres, il y a un «trou». Par exemple :

0 11111110 0 · · · 0︸ ︷︷ ︸23 fois

= 2127

et le nombre suivant est :0 11111110 0 · · · 0︸ ︷︷ ︸

22 fois

1 = 2127 + 2104

Par conséquent, entre ces deux nombres successifs, il y a un trou de dimension 2104 ( !) sans aucun nombre.En résumé, le codage en simple précision permet de représenter de manière approchée les nombres entre −3.4× 1038

et 3.4× 1038, tout en atteignant des valeurs aussi petites (en valeur absolue) que 1.4× 10−45.Il est important de retenir :

— que c’est le codage le plus utilisé pour les nombres non entiers ;— qu’il représente les nombres de manière approchée, et qu’en virgule flottante, les calculs ne sont (pratiquement)

jamais exacts.

1. À ne pas confondre avec e. On a en simple précision : E = e + 127.2. NaN : not a number.

7

Programmation en C

FlopsUne indication de vitesse des processeurs donnée en flops (ou un de ses multiples) fait référence au nombred’opérations en virgule flottantes par seconde (FLoating point Operations Per Second).L’ordinateur le plus rapide du monde (depuis novembre 2016, c’est le TaihuLight) a une vitesse estimée de 93,59PetaFlops (1015) soit plus de 93 millions de milliards d’opérations en virgule flottante par seconde. Un ordinateurdomestique atteint des vitesses de quelques dizaines de GigaFlops (109 Flops)

8

Chapitre II

Premiers pas

Dans ce chapitre, nous n’allons pas entrer dans le détail de chaque construction du C, mais présenter les élémentsessentiels qui permettent de commencer à programmer. Nous entrerons dans les détails au chapitre suivant.

1 Environnement de programmation

Actuellement, les salles sont équipées avec l’environnement wxDevC++. Il existe des alternatives intéressantes :— Code Blocks : http://www.codeblocks.org/— Orwell DevC++ : http://orwelldevcpp.blogspot.com.au/— Visual Studio Express (attention, le compilateur n’est plus MingW dans ce cas)

Certains préférerons installer le compilateur d’une part (http://www.mingw.org/wiki/Getting_Started), et utiliserun autre éditeur comme : Notepad++, Geany ou Scite.Pour le système gnu/Linux, le compilateur est généralement gcc (il est probablement déjà installé sur votre distributionou tout au moins facilement installable). Les environnements de développement ne manquent pas : Emacs, Code Blocks,Geany,...Le contenu des programmes est le même quel que soit l’Edi 1 que vous choisissez...

1.1 Utilisation de wxDevC++

Créer un nouveau programme avec wxDevC++ consiste à démarrer un nouveau projet (Fichiers/Nouveau Projet(fig.II.1). Au début, le type d’application à sélectionner sera Console Application.Après avoir donné un nom au projet (de préférence sans espace et sans accent), wxDevC++ ouvre une fenêtre (fig.II.2) contenant le fichier principal du projet (qui s’appelle par défaut main.c). Au premier enregistrement, nous auronsla possibilité de renommer ce fichier main.c avec un nom plus évocateur 2 (fig. II.3).Nous pouvons modifier ce programme principal (fig.II.4), puis demander à wxDevC++ de construire un exécutable(menu Exécuter/Tout reconstruire).La compilation et l’édition de liens doivent se dérouler sans aucune erreur. Le programme peut alors être testé par lebiais du menu Exécuter/Exécuter (fig.II.5) .

1.2 Programmer sous Linux

Les différentes distributions Linux offrent un excellent confort de développement, une fois l’apprentissage de départeffectué. Un simple éditeur, comme Geany permet, sans configuration particulière, de commencer à programmer(fig. II.6).Le principe est toujours le même : construire le projet (compilation et édition de lien), puis tester (fig. II.7).

1. Un Edi (Ide en anglais) est un Environnement de Développement Intégré : un logiciel qui permet d’éditer du code et propose unaccès direct aux outils de compilation, test, analyse...

2. Il n’est pas absurde de reprendre le nom du projet.

9

Programmation en C

Figure II.1 – Ouverture d’un nouveau projet

Figure II.2 – Fichier C principal

10

Notes de cours, Ensip, Mee2

Figure II.3 – Enregistrement du fichier C principal

Figure II.4 – Ajout d’une ligne au programme principal

11

Programmation en C

Figure II.5 – Exécution de notre programme

Figure II.6 – Compilation et édition de lien sous Geany (F9)

Figure II.7 – Exécution du programme depuis Geany (F5)

12

Notes de cours, Ensip, Mee2

2 Programme minimal

Entrons directement dans le vif du sujet avec un premier programme écrit en C, le fameux Hello World :

#include <stdio.h>int main(void) {

puts("Hello World\n");getchar();return 0;

}

Pour tester ce programme, vous devez l’entrer dans l’edi, le sauvegarder (par exemple avec le nom hello.c), lecompiler, faire l’édition de liens, de manière à obtenir un fichier qui se nommera probablement hello.exe. Ce dernierfichier est un programme exécutable que vous pouvez lancer depuis l’edi ou depuis le gestionnaire de fichiers endouble-cliquant dessus.Contrairement à Python, C est un langage qui nécessite une phase de compilation et d’édition de liens. Ces étapestransforment le ou les fichiers sources en programme exécutable (le .exe sous Windows). Le programme exécutableobtenu est indépendant (aux bibliothèques liées dynamiquement près) et fonctionnel. Pour s’en servir, l’utilisateur finaln’a besoin que de cet exécutable (et de quelques bibliothèques éventuellement) : il n’a plus besoin du compilateur C.Notre programme d’exemple comporte une directive à l’adresse du préprocesseur (c’est la ligne qui commence par #),et une fonction. Cette fonction est spéciale, c’est la fonction main de notre programme : son point d’entrée.La fonction main ne comporte que 3 lignes :

— la première affiche Hello World sur l’écran ;— la seconde marque une pause jusqu’à ce que l’utilisateur ait pressé une touche ;— la dernière quitte en envoyant un code de retour, 0, qui informe le système d’exploitation que tout s’est bien

déroulé.Les fonctions puts et getchar ne sont pas des instructions du C, mais des fonctions qui appartiennent à la bibliothèquestandard. Précisément ces deux fonctions font partie de la bibliothèque standard d’entrées/sorties. La première ligneindique au préprocesseur dans quel fichier trouver les prototypes des fonctions utilisées dans le programme. Le fichierstdio.h est un fichier d’en-tête (c’est le sens du .h (header)) qui contient diverses déclarations, dont celles des fonctionsde la bibliothèque standard d’entrées-sorties dont font partie les fonctions puts et getchar.Les programmes que nous écrirons suivront toujours le même squelette :

/* Directives préprocesseur */#include ....#define ...

/* Fonctions annexes */

/* Fonction principale */int main(void) {

...

return 0;}

Chaque fichier source comporte généralement des directives au préproceseur (commençant par #), éventuellement desdéclarations globales, puis des fonctions, et (très souvent à la fin) la fonction principale main qui renvoie un entier,par convention 0 pour indiquer que tout s’est bien déroulé.

3 Variables

Comme dans la plupart des langages, le C utilise des variables. Les règles de nommage sont sans surprise (consultezla norme pour les connaître), et le C est sensible à la casse.En C, le typage est (assez) fort et statique. Cela signifie que les conversions de types doivent être (souvent) explicites,et que le type des variables est fixé à leur déclaration. Un point important par rapport à la programmation Pythonest justement la déclaration préalable des variables.

13

Programmation en C

Voici un exemple :

int a,b;float c;char d=’E’;

a=5;b=6;c=1.01;

Les trois premières lignes permettent de déclarer les deux variables entières (int) a et b, le nombre à virgule (float) cet le caractère (char) d. Au passage, nous voyons qu’on peut affecter une variable au moment de sa déclaration (c’estle cas pour d).

Affichage du contenu des variablesOn utilise généralement la fonction printf pour afficher le contenu des variables. Cette fonction est un peu difficileà utiliser. On lui passe en paramètre le format, qui décrit ce qui va être affiché, et les variables, qui seront substituéesaux chaînes de la forme %... dans le format.Les formats les plus utilisés sont %c pour les caractères, %i (ou %d) pour les entiers, %f pour les nombres à virgule(on peut restreindre le nombre de décimales à afficher) et %s pour les chaînes de caractères.

int a=5;float p=3.1415926535;char c=’P’;

printf("a vaut %i et p vaut %f\n",a,p);printf("ou plutôt devrais-je dire que p vaut environ %.4f\n",p);printf("De son côté, c contient %c\n",c);

4 Tests, boucles et fonctions

4.1 Tests

Les tests utilisent la construction ordinaire if, avec un else facultatif. Les blocs sont matérialisés par des accolades.La condition doit être entre parenthèses.

IndentationEn Python, les blocs étaient matérialisés par l’indentation du code. Puisqu’en C, les blocs sont matérialisés pardes accolades, l’indentation n’est plus nécessaire. Il est néanmoins habituel d’indenter proprement le code pour enfaciliter la lecture 3. En particulier, il est peu probable que le chargé de tp accepte (et il aura bien raison) de relireun programme C mal indenté...

Voici un exemple de test :

int a=rand()%100; /* nb entier au hasard entre 0 et 99 */if (a%2==0) {

printf("%d est pair\n",a);}

else {printf("%d est impair\n",a);}

La seconde partie de l’instruction (else) est facultative (on ne le dit jamais assez...).

2. C’est cette constatation qui a poussé le concepteur de Python à utiliser l’indentation aussi comme élément syntaxique.

14

Notes de cours, Ensip, Mee2

4.2 Boucles

La boucle while est très ordinaire (comme avec un if, la condition est entre parenthèses) :

int a=rand()%100;int c=-1;while (a!=c) {

printf("Entrez un nb entre 0 et 99 ");scanf("%d",&c);if (c>a) printf("Votre nombre est trop grand\n");if (c<a) printf("Votre nombre est trop petit\n");}

printf("Bravo;");

La ligne scanf("%d",&c) permet à l’utilisateur d’entrer un nombre entier, qui sera stocké dans la variable c.

4.3 Fonctions

Lors de l’écriture d’une fonction, il faut préciser le type des paramètres, et le type de valeur de retour :

int pgdc (int a, int b) {if (a==b) return a;if (b>a) return pgdc(b%a,a);return pgdc(a%b,b);

}

15

Programmation en C

16

Chapitre III

Le C, plus en détails

1 Éléments de syntaxe1. les instructions simples se terminent par un point virgule ;

2. les conditions sont entre parenthèses (dans les if et while)3. les blocs qui suivent if, else, switch, while, for ou la définition d’une fonction (dès qu’il est nécessaire de

limiter un bloc d’instructions) sont placés entre accolades. Le bloc fait généralement partie de l’instruction, sibien qu’on ne trouve pas de ; avant une accolade ouvrante.

2 Types

Le C possède plusieurs types numériques, et différentes longueurs de codages. Nous allons les énumérer ici :

Les entiers peuvent être signés ou non. Par défaut, ils sont signés. Ils existent en plusieurs longueurs de codage :short, int, long et long long. Chacun de ces types peut être préfixé par unsigned pour indiquer que les nombres nesont pas signés. La longueur du codage dépend du type de machine utilisé. Le programme suivant utilise l’opérateursizeof pour indiquer la taille en octet de ces types :

#include<stdio.h>int main(void) {

printf("%d %d %d %d\n",sizeof(short),sizeof(int),sizeof(long),sizeof(long long));return 0;

}

Sur la machine de test, ce programme affiche :

2 4 8 8

Les constantes entières peuvent être données en décimal (43), en hexadécimal (0x2B) ou en binaire (0b101011).

Les nombres à virgule sont représentés par un codage en virgule flottante (norme ieee 754) en simple (float) oudouble (double) précision. Les constantes virgule flottante peuvent être représentées en notation scientifique (2.54e-5)ou non (0.00023) mais doivent toujours comporter un point décimal (42 est un entier alors que 42.0 ou même 42. et.37 sont des nombres à virgule flottante).

Les caractères (type char), sont codés sur un octet (par défaut signé, mais qui peut être précédé de unsigned.Il y a équivalence entre un caractère et son code ascii. Selon qu’on l’affiche (avec printf par exemple) comme unnombre ou comme un caractère, la même variable de type char donnera le code ascii (un nombre) ou le caractère.Les constantes caractères sont notées entre guillemets simples ’. Les caractères spéciaux peuvent être codés par des\ : ’\n’ pour un retour chariot par exemple. Quelques caractères spéciaux sont mentionnés dans la table III.1. Lescaractères peuvent aussi être donnés sous la forme de leur code ascii en décimal : ’\65’ ou en hexadécimal : ’\x41’pour ’A’ par exemple.

17

Programmation en C

\a bip\b backspace\n saut de ligne\t tabulation\\ antislash\’ guillemet simple

Table III.1 – Quelques caractères spéciaux en C

Booléens et complexes La norme C99 a ajouté les deux types complex (avec les variantes float et double) etbool. Les seules constantes de type bool étant true et false.

2.1 Conversions implicites et cast

Les opérations arithmétiques fonctionnent sur des opérandes de même type (int avec int ou float avec float parexemple).Il est pourtant possible d’additionner un int et un float. Pour cela, le compilateur réalise des conversions de typesimplicites, uniquement lorsque c’est nécessaire. Les données sont converties de telle manière que la valeur soit dégradéele moins possible (voir plus loin pour un exemple où la donnée est dégradée). Par exemple, convertir un float en intentraîne des modifications de valeur (généralement) plus gênante que convertir un int en float.La hiérarchie de conversion est la suivante :int→long→float→double

Une simple affectation peut en outre mener à une conversion, systématiquement vers le type de la variable à affecter :

int a;a=4.8;

Ce qui précède est valide. La valeur 4.8 est convertie en int avant d’être affectée à la variable a (qui contiendra donc4).Enfin, un opérateur de conversion explicite est disponible (opérateur de cast). La priorité de cet opérateur est plusélevée que celle des opérations arithmétique. En ajoutant à ceci les conversions implicites, nous pouvons étudier lebout de code suivant :

int n=5,d=9;float f1,f2,f3,f4;

f1=n/d;f2=(float) n/d;f3=(float) (n/d);f4=(float) n / (float) d;printf("%f %f %f %f\n",f1,f2,f3,f4);

— Lors du calcul de f1, on divise deux int, le résultat est donc un int (0), converti en float pour l’affectation :0.0

— Lors du calcul de f2, n est converti en float (présence du cast). Nous avons une opération entre un float etun int. L’entier est donc converti en float avant de donner un résultat de type float, affecté à f2 : 0.555...

— À cause des parenthèses, le cast est appliqué au résultat qui, nous l’avons vu pour f1 est nul. Le 0 est doncconverti en 0.0 puis est affecté à f3.

— Dans le dernier cas, les deux opérandes sont converties en float par le cast. Le résultat est donc 0.5555...

Le programme précédent affiche donc : 0.0 0.5555 0.0 0.5555.Voici un autre exemple, qui convertit une valeur entière de degrés Celcius vers des degrés Fahrenheit :

int c=46;int f;

f=32+c*1.8;

18

Notes de cours, Ensip, Mee2

Op. Signification+ addition- soustraction* multiplication/ division% reste de la division entière

Table III.2 – Opérateurs arithmétiques en C

Dans le calcul qui précède, les règles de priorité des opérateurs arithmétiques impliquent que * est la première opération.c étant un int et 1.8 un float, c est converti en float et le résultat de la multiplication est un float : 82.8. Puis,il y a une addition entre un int (32) et un float. La valeur 32 est donc convertie en float et le résultat vaut 114.8.Ce résultat étant affecté à un int, une nouvelle conversion est réalisée, et f contiendra finalement 114.

Conversion avec perte d’informationNotons que si une conversion de int vers float occasionne moins de dégâts qu’une conversion de float vers int,elle n’est pas pour autant anodine. Si les float et les int sont représentés sur le même nombre d’octets (ce qui estle cas sur ma machine : 4 octets), puisque les float contiennent des nombres à virgule qui ne sont pas dans les int,ils contiennent nécessairement, pour compenser, des entiers en moins... :

int i;float f;i=2147483640;f=1.0*i;printf("%d %f\n",i,f);

Le programme précédent affiche :

2147483640 2147483648.000000

Il y a bien une erreur, même si elle est faible (moins de 0.0000004 %).

2.2 Occupation mémoire

L’opérateur sizeof permet de connaître l’occupation mémoire d’une variable ou d’un type :— si n est déclaré ainsi : int n; alors sizeof(n) vaudra 4, la taille en octet d’un entier de type int 1 ;— sizeof(float) vaut 4, puisque les flottants simple précision sont codés sur 4 octets.

3 Opérateurs et expressions

3.1 Opérateurs de base

Le C dispose d’opérateurs arithmétiques (table III.2), de comparaisons (table III.3), logiques (table III.4) et de mani-pulations de bits (table III.5).Le résultat d’un opérateur de comparaison est généralement vrai ou faux. Le C (avant la norme C99) ne disposait pasde type booléen, et ce sont donc des nombres qui sont utilisés avec la convention suivante : 0 vaut faux et toute valeurnon nulle vaut vrai. Le résultat d’un opérateur de comparaison sera 0 pour faux et 1 pour vrai. Ainsi : 3<6 vaut 1.De même, le connecteurs logiques manipulent aussi des nombres avec les mêmes conventions. Ainsi, a<b && b<c vaudra1 si a, b, c sont 3 nombres strictement croissants et 0 sinon.Tous ces opérateurs peuvent être mêlés dans des instructions et des expressions. S’il est préférable, dans le doute, deparenthéser, on peut se reporter aux règles de priorité (règles de précédence) données table III.6.Ainsi cette expression : a=5*3+1>6+2&&5&2 se lit : a = ( ((5*3)+1)> (6+2)) && (5&2) ce qui n’est pas forcémenttrès lisible non plus... et vaut 0.

1. Cette taille est variable selon les plates-formes. La valeur 2 est courante aussi...

19

Programmation en C

Op. Signification> strictement supérieur à< strictement inférieur à>= supérieur ou égal à<= inférieur ou égal à== égal à!= différent de

Table III.3 – Opérateurs de comparaison en C

Op. Signification&& et|| ou! non

Table III.4 – Connecteurs logiques en C

Op. Signification& et| ou inclusif^ ou exclusif<< décalage à gauche>> décalage à droite~ complément à un

Table III.5 – Manipulations de bits en C

++ -- ! ~* / %+ -<< >>< > <= >=== !=&^|&&||? : (opérateur ternaire)= += -= *= /= (toutes les affectation),

Table III.6 – Priorité de quelques opérateurs (décroissante de haut en bas)

20

Notes de cours, Ensip, Mee2

Exercice : Arithmétique, comparaison, et connecteurs logiques

Écrivez une expression booléenne indiquant si n est uneannée bissextile. Les années bissextiles sont les annéesmultiples de 4, excepté si elles sont aussi multiples de100 sans l’être de 400. Question subsidiaire : avec cecalcul, quelle est la durée moyenne d’une année civile ?

Exercice : Manipulations de bits

Calculez :— 186 & 203— 186 | 203— 186 ^ 203— 186 << 2— 203 >> 3

3.2 Autres opérateurs

Le C possède un opérateur conditionnel ternaire, utilisant les symboles ? et : . Cet opérateur permet d’écrire desexpressions dont le calcul est différent selon qu’une condition est remplie ou non :

condition ? expr1 : expr2L’expression qui précède aura la valeur de expr1 si condition est vraie et la valeur de expr2 sinon.Par exemple : a>b?a:b vaut nécessairement le plus grand des deux nombres a et b.

Suite de CollatzLa suite de Collatz est définie pour tout entier u0 supérieur ou égal à 1 puis par récurrence :

un+1 ={

un/2 si un est pair3un + 1 sinon

Si u est un terme de la suite de Collatz, le terme suivant vaut donc : u%2==0?u/2:3*u+1Puisque ce qui précède est une expression, on peut l’affecter à une variable. La portion de programme qui suitcalcule donc des termes de la suite de Collatz et les affiche :

int u=27;while (u!=1) {

u=u%2==0?u/2:3*u+1;printf("%d\n",u);

}

Les opérateurs d’affectation élargie constituent des raccourcis d’écriture. Ils sont donnés tableau III.7.

3.3 Programmation de micro-contrôleurs

La manipulation des valeurs au niveau du bit est courante lorsqu’on programme un micro-contrôleur. Régler une broched’un port particulier en sortie, par exemple la broche 2 (donc la troisième, la numérotation commence généralementà 0) du port 6, se résume souvent à mettre à 1 un bit particulier (probablement le bit 2 dans notre exemple) dans

21

Programmation en C

Op. Significationi++ i=i+1i-- i=i-1i+=b i=i+bi-=b i=i-b’i*=b i=i*bi/=b i=i/bi%=b i=i%ba|=b a=a|ba^=b a=a^ba&=b a=a&ba<<=b a=a<<ba>>=b a=a>>b

Table III.7 – Opérateurs d’affectation élargie

une variable (stockée à une adresse bien précise) correspondant à la configuration du port 6. La plupart des valeursnumériques sont de plus définies dans des fichiers d’en-tête (spécifiques au micro-contrôleur).Plus précisément, la configuration en sortie de la broche 2 du port 6 d’un microcontrôleur type MSP 430 est réaliséeen mettant à 1 le bit 2 à l’adresse 0x36. L’accès à cette adresse se fait via une variable particulière, P6DIR, définiedans un fichier d’en-tête. Configurer la broche 2 du port P6 en sortie revient donc à mettre à 1 le bit 2 de la variableP6DIR, ce qui peut être réalisé ainsi :

P6DIR = P6DIR | 0b00000100

On peut aussi écrire (ou lire...) de manière équivalente :

P6DIR = P6DIR | 4P6DIR = P6DIR | 0x04

Les constantes étant souvent définies elles aussi dans des fichiers d’en-tête (voir #define dans la section 18), il n’estpas rare d’écrire ou de rencontrer :

P6DIR = P6DIR | BIT4

ou en utilisant les opérateurs d’affectation élargie :

P6DIR |= BIT4

4 Entrées sorties

Dès les premiers programmes nous aurons besoin, soit d’avoir un retour sur le terminal d’une valeur calculée, soitd’utiliser une valeur entrée par l’utilisateur.Les affichages sont réalisés par la fonction printf qui prend en premier argument le format (une chaîne de caractères),et en arguments supplémentaires, des expressions. Le format permet de préciser le type d’affichage que l’on souhaitepour chacune des expressions : nombre entier, caractère, nombre à virgule etc... :

float a;char c;int i;printf("Bonjour à tous\n");printf("Bonjour, vive %i\n",42);a=42.;i=42;printf("Attention, %i n’est pas %f\n",i,a);printf("Un petit 42 scientifique : %e\n",a);c=’\x42’;printf("Mais 42, c’est aussi (en héxa) %x, ou %c\n",c,c);

22

Notes de cours, Ensip, Mee2

Format Signification%d entier décimal%ld entier long décimal%09d entier décimal sur 9 chiffres complété par des 0 à gauche%f nombre à virgule flottante

%.3f nombre à virgule avec 3 chiffres après la virgule%e nombre à virgule flottante en notation scientifique%s chaîne de caractères (délimitée par un espace)%c caractère%x nombre hexadécimal

Table III.8 – Quelques exemples de formats

Quelques exemples de formats sont donnés dans la table III.8.La fonction scanf marque une pause et lit des valeurs formatées sur l’entrée standard. Voici quelques exemples :

int n;float v;printf("Entrez un nombre entier puis un nombre à virgule");scanf("%i%f",&n,&v);printf("Vous avez entré %i et %f\n",n,v);

Le fait que les variables doivent être préfixées par & sera expliqué dans le chapitre sur les pointeurs.Lors qu’on souhaite saisir des chaînes de caractères, la fonction scanf n’est pas toujours pratique, car elle découpel’entrée en mots (en fait la saisie s’arrête sur tout type de blanc : espace, tabulation, retour chariot....). Il est alorsconseillé d’utiliser la fonction fgets, à laquelle on fournit un endroit où stocker la chaîne de caractères (voir section 11sur les chaînes), la taille maximum de la saisie et le flot d’entrée (voir section 8). Pour une saisie au clavier, le flotd’entrée sera stdin :

char texte[256];printf("Entrez une phrase (255 caractères max)\n");fgets(texte,256,stdin);printf("Merci d’avoir dit :\n%s\n",texte);

5 Instructions et expressions

Dans de nombreux langages, on ne confond pas ces deux notions. En C, la plupart des instructions sont aussi desexpressions et peuvent à ce titre être utilisées dans un calcul.Une simple affection i=5; est une instruction. Mais c’est aussi une expression, qui vaut la valeur affectée (donc 5).Nous pouvons donc écrire j=(i=5). Le symbole d’affectation étant associatif à droite 2, on omet généralement lesparenthèses pour écrire : j=i=5;.Les opérateurs d’affectation élargie, comme i++ sont des instructions, mais aussi des expressions. Ainsi, dans leprogramme suivant :

i=5;j=i++;printf("%d %d\n",i,j);

la seconde ligne ajoute 1 dans i et réalise une affectation. Mais dans quel ordre ? La différence entre i++ et ++i résidejustement dans cet ordre. Si nous écrivons i++, c’est la valeur de i initiale qui est utilisée dans l’expression, puis iest augmenté de 1. Si au contraire nous écrivons ++i, alors la valeur de i est d’abord augmentée, puis cette nouvellevaleur est utilisée dans l’expression.Le programme précédent affiche donc 6 5. Si nous avions écrit ++i, il aurait affiché 6 6.Une instruction simple (qui se termine par un ;) peut être composée de plusieurs expressions, séparées par des virgule.Auquel cas, les expressions sont évaluées de manière séquentielles, de gauche à droite, et l’instruction aura pour valeurcelle de la dernière expression évaluée. Ainsi le programme suivant affichera 5 10 4.

2. Associatif à droite signifie que a=b=c équivaut à a=(b=c) et non pas à (a=b)=c (associatif à gauche).

23

Programmation en C

Condition ?

Instruction

oui

non

Figure III.1 – Un if simple en C

int i=4,b,a;b=(a=i++,2*i);printf("%d %d %d\n",i,b,a);

En effet, lors de l’évaluation de la ligne : b=(a=i++,2*i); on commence par évaluer la parenthèse 3. Il y a deuxexpressions séquentielles. La première a=i++ copie i dans a (qui vaut maintenant 4) et augmente i de 1 (qui vautmaintenant 5). Puis, la seconde expression, 2*i est évaluée, et vaut 10. La valeur des deux expressions séquentiellesétant celle de la dernière, la parenthèse vaut 10. Cette valeur est affectée à b qui vaut maintenant 10.Naturellement, cet exemple est volontairement tordu et permet d’illustrer les possibilités et les pièges. Un programmeurn’a jamais intérêt (sauf dans le cadre d’un jeu) à écrire un programme difficile à comprendre, pour lui ou pour unautre. Hormis dans le cadre d’un exercice de style particulier, c’est un défaut d’écrire des programmes qui sont difficilesà comprendre, et non pas une preuve des grandes capacités du programmeur.

6 Instructions et structures de contrôle

On distingue en C trois types d’instructions :

1. Les instructions simples, comme a=3; ou i=collatz(27);. Elles se terminent nécessairement pas un ; 4

2. Les instructions structurées (tests, boucles...), qui contiennent souvent un ou plusieurs blocs et que nous allonsdétailler dans la suite.

3. Les blocs, qui sont délimités par des accolades, et qui peuvent contenir des instructions simples, des blocs etdes instructions structurées (il peut aussi être vide...).

L’acquisition de ce vocabulaire est nécessaire à la compréhension de la syntaxe (Faut-il un ; à tel endroit ? Faut-il desaccolades ?...)

6.1 Tests

L’instruction if existe sous deux formes, selon qu’elle est accompagnée de else ou non.La syntaxe générale 5 est :

if (condition)instruction

if (condition)instruction 1

elseinstruction 2

Ces deux instructions structurées sont respectivement équivalentes aux organigrammes III.1 et III.2.

3. Les parenthèses sont ici nécessaires, car en leur absence, l’instruction serait équivalente à (b=a=i++),2*i; car la précédence de = estsupérieurs à celle de ,.

4. Une instruction simple peut être composée, et les morceaux qui la composent sont séparés par des , comme dans : i=4,b=3*i+1;5. Gardez à l’esprit qu’une «instruction» peut être : une instruction simple, un bloc, ou une instruction structurée...

24

Notes de cours, Ensip, Mee2

Condition ?

Instruction 2Instruction 1

non oui

Figure III.2 – Un if-else en C

ExerciceParmi les portions de code suivantes, relevez celles qui sont valides et celles qui ne le sont pas. Donnez les raisonsde leur invalidité.

// snippet 1if (a==3) printf("Quel beau a...\n");

// snippet 2if a==b {

printf("a et b...\n");printf("...sont égaux...\n")

}

// snippet 3if (a==b)

printf("a et b...\n");printf("...sont égaux...\n");

elseprintf("a et b diffèrent\n");

// snippet 4if (a==b)

printf("a et b...\n");printf("...sont égaux...\n");

//snippet 5if (a<=b)

if (b<=c) printf("Triés\n");else printf("Non Triés\n");

else printf("Non Triés\n");

L’instruction switch est une instruction de branchement, qui rend certaines portions de code plus lisibles. Voici sasyntaxe générale :

switch (expression) {case constante_0 :

instructions...case constante_1 :

instructions......default :

instructions...}

La partie default est facultative. Le déroulement est le suivant : l’expression est évaluée (par exemple à 42), puis lasérie de case est examinée. Le branchement est réalisé au premier case qui convient (celui pour lequel la constante

25

Programmation en C

vaut 42). Les instructions qui suivent ce case sont exécutées. Attention, il s’agit d’une série d’instructions, et non d’unbloc.

Penser au break

Attention, une fois le branchement effectué, la série d’instruction est exécutée, jusqu’à la fin du bloc (tous les casesont exécutés à partir du premier convenable). La suite d’instruction se termine donc généralement par l’instructionbreak, qui permet de ressortir du bloc switch.

Voici un exemple d’utilisation :

scanf("%d",&a);switch(a%3) {

case 0 :printf("a est un multiple de 3\n");break;

case 1 :printf("a est congru à 1 modulo 3\n");break;

default :printf("a n’est pas congru à 1 ou 0 modulo 3\n");break;

}

6.2 Boucles

La boucle while s’écrit :

while (expression) instruction

Ici aussi, l’instruction peut être simple, structurée, ou peut être un bloc.Voici un exemple d’utilisation :

scanf("%d",&n);while(n%7!=0) {

printf("%d n’est pas un multiple de 7...\n",n);n=n+1;}

printf("Le premier multiple de 7 trouvé est %d\n",n);

Le code qui précède est équivalent à l’organigramme de la figure III.3.Il existe un second type de boucle, pour lequel la condition est évaluée en fin de boucle :

do instructionwhile (expression);

Voici un exemple d’utilisation :

a=rand()%100;do {

printf("Entrez un nombre ");scanf("%d",&p);if (p>a) printf("Trop grand...\n");if (p<a) printf("Trop petit...\n");} while (p!=a);

printf("Bien joué...\n");

L’organigramme correspondant est donné figure III.4.

26

Notes de cours, Ensip, Mee2

Début

n← nb entier

n%7 6= 0

afficher (n,"n’est pas un multiple de 7")

n← n + 1

afficher ("Le premier mul. de 7 est",n)

oui

boucle

non

Figure III.3 – Recherche du premier multiple de 7 supérieur ou égal à un nombre donné... renvoie doncn+(n%7==0?0:7-n%7)

Début

a←nb au hasard entre 0 et 99

afficher "Entrez un nombre"

p←saisie entier

p > a ? aff "Trop grand"

p < a ? aff "Trop petit"

p 6= a ?

aff "Bien joué"

non

non

oui (boucle)

oui

oui

non

Figure III.4 – Jeu du «c’est plus ou c’est moins»

27

Programmation en C

Expression 1

Expression 2 ?

Instruction

Expression 3

ouiboucle

non

Figure III.5 – Une boucle for en C

Le langage C dispose aussi d’une instruction for :

for (expression1 ; expression2 ; expression3)instruction

Comme précédemment, instruction peut être simple, structurée ou bloc. Les trois expressions, qui figurent dans lesparenthèses sont évaluées à différents moments :

— expression1 est évaluée une seule fois, avant d’entrer dans le boucle.— expression2 est évaluée avant chaque tour de boucle (y compris le premier). Le tour est effectuée si expression2

est vraie. Sinon, on passe à la suite.— expression3 est évaluée à chaque tour, après l’exécution de instruction.

L’organigramme correspondant est donné figure III.5.On peut construire une boucle équivalente avec un while 6 :

expression1while (expression2) {

instructionexpression3

}

Notons enfin que chacune des expressions dans la parenthèse du if peut être vide.Nous avons vu l’opérateur virgule (,) qui permet de réunir plusieurs expression en une seule. Il est rarement utilisé,mais a de l’intérêt dans le cas des boucles for et permet par exemple d’écrire :

for (i=0,k=1; i<10 ; i++,k*=2)printf("2**%d=%d\n",i,k);

6.3 Branchements

Le langage C contient trois instructions de branchement : break, continue, goto. Elles sont à éviter, exception faitede break qui est presque indispensable si on utilise l’instruction switch.L’instruction break permet de sortir prématurément de la boucle (ou du switch) la plus petite qui le contient.L’instruction continue, utilisée dans une boucle permet d’ignorer la fin du corps de boucle et de passer directement

6. En fait, cette boucle while est équivalente à une boucle for à condition qu’on n’utilise pas de branchement type continue ou gotodans le corps de la boucle.

28

Notes de cours, Ensip, Mee2

au tour suivant. On peut utiliser ces instructions, mais uniquement si elles simplifient grandement l’écriture. Autrement,mieux vaut les éviter car elles nuisent la plupart du temps à la lisibilité.L’instruction goto permet de se brancher sur une instruction de son choix, repérée par un label :

printf("La ligne avant le Goto\n");goto fin;printf("Personne ne veut m’exécuter\n");fin : printf("C’est fini\n");

Sauf cas exceptionnel (programme très court, nécessité d’optimisation...), cette instruction n’est jamais utilisée.

7 Fonctions

En C, on ne fait pas la distinction entre procédures et fonctions. Il n’y a que des fonctions (qui peuvent ne rienrenvoyer). Contrairement à Python, les paramètres d’une fonction doivent être typés. De même le type de retour estfixe.Voici un exemple de fonction :

double exposant(double x, int n) {double res;if (n==0) return 1;if (n%2==0) res=exposant(x*x,n/2);else res=exposant(x*x,n/2)*x;return res;

}

La première ligne comporte les informations suivantes :— le type de retour est double (premier mot de la ligne)— le nom de la fonction est exposant— la fonction prend deux paramètres : le premier est un double et le second est un int.

Voici comment utiliser cette fonction dans un programme :

#include <stdio.h>

double exposant(double,int);

int main(void) {int a=13;double val, x=3;val=exposant(x,a);printf("%f^%d=%f\n",x,a,val);return 0;

}

Notez la ligne : double exposant(double,int); qui permet au compilateur de connaître la signature de la fonction.Cette ligne de «déclaration», qu’on appelle aussi prototype de la fonction pourrait aussi figurer dans la fonctionmain. On peut s’en passer si la fonction elle même est définie avant son utilisation dans le code. Néanmoins, écriresystématiquement la ligne de déclaration des fonctions est une bonne habitude.

Paramètres formelsLes noms des paramètres utilisés dans la fonction (x et n dans l’exemple) ne servent qu’à décrire ce que fait lafonction en interne : ce sont des paramètres formels. Modifier le nom de ces paramètres (modifier x en y parexemple) implique que l’on modifie le corps de la fonction (on remplace x par y partout), mais pas qu’on modifiequoi que ce soit en dehors de la fonction.Ce principe est similaire à celui de la définition d’une fonction mathématique : f(x) = 3x + 2.On pourrait écrire de manière équivalents f(y) = ..., à condition de remplacer x par y dans l’expression : f(y) =3x + 2. Mais cette modification ne change pas la fonction (ni ce qu’elle calcule, ni la manière de l’utiliser) : elles’appelle toujours f , et associe toujours le même nombre au même argument.

29

Programmation en C

L’instruction return interrompt la fonctionEn plus de permettre de préciser la valeur qui sera retournée, return stoppe l’exécution de la fonction, ce qui estparfois pratique pour éviter trop de if ou else imbriqués. Dans l’exemple qui précède, si n vaut 0, la fonctionretourne 1 et stoppe son exécution au niveau du return 1, si bien que le reste de la fonction n’est même pasconsidéré.

Les fonctions qui ne renvoient rien (et qui sont donc des procédures) doivent annoncer void comme type de retour. Demême, si une fonction ne prend pas de paramètre, la signature doit indiquer int mafonction(void) (les compilateursacceptent généralement la syntaxe C++ int mafonction()).

Type de retour par défautAttention, si on ne précise pas le type de retour, il est pris par défaut égal à int et non pas à void....

7.1 Portée des déclarations

La portée d’une variable s’étend de sa déclaration jusqu’à la fin du bloc qui contenait cette déclaration. Si la variablen’est pas déclarée à l’intérieur d’un bloc, sa portée s’étend jusqu’à la fin du fichier source.

#include <stdio.h>

int n;void affn(int a) {

int b=5;printf("%d %d %d\n",a,b,n);

}int m;void affm(void) {

printf("%d\n",m);}int main(void) {

int c=3;n=10;m=6;affn(c);affm();return 0;

}

Dans l’exemple qui précède la portée de la variable globale n est l’ensemble du fichier. La porté de m est en revanchelimitée à affm et main. La portée de a et b est la fonction affn uniquement. La portée de c est la fonction main.Il est possible de partager une variable entre plusieurs fichiers en utilisant le mot-clé extern.

mot clé staticLe mot clé static, lorsqu’il accompagne la déclaration d’une variable locale à une fonction, permet à cette dernièrede conserver sa valeur entre deux appels. Si la variable est initialisée sur la ligne de déclaration, l’initialisation n’estréalisée qu’au premier appel de la fonction.

8 Fichiers

L’utilisation de fichiers se fait généralement en trois temps :1. ouverture du fichier (en lecture ou écriture)2. lecture ou écriture dans le fichier3. fermeture du fichier

8.1 Ouverture et fermeture d’un fichier

La fonction fopen permet d’ouvrir les fichiers en lecture ou en écriture.

30

Notes de cours, Ensip, Mee2

#include <stdio.h>FILE * f;f=fopen("toto.txt", "rw");

La fonction renvoie un pointeur vers un descripteur de fichier (FILE*). En cas d’échec, fopen renvoie NULL.Les principaux modes d’ouverture des fichiers sont :

— r : lecture— w : écriture— a : écriture en fin de fichier (append)

On dispose aussi de :— r+ (rw) : lecture/écriture (le fichier doit exister)— w+ : lecture/écriture (le fichier est créé)— a+ : écriture en fin de fichier et lecture— b : mode binaire (à ajouter au mode d’ouverture)

La fonction fclose, qui prend comme unique paramètre un pointeur vers un descripteur de fichier, permet de refermerle fichier après utilisation.

8.2 Lecture et écriture

Les deux fonctions printf et scanf possèdent leur pendant pour les fichiers : fprintf et fscanf. Ces deux dernièresfonctions prennent en premier argument supplémentaire un pointeur vers le descripteur de fichier.L’exemple qui suit ouvre le fichier data.txt, calcule la somme des nombres à virgule qui s’y trouvent, puis ajoute cetotal à la fin du fichier somme.txt.

#include <stdio.h>

int main(void) {FILE * in, * out;float sum=0,val=0;in=fopen("data.txt","r");if (in==NULL) {

printf("L’ouverture du fichier d’entrée a échoué\n");return -1;

}while(fscanf(in,"%f",&val)!=EOF) {

sum+=val;}fclose(in)

out=fopen("somme.txt","a");if (out==NULL) {

printf("L’ouverture du fichier de sortie a échoué\n");return -1;

}

fprintf(out,"%f",sum);fclose(out);

}

8.3 Fichiers binaires

Les deux fonctions fread et fwrite permettent de lire et écrire des octets dans un fichier.La fonction fread prend en premier paramètre un pointeur vers la zone mémoire où inscrire les données lues, ensecond paramètre le nombre de données à lire, en troisième paramètre la taille d’une donnée en octets, et en dernierparamètre, un pointeur vers le descripteur de fichiers.La fonction fwrite prend en premier paramètre un pointeur vers la zone mémoire où prendre les données à écriredans le fichier, en second paramètre le nombre de données à écrire, en troisième paramètre la taille d’une donnée en

31

Programmation en C

octets, et en dernier paramètre, un pointeur vers le descripteur de fichiers.

9 Pointeurs

Les pointeurs sont très largement utilisés en C, mais sont parfois délicats à manipuler.Nous avons vu la notion de variable qui peut contenir une valeur d’un certain type. En mémoire, cette valeur estinscrite à une certaine adresse. On peut donc parler d’adresse d’une variable. Cette adresse est constante, pendanttoute la durée de vie de la variable, et si on n’utilise pas les pointeurs, deux variables ne peuvent normalement pasavoir la même adresse.Il est possible de connaître l’adresse d’une variable avec l’opérateur & :

int a=3;printf("a est stockée à l’adresse %p\n",&a)

Nous voyons au passage qu’une adresse peut être formatée en hexadécimal, grâce au format %p.Cette adresse est elle même une valeur... et nous pouvons donc la stocker dans une variable. Le type de cette nouvellevariable sera pointeur. Si l’adresse est censée contenir un entier, le type de pointeur sera pointeur vers un entier :

int a=3;int *t;t=&a;printf("a est stockée à l’adresse %p\n",t);

L’intérêt des pointeurs consiste à donner la possibilité de modifier une variable (par exemple a), à partir de son adresse,ou d’un pointeur qui pointe vers a (par exemple t). Faire cette opération met en jeu un nouvel opérateur, l’opérateurd’indirection qui sert en quelque sorte à désigner le contenu de la variable dont l’adresse est dans le pointeur :

int a=3;int *t;t=&a;*t = *t +1;printf("a contient : %i\n",a);

La ligne *t=*t+1 ajoute 1, non pas dans t 7, mais dans la variable pointée par t, c’est à dire dans a.

scanf et &Le sens du & présent devant les variables avec la fonction scanf doit maintenant être plus clair. Avec scanf, nousne devons pas fournir le contenu de la variable (qui ne contient rien d’intéressant avant l’appel à scanf), mais bienl’adresse de la zone mémoire dans laquelle scanf doit écrire les données saisies par l’utilisateur. Nous reviendronssur ceci section 12.

scanf et chaînesC’est pour la même raison que lors d’une saisie de chaîne avec scanf (format %s), le & n’est pas nécessaire puisque,comme nous allons le voir section 11, la chaîne est déjà désignée par un pointeur....

Pointeurs génériquesLe type de pointeur void* est dit générique. L’objet désigné par ce type de pointeur peut être de n’importe queltype.

10 Tableaux

Les tableaux sont des collections ordonnées d’éléments homogènes (contrairement aux listes de Python, dont leséléments peuvent être hétérogènes).

7. Pour ajouter 1 dans t, il faut écrire simplement t=t+1... En fait, ce n’est pas tout à fait exact, car t=t+1 ajoute réellement à t laquantité nécessaire pour «avancer» d’un entier. La valeur ajoutée vaut donc probablement 4 (un entier est codé sur 4 octets).

32

Notes de cours, Ensip, Mee2

En C (avant la norme C99), la taille des tableaux doit être connus à la compilation 8. De plus, la taille des tableauxdoit rester fixe (pas de fonction append ou extend comme en Python).L’indice des tableaux commence classiquement à 0, et tout comme les autres variables, les tableaux doivent êtredéclarés :

int mtab[20];int i;mtab[0]=0;mtab[1]=1;for (i=2;i<20;i++) {

mtab[i]=3*mtab[i-1]-5*mtab[i-2];}

for (i=0;i<20;i++) printf("%d\n",mtab[i]);

Les tableaux peuvent avoir plusieurs dimensions (plusieurs indices), auquel cas, il suffit de préciser la taille sur chaquedimension à la déclaration :

int t[4][3];

En mémoire, les éléments sont rangés de manière séquentielle. Pour connaître l’ordre de rangement des éléments destableaux multidimensionnels, on fait varier le dernier indice en premier : t[0][0], t[0][1], t[0][2], t[1][0],...Attention à ne pas confondre la dimension d’un tableau, qui correspond au nombre de paires de crochet, et la taille,qui correspond au nombre d’éléments (en tout ou le long d’une dimension).Les tableaux peuvent être initialisés sur la ligne de déclaration, en énumérant les valeurs entre accolades. Dans ce cas,il n’est pas forcément utile de préciser la taille du tableau si elle peut être déduite du nombre d’éléments utilisés dansl’initialisation :

int t1[]={4,5,6,7,8};int t2[3]={10,11,10};int t2[2][3]={{2,3,4},{4,5,6}};

11 Chaînes de caractères

11.1 Déclaration et allocation

Le C ne dispose pas d’un véritable type chaîne. Les chaînes seront représentées par de simples suites d’octets (laplupart du temps un tableau de char). Pour des raisons pratiques, la chaîne se terminera par l’octet de valeur 0. Cetteconvention permettra, bien que le tableau accueillant la chaîne soit de longueur fixe, de connaître la longueur réellede la chaîne qu’il contient (en repérant la position du 0 final).Voici plusieurs façons de déclarer et d’initialiser une chaîne de caractères. Certaines font appel aux pointeurs.

char chaine1[]="Programmer en C";char chaine2[50]="Programmer en C";char chaine3[]={’P’,’r’,’o’,’g’,’a’,’m’,’m’,’e’,’r’,’ ’,’e’,’n’,’ ’,’C’,’\0’};char * chaine4 = "Programmer en C";

L’utilisation des constantes entre doubles guillemets nous évite de devoir penser au 0 final (qui est précisé pourchaine3). Si la taille du tableau n’est pas précisée, elle est calculée au plus juste : 15 caractères + le 0 final c’est àdire 16 caractères.

11.2 Fonctions sur les chaînes

La bibliothèque standard permet de réaliser des opérations sur les chaînes de caractères. Le fichier d’en-tête contenantles déclarations de ces fonctions est string.h.

8. Avec la norme C99, il est nécessaire que la taille soit connue à l’exécution seulement.

33

Programmation en C

Voici quelques fonctions parmi les plus utiles :

// Recopie une chaîne, la destination doit être allouéechar * strcpy(char *dest, const char *src);// Recopie une chaîne au bout d’une autrechar * strcat(char *dest, const char *src);// Donne la longueur d’une chaîne (sans compter le 0 final)size_t strlen(const char *s);// Compare deux chaînes (donne un classement type dictionnaire)int strcmp(const char *s1, const char *s2);// Recherche une chaîne dans une autrechar *strstr(const char *meule_de_foin, const char *aiguille);

Voici des portions de code utilisant ces fonctions :

char str1[]="Il est morne ";char str2[]=" taciturne";char str4[255];char *str5;

strncpy(str4,str1,6);strcat(str4,str2);printf("%s\n",str1);printf("%s\n",str4);

/* Attention !!! */strcpy(str5,str1);strcpy(str2,str3);

Le code précédent afficherait :

Il est morneIl est taciturneErreur de segmentation (core dumped)

Les deux dernières lignes ne peuvent pas fonctionner car les chaînes de destination ne sont pas allouées du tout ouavec un espace trop faible.Autre exemple :

char str1[]="Il preside aux choses du temps";char str2[]="Il porte un joli nom : Saturne";char * str3;

int l,c1,c2;l=strlen(str1);printf("%d\n",l);

str3=strstr(str1,"choses");printf("%s\n",str3);

c1=strcmp(str1,str2);str1[3]=’\0’;str2[3]=’\0’;c2=strcmp(str1,str2);printf("%d %d\n",c1,c2);

L’exécution de ce programme afficherait :

>30choses du temps

34

Notes de cours, Ensip, Mee2

3 0

De même que les fonction printf et scanf permettent d’écrire sur la sortie standard et de lire sur l’entrée standard,les fonctions fscanf et fprintf permettent de lire et écrire dans un fichier. Toujours de la même manière, les fonctionssscanf et sprintf permettent de lire et écrire dans une chaîne de caractères. Un pointeur vers la chaîne en questionest alors donné en premier argument :

char st[256];int h=4,m=45,s=56;sprintf(st, "%02dH%02dM%02dS", h,m,s);

Après exécution de ce code, la chaîne st contiendra : 04H45M56S

11.3 Pointeurs et chaînes

Revenons sur cette déclaration de chaîne :

char * chaine = "Programmer en C";

La représentation en mémoire de la chaîne est créée (16 octets), puis on affecte au pointeur chaine l’adresse de débutde cette zone.

scanf et chaînesLors d’une saisie de chaîne avec scanf (format %s), le & n’est pas nécessaire puisque la chaîne est déjà désignée parune adresse (un char * ou un tableau de char)...

Il est courant de construire un tableau de chaînes de caractères sur le même modèle :

char * magie[]={"am", "stram", "gram"};

Dans le cas qui précède, magie est un tableau de 3 éléments. Chacun de ces éléments est un char* qui pointe vers ledébut d’une chaîne de caractères. Ces chaînes occupent respectivement 3, 6 et 5 octets en mémoire, et elles ne sontpas nécessairement contiguës.

11.4 Conversions

Les fonctions sprintf et sscanf, évoquées plus haut permettent de réaliser des conversions entre valeurs numériqueset chaînes. Signalons aussi les fonctions : atoi (ascii to int), atof (ascii to float).

12 Retour sur les fonctions

12.1 Passage par valeur et par adresse

En C, les variables sont passées pas valeur.Si l’on n’utilise pas de pointeur, et que le paramètre n’est pas un tableau, c’est une copie de la valeur qui parvient àla fonction (et modifier cette copie dans la fonction ne modifie par la variable d’origine).

#include <stdio.h>void fonc(int a) {

printf("a(fonc) reçu : %d\n",a);a=a+1;printf("a(fonc) modifié : %d\n",a);

}

int main(void) {int a=4;printf("a(main) avant l’appel : %d\n",a);fonc(a);

35

Programmation en C

printf("a(main) après l’appel : %d\n",a);return 0;

}

ExerciceQue va afficher le code précédent ?

Lorsqu’on passe un tableau à une fonction, c’est un pointeur qui est donné en paramètre, et c’est donc l’adresse dutableau, et non son contenu, qui est communiqué à la fonction. Modifier le tableau dans la fonction revient donc àmodifier le tableau original.Si un tableau est passé en paramètre, c’est bien le tableau d’origine qui est manipulé dans la fonction. Modifier cetableau dans la fonction revient à modifier le tableau d’origine.

#include <stdio.h>void affiche(int t[]) {

int i;for(i=0;i<5;i++) printf("%d ",t[i]);printf("\n");

}void fonc(int t[]) {

int i;printf("t(fonc) reçu : ");affiche(t);for(i=0;i<5;i++) t[i]=t[i]*t[i];printf("t(fonc) modifié : ");affiche(t);

}

int main(void) {int t[]={1,2,3,4,5};printf("t(main) avant l’appel : ");affiche(t);fonc(t);printf("t(main) après l’appel : ");affiche(t);return 0;

}

ExerciceQue va afficher le code précédent ?

36

Notes de cours, Ensip, Mee2

Une variable qui n’est pas de type tableau peut tout de même être passée par adresse. Pour cela on utilise explicitementles pointeurs pour communiquer à la fonction l’adresse de la variable plutôt que son contenu.

#include <stdio.h>void fonc(int *pa, int a) {

printf("*pa(fonc), a(fonc) : %d %d\n",*pa,a);a=a*2;*pa=*pa*3;printf("*pa(fonc), a(fonc) modifiés : %d %d\n",*pa,a);

}

int main(void) {int a=5;printf("a(main) avant l’appel : %d\n",a);fonc(&a,a);printf("a(main) après l’appel : %d\n",a);return 0;

}

ExerciceQue va afficher le code précédent ?

12.2 Passage des tableaux en paramètres (reloaded)

Lorsqu’on passe un tableau en paramètre d’une fonction, on souhaite pouvoir utiliser le formalisme des tableaux (lescrochets []) dans la fonction.Dans le cas des tableaux à une seule dimension, cela ne pose pas de problème (on rajoute souvent la taille en paramètre).Dans le cas des tableaux à plusieurs dimensions, il faut préciser, par une constante, la taille selon toutes les dimensions,sauf éventuellement la première :

#define N1 3#define N2 4#define N3 5

void fonc(int t[][N2][N3]) {.... t[i][j][k]...

}

int main(void) {int t[N1][N2][N3]={...};fonc(t);...

}

Nouveauté du C99Le C99 a introduit la possibilité de préciser la taille des dernières dimensions d’un tableau à l’aide de variables,plutôt que de constantes. Ces variables peuvent être globales, ou peuvent être des paramètres de la fonction, àcondition qu’elles précèdent le paramètre tableau dans la signature de la fonction.

37

Programmation en C

12.3 Pointeurs vers des fonctions

De même qu’il existe des pointeurs vers des données, nous pouvons utiliser et manipuler des pointeurs vers desfonctions. Ceci peut être utile, par exemple, pour l’écriture de code générique, ou en programmation événementielle(enregistrement des callbacks).Voici comment déclarer un pointeur vers une fonction :

type_retour (* nom_var) (signature);

Dans l’exemple ci-dessous, pfonc sera un pointeur vers une fonction prenant en argument deux entiers, en retournantun entier :

int (*pfonc) (int, int);

Voici un exemple d’utilisation (sans intérêt, mais qui illustre le principe) :

#include <stdio.h>int mul(int a, int b) {return a*b;}int add(int a, int b) {return a+b;}

int main(void){

int a=5,b=6,c,d;int (*pfonc) (int, int);pfonc=mul;c=(*pfonc)(a,b);pfonc=add;d=(*pfonc)(a,b);printf("%d %d\n",c,d);return 0;

}

ExerciceQu’affichera l’exécution du programme précédent ?

13 Stuctures

Les structures permettent de créer un nouveau type de variable dont la valeur est un agrégat de plusieurs éléments,nommés, de types éventuellement différents.Si par exemple nous voulons créer un type fiche capable de stocker l’identité, l’âge et la taille de quelqu’un, nouspouvons écrire :

struct fiche {char nom[255];int age;float taille;

};

Puis nous déclarons une variable ayant ce type et la renseignons :

struct fiche fiche1;

strcpy(fiche1.nom,"Dupond");fiche1.age=35;

38

Notes de cours, Ensip, Mee2

fiche1.taille=1.72;

Les champs de la structure sont utilisables de la même façon que des variables ordinaires.

Affectation de structuresLors d’une affectation de type :

fiche2=fiche1;

tous les champs sont recopiés. En fait, c’est toute la zone mémoire contenue dans la structure qui est recopiée, mêmesi celle-ci contient des chaînes de caractères ou des tableaux (qui d’habitude ne sont pas recopiés par une simpleaffectation).

Utilisation de typedef

La répétition du mot clé struct lors de la déclaration des variable (ou des prototypes des fonctions) est fastidieuse.Aussi, on utilise souvent conjointement avec les structures la possibilité, en C, de donner de nouveaux noms auxtypes.Par exemple, typedef int entier; permettra d’utiliser le mot clé entier à la place de int. De même :

struct fiche {char nom[255];int age;float taille;

};typedef struct fiche fiche;

ou encore :

typedef struct {char nom[255];int age;float taille;

}fiche ;

permettra d’utiliser le mot fiche à la place de struct fiche.

Pointeurs et structuresNous pouvons bien entendu définir un pointeur vers une structure :

struct fiche * pf;

L’accès aux champs peut alors se faire ainsi :

(*pf).age=25;

ou ainsi :

pf->taille=1.69;

14 Énumérations

Les énumérations permettent de définir un nouveau type discret, contenant des valeurs telles que des couleurs, desétats,...L’exemple le plus courant est sans doute :

// Définitiion du type enum couleurenum couleur {rouge,orange,jaune,vert,bleu,violet,indigo};

// Utilisation de variables de ce typeenum couleur c1,c2;

39

Programmation en C

c1=orange;c2=vert;

La réalité est que le compilateur associe un entier, en partant de 0, à chaque valeur possible pour l’énumération.L’utilisateur manipule de son côté des symboles plus évocateurs que des nombresIl est toutefois possible de contrôler les entiers utilisés :

enum jour {lundi=1,mardi,mercredi,jeudi,vendredi,samedi,dimanche};

Dans le cas qui précède, lundi sera associé à 1 (plutôt que 0 par défaut), et les autres valeurs suivront. Chaque valeurpeut néanmoins être spécifiée :

enum etat {sain=0, feu=2, cendre=4};

15 Champs de bits

Dans certains cas, il est nécessaire de pouvoir contrôler le nombre de bits exacts sur lesquels une information eststockée (plutôt que de s’accommoder des 8 bits d’un unsigned char par exemple).La déclaration d’un champ de bits ressemble à celle d’une structure. Pour chaque membre de la structure, qui peutêtre signé ou non signé, on indique combien de bits il faudra utiliser. Ainsi, si on veut diviser un mot de 16 bits en 2groupes de 6 bits, et un groupe de 4, on peut écrire :

struct state {int grp1 : 6;unsigned int grp2 : 6;int val : 4;

};

Les trois champs se nommeront alors grp1, grp2 et val. Enfin, grp1 pourra prendre des valeurs entre -32 et +31,grp2 entre 0 et 63, et val entre 0 et 15.On accède aux données comme on le ferait avec une structure :

struct state var;var.grp2=50;...

16 Unions

Les unions permettent de créer un type de données qui pourra être interprété de différentes manières, c’est à diredécodé selon les principes d’un certain type ou d’un autre.Voici par exemple un type de donné, nommé tabchar qui peut être interprété comme un entier codé sur 4 octets (int)ou bien comme un tableau de 4 octets :

union tabchar{int val;unsigned char tab[4];

};

On peut ensuite interpréter la donnée de ce type comme un entier ou comme un tableau de char :

union tabchar v;int i;v.val=65538;for (i=0;i<4;i++) {

printf("%d ",v.tab[i]);

40

Notes de cours, Ensip, Mee2

}printf("%d\n",v.val);

Le programme affichera : 2 0 1 0 65538

ExerciceExpliquez l’affichage précédent.

17 Allocation dynamique de mémoire

L’allocation dynamique de mémoire est généralement nécessaire lors de l’utilisation de structures dont la taille n’estpas connue à la compilation : tableau de taille a priori inconnue (par exemple lecture en mémoire de l’intégralité d’unfichier dont on ne connaît pas la taille), utilisation de données de type listes, arbres, graphes...La fonction d’allocation malloc permet d’allouer lors de l’exécution (et non pas lors de la compilation) un nombredonné d’octets. La fonction renvoie un pointeur (de type void *) vers la zone allouée. Il est habituel de stocker lavaleur renvoyée dans un pointeur typé (par exemple unsigned int * si on sait que les données seront des entiers nonsignés), pour pouvoir utiliser l’arithmétique sur les pointeurs (ajouter 1 au pointeur passera bien à l’entier non signésuivant et pas à l’octet suivant).La mémoire allouée peut être libérée par la fonction free à laquelle on communique un pointeur vers le début de lazone allouée.La fonction realloc prend en paramètre un pointeur vers une zone déjà allouée, une taille en octets, et renvoie unpointeur vers la zone agrandie (qui peut être la même zone, réellement agrandie, ou une nouvelle zone plus grandedans laquelle les données d’origines auront été copiées).L’exemple suivant alloue une zone mémoire par blocs de 512 octets, et y stocke l’intégralité d’un fichier texte.

#include <stdio.h>#include <stdlib.h>

int main(void) {unsigned char * txt;size_t taillealloc=512;size_t taillereelle=0;FILE *f;int c;txt=malloc(taillealloc*sizeof(char));if (txt==NULL) return -1;f=fopen("data.txt","r");while( (c=fgetc(f))!=EOF ) {

if (taillereelle>=taillealloc) {taillealloc+=512;txt=realloc(txt,taillealloc*sizeof(char));if (txt==NULL) {fclose(f); return -1;}

}txt[taillereelle]=c;taillereelle++;//printf("%c",c);

}fclose(f);printf("Taille alloc : %ld\n",taillealloc);printf("Taille réelle : %ld\n",taillereelle);free(txt);return 0;

}

41

Programmation en C

18 Préprocesseur

Les directives adressées au préprocesseur sont reconnaissables au #. Nous avons déjà rencontré la directive #includequi permet d’inclure un fichier en-tête (certains l’utilisent aussi pour include un fichier C entier) dans un fichier C.Une autre directive assez courante, #define, permet de définir des constantes et des macros.Utilisé, sans paramètre, #define A B remplace A par B dans le source avant compilation.L’exemple suivant fonctionne donc, même pour des versions du compilateur antérieures à C99 :

#define SIZE 512....int tab[SIZE];

Ce qui précède est équivalent à int tab[512].Attention à ne pas mettre de ; au bout de la ligne #define, sans quoi la déclaration du tableau serait interprétéecomme : int tab[512;] et ne fonctionnerait donc pas. La valeur de remplacement peut être une ligne de codecomplète.Le terme à remplacer peut prendre un ou plusieurs paramètres (auquel cas nous définissons une macro) comme dans :

#define MIN(a,b) (a<b?a:b)...int x,y;scanf("%d%d",&x,&y);printf("Le + petit est %d\n",MIN(x,y))

Notez les parenthèses dans le define, qui nous assurent que :

c=MIN(x,y)-1

sera interprété comme c=(x<y?x:y)-1 (et vaudra donc 0 si x vaut 1 et y vaut 10) et non comme c=x<y?x:y-1 (quivaudrait 1 et non 0 dans l’exemple précédent).Les directives du préprocesseur permettent aussi de faire de la compilation conditionnelle, souvent (mais pas que) dansun but de portabilité :

#define PLATFORME1

int main(void) {#ifdef PLATEFORME1

printf("Sur P1...\n");a=5;

#elseprintf("Sur Autre plate-forme\n");a=0;

#endifprintf(" Au revoir\n");return 0;

}

Le code qui précède est transformé avant compilation en :

int main(void) {printf("Sur P1...\n");a=5;printf(" Au revoir\n");return 0;

}

42

Notes de cours, Ensip, Mee2

En revanche, si la ligne define est enlevée (parce qu’on compile pour une autre plate-forme par exemple), le codedevient :

int main(void) {printf("Sur Autre plate-forme\n");a=0;printf(" Au revoir\n");return 0;

}

La définition de la plate-forme est souvent donnée dans des fichiers d’en-tête standards ou par le biais d’un Makefile.

43

Programmation en C

44

Chapitre IV

Exercices

Codage

Exercice 1 : Nombres entiersÉcrivez en base 2, 10 et 16 les nombres suivantes, exprimés en base 2, 10 ou 16 :127|10, 15|10, 170|10, 10|10, 10011010|2, 1001|2, 10|2, E|16, B6|16, FF |16, 10|16

Exercice 2 : Complément à 2Écrivez en codage en complément à 2 sur 8 chiffres, les nombres suivants : -15, 127, -1Exercice 3 : Virgule fixeÉcrivez en binaire les nombres suivants : 6.25 3.3125 1.2Exercice 4 : Limites virgule flottanteEn simple précision, calculez (dans les nombres positifs) le plus petit et le plus grand nombre dénormalisé, ainsi quele plus petit et le plus grand nombre normalisé.Exercice 5 : Répétition virgule flottanteSupposons qu’on utilise un codage en virgule flottante sur 5 bits avec 1 bit de signe et 2 bits d’exposant (le décalaged’exposant vaut alors 1). Écrivez tous les codes possibles et le nombre qu’ils représentent sur l’axe réel. Effectuez lesopération suivantes en virgule flottante : (3+0.25)+0.25 et 3+(0.25+0.25). Quelle règle utiliser pour choisir l’ordre desadditions ?Opérateurs et expressions

Exercice 6 : Commandes de LedsNous écrivons un programme en C qui permet de piloter une série de 8 leds, par le biais d’une variable nomméeOUTLEDS. Chacune des 8 leds correspond à un bit de la variable. La led 0 au bit 0 (bit de poids faible) etc... Si le bitest à 1, la led est allumée. Écrivez les instructions qui permettent de réaliser les opérations suivantes :

1. éteindre toute les leds ;2. allumer toutes les leds ;3. allumer la led 4 et éteindre les autres ;4. allumer la led 5 sans toucher aux autres ;5. éteindre la led 5 sans touche aux autres ;6. allumer les leds de rang pair (0,2,4,..) sans toucher aux autres ;7. provoquer le clignotement : leds paires/leds impairess8. provoquer le clignotement des leds 0 à 7, indépendamment de leur état de départ, sans modifier l’état des autres

leds.Sur les entrées/sorties

Exercice 7 : CercleÉcrivez un programme qui demande à l’utilisateur le rayon d’un cercle, puis affiche son périmètre et sa surface, avecdeux chiffres après la virgule.

Sur les instructions et les structures de contrôleExercice 8 : Extraction de chiffresÉcrivez un programme qui demande un nombre entier à l’utilisateur, puis affiche successivement ses chiffres et en faitla somme.

45

Programmation en C

Exercice 9 : Le plus grand des 3Écrivez un programme qui demande à l’utilisateur d’entrer trois nombres à virgule, puis affiche le plus grand des trois.Exercice 10 : DegrésÉcrivez un programme qui affiche un table de conversion de degrés Celcius (toutes les valeurs de -200 à 200 par pasde 20) vers dégrés Farhenheit et Kelvin. La table devra être correctement formatée et les nombres alignés.

TK = TC + 273.15 et TF = 95TC + 32

Exercice 11 : EncadrementÉcrivez une fonction qui encadre au plus juste un nombre choisi par l’utilisateur entre des puissances successives d’unnombre de son choix.Exemple :

Entrez un nombre : 345Entrez un (petit) entier : 3345 et compris entre 3^5(243) et 3^6(729)

Sur les fichiersExercice 12 : Somme et produitÉcrivez un programme qui ouvre un fichier contenant des nombres, les lit, et affiche leur somme et leur produit àl’écran.Sur les tableauxExercice 13 : TableauxÉcrivez un programme qui demande des nombres à l’utilisateur (50 maximum) et les stocke dans un tableau, puisaffiche le maximum, le minimum et la somme alternée de ces nombres (le premier moins le second, plus le troisièmemoins le quatrième etc..).Exercice 14 : Conversion en binaireÉcrivez une fonction qui prend en paramètres un nombre et un tableau et place dans le tableau les chiffres binairesreprésentant le nombre. Le bit de poids faible sera placé au début du tableau (case 0).Exercice 15 : InsertionÉcrivez une fonction qui prend en paramètres un tableau, sa taille, un nombre n et un indice i et place le nombre nen position i dans le tableau. Toutes les cases situées après i seront décalées et la dernière case sera supprimée.Exercice 16 : HistogrammeÉcrivez un programme qui lit un fichier contenant des relevés de taille de personnes. Produisez un tableau de nombreindiquant en case k combien de personnes mesurent entre k × 5 et (k + 1)× 5 centimètres.Exercice 17 : Calcul de moyenneComplétez le programme suivant pour qu’il calcule la moyenne des éléments su tableau :

#include <stdio.h>int main(void) {

short vals[]={9876,8723,6918,3542,9767,5687, 5485,9847,2934,1029};short m;short i;for (i=0;i<10;i++) {

...}...printf("La moyenne est : %d\n",m);return 0;

}

Sur les chaînes de caractèresExercice 18 : Carré démoniaqueÉcrivez une procédure qui prend un mot (par exemple PROGRAMME) en paramètre et l’affiche sous forme d’uncarré :

46

Notes de cours, Ensip, Mee2

PROGRAMMEROGRAMMEPOGRAMMEPRGRAMMEPRORAMMEPROGAMMEPROGRMMEPROGRAMEPROGRAMEPROGRAMM

Exercice 19 : MotsUn fichier contient des mots de moins de 8 lettres. Écrivez un programme pour stocker ces mots en mémoire centrale,qui recherchera ensuite les mots correspondant à un motif particulier. Les motifs seront donnés sous forme d’unechaîne contenant des lettres ou des points. Les points pourront représenter n’importe quelle lettre. Par exemple, lemotif .vi.. correspond à avion et ovins, mais pas à avions, ovnis ou avant.Exercice 20 : Chiffre de CésarÉcrivez un programme qui demande une phrase à l’utilisateur, convertit les minuscules en majuscules, puis affichechaque caractère décalé de 3 rangs dans l’alphabet.Par exemple, si l’utilisateur entre :

Enfants, voici des boeufs qui passent, cachez vos rouges tabliers.le programme devra répondre :

HQIDQWV, YRLFL GHV ERHXIV TXL SDVVHQW, FDFKHC YRV URXJHV WDEOLHUV.

Sur le passage par adresse, les fonctions

Exercice 21 : ÉchangeÉcrivez une fonction qui échange le contenu de deux variables (dont l’adresse sera passée en paramètres).Exercice 22 : Retours multiples avec les pointeursÉcrivez une fonction qui prend un tableau d’entiers en paramètres, et renvoie les valeurs min et max de ce tableau(puisqu’on ne peut pas (simplement) retourner deux entiers en C, on utilisera les pointeurs).Exercice 23 : Analyse fréquentielleÉcrivez une fonction qui comptabilise le nombre d’apparitions de la lettre e dans une chaîne de caractères.Écrivez une fonction qui prend en paramètres une chaîne et un tableau de 256 cases et stocke dans la case i du tableaule nombre d’apparitions dans la chaîne du caractère de code i.Exercice 24 : Chiffre de VigénèreLe chiffre de Vigénère permet de chiffrer des caractères avec un mot de passe (voir les détails sur Ars-cryptographicaou Wikipedia, ou écouter en td... :) ).Par exemple, la phrase :

Il porte un joli nom : Saturne. Mais c’est un Dieu, fort inquiétant.chiffrée avec le mot clé Georges donnera :

OPDFX XWARX FRMFU QGRZY JTIAR OWUKW HLTHA KYTFX XATUI ZZEFZÉcrivez des fonctions pour chiffrer et déchiffrer des messages utilisant cette méthode.Exercice 25 : Produit matricielÉcrivez une fonction qui calcule le produit de deux matrices.Exercice 26 : Fonction ReduceOn souhaite simuler la fonction reduce de Python. Pour cela, on voudrait pouvoir écrire :

#include <stdio.h>int mul(int a, int b) {return a*b;}int add(int a, int b) {return a+b;}int reduce(...)int main(void) {

int t[]={1,3,4,6,7};int s,p;

s=reduce(t,5,add);p=reduce(t,5,mul);

47

Programmation en C

printf("Somme : %d \nProduit : %d\n",s,p);return 0;

}

Le programme précédent affichera :

Somme : 21Produit : 504

Écrivez la fonction reduce

Sur les structuresExercice 27 : Nombres rationnelsDécrivez une structure pour représenter les nombres rationnels sous forme de fractions. Écrivez une fonction quisimplifie au maximum une fraction, qui additionne deux fractions, et qui multiplie deux fractions.

Sur l’allocation dynamique de mémoire

Exercice 28 : Chargement en mémoire centraleUn fichier de données contient en premier enregistrement le nombre de valeurs (float) du fichier. Écrivez un programmequi alloue la mémoire nécessaire et stocke les données en mémoire centrale.Exercice 29 : Allocation dynamiqueÉcrivez un programme qui ouvre un fichier de données contenant des entiers, alloue une zone mémoire pour les stockeret y stocke les entiers. On fera en sorte que la zone allouée s’agrandisse au fur et à mesure de la nécessité. On écriraensuite une fonction qui calcule le nombre de valeurs du fichier contenue dans une plage de plus ou moins 10% autourde la moyenne. Enfin, on fera une fonction de désallocation propre.

48