Programmation C

Download Report

Transcript Programmation C

Programmation C
Dominique Rossin
[email protected]
Première partie
Généralités sur
la syntaxe C
Historique de C

AT&T Bell Labs 1969 : Premiere Unix
 1973 Unix écrit en C
– B.Kerninghan, D.Ritchie, The C programming
Langage, Prentice Hall, 1978

C ANSI = C ISO (1990) = C« Standard »
– Kerningham & Ritchie, the C programming Langage
second edition, Prentice Hall, 1988
– SP Harbison, GL Stelle jr, C-A reference Manual,
fourth edition, Prentice Hall 1995
Caractéristiques de C







Compilé
Typé avec des types structurés
Limité à des primitives de base mais
accompagné d’une librairie standard pour les
copies complexes, les entrées/sorties, la
gestion de la mémoire …
Interfaçage possible avec tous les langages
ATTENTION
Langage laxiste à la compilation
Débogage parfois difficile
Différences entre C et Java

Java a repris une grande partie de C mais :
–
–
–
–
C est compilé et utilise un préprocesseur
Certains mots clés changent de définition
Pas d’objets en C, juste des structures
Gestion explicite de la mémoire en C
• En C on demande une taille n de mémoire
• En java new s’occupe de cette opération
• Il faut libérer la mémoire utilisée quand on utilise plus
une structure de données
• En Java, le ramasseur de miettes (GC) s’en occupe
– En C il faut toujours déclarer avant d’utiliser
Exemple très simple en Java

Fichier Bonjour.java
public class Bonjour {
public static void main(String args[]) {
System.out.println("Bonjour ");
} // fin de main
} // fin de la classe
 On peut avoir plusieurs fonctions main
 l'exécution du programme commence par
l'appel à la fonction main du programme
appelé.
Un exemple très simple en C

Fichier Bonjour.c (le nom importe peu)
#include <stdio.h>
int main(void) {
printf("Bonjour\n");
return 0;
} /* fin de main */
 Il ne peut y avoir qu'une seule fonction main
par programme
 Elle doit retourner un int (code d'erreur Unix)
Mise en œuvre

En Java
– Compilation : javac Bonjour.java
– produit un fichier Bonjour.class (bytecode)
– commande java Bonjour :
• Interprétation de Bonjour.main(String args[])

En C
– prétrait., compilation, éd. de liens : gcc Bonjour.c
– produit un fichier a.out (code machine exécutable)
– commande ./a.out : exécution de la fonction main
Deuxième exemple

Avec :
– formatage des impressions
– options de compilation
public class PGCD {
public static void main(String args[]) {
int a = 257, b = 381;
if (a > 0 && b > 0) {
System.out.println(" PGCD("+a+","+b+")");
while (a != b) {
if (a < b) b = b-a;
else a = a-b;
System.out.println("=PGCD("+a+","+b+")");
}
System.out.println("= "+a);
}
}
}
#include <stdio.h>
int main(void) {
int a= 257, b = 381;
if (a > 0 && b > 0) {
printf(" PGCD(%3d,%"d)\n",a,b);
while (a != b) {
if (a < b) b = b-a;
else a = a-b;
printf("=PGCD(%3d,%3d)\n",a,b);
}
printf("=%d\n",a);
}
return 0;
}
Exemple de formatage avec
printf
PGCD(257,381) "PGCD(%3d,%3d)"
=PGCD(257,124)
=PGCD(133,124)
=PGCD( 9,124)
=PGCD( 9,115)
Options de compilation

Prétraitement, compilation et édition de liens :
gcc –Wall –g pgcd.o –lm –o pgcd
 l'option –Wall demande une compilation avec
des diagnostics sur la propreté du code
 l'option –g demande que la table des
symboles soit ajoutée à l'exécutable
 l'option –lm demande de lier la librairie
mathématique
 l'option –o pgcd demande que le résultat
(l'exécutable) soit nommé pgcd au lieu de
a.out
Déclaration de variables

Syntaxe, exemples:
– id_type id_variable;
– id_type id1,id2;
– id_type id = valeur;

Les variables locales ne sont pas initialisées
implicitement
 Les déclarations ne peuvent être qu'en début
de bloc
 Des constructions comme for(int i = …) et
similaires sont interdites
Types élémentaires

La représentation n'est pas normalisée :
– char : entiers, codés sur 8 bits
– int : généralement codés sur 32 bits,
– float : généralement codés sur 32 bits
(IEEE)
– double : généralement codés sur 64 bits
En C, une affectation peut faire perdre
implicitement de la précision. On a le droit
d'écrire int i = 1.5;
Qualificatifs short et long
short int (ou short simplement) : 16 bits
 long int (ou long simplement) : 32 ou 64
 long long int : généralement 64 bits
 long double : au moins 64 bits

Conversion de types
arithmétiques implicites

En cas de +,*,<,… sur deux types différents:
–
–
–
–

un opérant est long double => long double
un opérant est double => conversion en double
un opérant est float => conversion en float
char et short int sont convertis en int
En cas de = d'une valeur de mauvais type :
conversion vers le type de la variable
sizeof

pseudo-opérateur : sizeof(type) ou
sizeof(expr)
 donne la taille en octets : sizeof(char)=1
 nombre entier, calculé à la compilation
 "retourne" une valeur de type size_t (int ou
long ?)
– incorrect :
• printf("%d\n",sizeof(…)); //suppose int
• printf("%ld\n",sizeof(…)); // suppose long
– correct
• printf("%ld\n",(long)sizeof(…));
Représentation des types
numériques sizeof selon les
systèmes
C
S
I
L
LL F
D LD
cw/mac
1
2
4
4
8
4
8
8
gcc/hp
1
2
4
4
8
4
8
8
gcc/pc
1
2
4
4
8
4
8
12
gcc/sun
1
2
4
4
8
4
8
16
gcc/alpha
1
2
4
8
8
4
8
8
char, short, int,long, long long, float, double, long double
Qualificatifs signed et unsigned

pour les types entiers seulement,
 modifient le domaine représenté mais pas la
taille de la représentation,
– signed char : un octet : -128..127
– unsigned char :un octet :0..255
– char seul : signed ou unsigned ???

int est implicitement signed
 changent la sémantique des opérations et
affectations
Valeurs booléennes
En C, il n'y a pas de type spécifique aux
booléens,
 on utilise les int
 false est représenté par 0
 pas de valeur particulière pour true
 test indicateur :
printf("%d\n",sizeof('a'=='b')==sizeof(int));

Tests, attention !!!!

En C, on a le droit d'écrire :
int i;
…
if (i = 0) …
affecte 0 à i et vaut 0, ie. faux
qui est très différent de :
…if (i == 0) …
compare 0 à i et vaut vrai ou faux selon le cas
Définition de constantes

3 moyens en C
– #define
– const
– enum
#define

exemples
– #define N (1 << 4)
– #define DEUX_N ((N)+(N))
Niveau préprocesseur = perte
d'information
 usage très répandu
 attention aux parenthèses

const

Exemples :
– const int N = 1 << 4;
– const int DEUX_N = N+N;

fondalementalement une déclaration de
variable,
 symboles utilisables par le débogueur
 l'affectation est en principe interdite,
 avec certains compilateurs, on a un simple
warning
Enumérations

Pour disposer de constantes symboliques
entières sans #define
 Déclaration
enum nom_d_enum { const1,const2,…};
 Déclaration de variables
enum nom_d_enum nom_de_variable
Fonctions
– Déclaration obligatoire avant toute
utilisation
– Récursivité à la Java
– void = absence d'argument ou de valeur en
retour

Exemples :
#include <stdio.h>
#define DEBUT 0
#define FIN 10
static int carre(int);
static int cube(int n) {return carre(n)*n;}
static int carre(int n) {return n*n;}
int main(void) {
int i;
for (i = DEBUT; i < FIN; i++)
printf("%3d^2=%3d,%3d^3=%3d\n",i,carre(i),cube(i));
return 0;
}
gcc –Wall –g carre.c –o carre
 ./carre
0^2= 0, 0^3= 0
…
5^2= 25, 5^3=125
6^2= 36, 6^3=216
7^2= 49, 7^3=343

Les boucles et tests
Même syntaxe qu'en Java
 Les boucles for, while et do…while

– while (condition) {…}
– do {…} while (condition)
– for (i = 0; i < 10; i++) {…}

Les structures de tests
– if (condition) {} else {}
Tableaux statiques et chaines de
caractères

Déclaration et allocation
type_des_elements nom_tab[nombre];
Initialisation (+declaration et allocation) :
int primes[]={2,3,5,7,11}; // int primes[6]
char s[] = "abc";
/*char s[4]; s[0]='a'; s[1]='b';s[2]='c';s[3]='\0'*/
Les indices de tableau vont de 0 à N-1 mais
IL N'Y A PAS DE VERIFICATION DE DEBORDEMENT
Copie d'un tableau ou d'une
chaine
void copie_tableau(char []t1,char []t2,int n) {
while (n --> 0) t1[n]=t2[n];
}
void copie_chaine(char []s1,char []s2) {
int i=0;
do s2=s1[i] while (s1[i++]!='\0');
}
Fonctions simples sur les chaines

strlen(s) : retourne la longueur de la
chaine s, sans compter le nul.
– Il faut mettre #include <string.h>
strcmp(s1,s2): compare deux chaines et
retourne un nombre <0,=0,>0.
 atoi(s),atol(s),atof(s) : conversion vers
entier, long et double

– Librairie stdlib.h
Structures

Déclaration
struct nom {type1 c1; type2 c2;…};
 Déclaration de variable :
struct nom variable;
 Exemple
–
–
–
–
struct evt {int jour,mois,annee; char evt[50]};
struct evt anniv;
anniv.jour = 14; anniv.annee=1973;
strcpy(anniv.evt,"Anniversaire de Dominique");
Exemple
#include <stdio.h>
#include <math.h>
struct point {float x; float y} p10 = {0,10};
struct rect {struct point p1; struct point p2};
static double sqr(double x) {
return x*x;
}
static double dist(struct point p1,struct point p2){
return sqrt(sqr(p1.x-p2.x)+sqr(p1.y-p2.y));
}
int main(void) {
struct rect r = {{0,5},{25,20}};
return 0;
}
Format d'affichage
printf(format, arg1, arg2,…);
format = chaine de caractères :
 %c : caractère
 %d : entier
 %f : float
 %s : chaine de caractères
 %u : entier non signé
char msg_erreur[20][100];
void erreur(int no) {
printf("erreur numero %d:%s",no,msg_erreur[no]);
}
Divers
Types synonymes :
typedef type nom_nouveau_type;

typedef struct {int re; int im;} Complexe;
Complexe z;
Macros avec arguments
#define mauvais_carre(x) x*x
#define carre(x) ((x)*(x))
#define max(a,b) ((a)<(b)?(b): (a))
max(mauvais_carre(x+1),carre(x+1));
((x+1*x+1)<(((x+1)*(x+1)))?…
Références

La référence :
– Kerningham, Ritchie, The C Programming
Language, second edition, Prentice Hall,
1988
Programmation C
Deuxième Partie
Pointeurs et adresses





Mémoire = Collection de cellules numérotées consécutivement
et contenant chacun un octet
Adresse = Localisation d'une variable dans la mémoire
Pointeur = variable contenant l'adresse d'une variable
Typage d'es pointeurs : T* est le type "adresse d'un objet de
type T".
Objectifs:
– Passage par référence pour modifier un paramètre
– efficacité dans la transmission des paramètres et du résultat
– référencer des objets dont la localisation est inconnue à la
compilation
Référencement et déréférencement
&var est l'adresse de (une référence à) la variable var
*ptr est la variable pointée par le pointeur ptr.
int age;
printf("entrez votre age : ");
scanf("%d",&age);
int i=2,j=5;
static void echanger(int *p1,int *p2) {
int t=*p1; *p1 = *p2; *p2 = t;
}
echanger(&i,&j); /* i==5 et j==2*/
L'adresse NULL
Un pointeur dont la valeur est ((void *) 0), noté par la constante
symbolique NULL ne pointe vers aucune cellule mémoire
DEREFERENCER NULL provoque une erreur !!!!
int main(void) {
int *p;
printf("%p,%p\n",p,NULL);
return 0;
}
0xbffffc58,(nil)
int x = 1, y=2, z[4], *ip;
ip = & x;
/* ip pointe sur x*/
y = *ip;
/* y==1*/
*ip = 0;
/* x==0*/
*ip = *ip+10; /*x==10*/
ip = &z[2];
/*ip pointe sur z[2]*/
ip=&y;
/*ip pointe sur y*/
++*ip;
(*ip)++;
100
/*y==2*/
/*y==3*/
104 108
10
23
110 1
10
2
x
y
112
z[0]
116
z[1]
120
z[2]
z[3]
124
100
116
104
104
ip
Tableaux et pointeurs
Le nom d'un tableau t s'évalue à l'adresse de t[0]
Si un pointeur p pointe sur t[0], p+i pointe par définition
sur t[i]
Ainsi,après
int t[N], *p=t;
on a :
p+i == &t[i],
et même
t+i == &t[i],
/*p==&t[0]*/
*(p+i) == t[i],
*(t+i) == t[i]
Mais on ne peut pas affecter à un nom de tableau
Usage idiotique des pointeurs
static int somme(int *t, int n) {
int i,s;
for(s=0,i=0; i < n; i++)
s = s+t[i];
return s;
}
est équivalent à :
static int somme(int *t, int n) {
int s = 0;
while (n-- > 0) s+=*t++;
return s;
}
/* lire *(t++) */
Déréférencements et type struct
ptr->champ = (*ptr).champ
typedef struct {int re; int im;} Complexe;
Complexe *addition(Complexe *a,Complexe *b) {
b->re=a->re+b->re;
b->im=a->im+b->im;
return b;
} /* permet addition(u,addition(v,w)) */
Liste chaînées
typedef struct cell {
int val;
struct cell *suivant;
} cell,*liste;
void affiche(liste l) {
for (;l;l=l->suivant) printf("%d, ",l->val);
}
Allocation dynamique malloc
void *malloc(size_t)
Permet la réservation de mémoire à l'exécution pour
une variable inconnue à la compilation.
malloc(n) renvoie:
 l'adresse de début d'un bloc de n octets
 NULL si plus aucune mémoire n'est disponible
Schéma typique :
/*typ p[N]; dynamique*/
typ *p=(typ *) malloc(N*sizeof(typ));
if (!p) erreur(…)
Constructeurs
#include <stdio.h>
#include <stdlib.h>
typedef struct cell
{int val; struct cell *next; } *List;
List new_List(int v,List n) {
List tmp = (List) malloc(sizeof(struct cell));
if (!tmp) {
fprintf(stderr,"Plus de memoire ");
exit(1);
}
tmp->val = v;
tmp->next=n;
return tmp; }
Constructeurs suite
int main(int argc, char **argv) {
int i;
List l=NULL,v;
for(i = 1,i < argc; i++)
l = new_List(atoi(argv[i]),l);
for (v=l;v;v=v->next) printf("%d\n",v->val);
return 0;
}
Désallocation void free(void *)
Bloc perdu = plus aucun pointeur ne permet d'y
accéder
Pas de ramasseur de miettes
 Il faut restituer explicitement les blocs qui ne
sont plus utilisés

l'argument doit être une adresse obtenue par
malloc
 on ne doit restituer qu'une seule fois
 il ne faut plus utiliser aucune adresse vers ce
bloc
Tableaux multidimensionnels
Statiquement : type[N][M] réserve N*M
variables, dans l'ordre
T[0][0],…T[0][M-1],T[1][0],…,T[N-1][M-1]
Dynamiquement :
typ **T=(typ **)malloc(N*sizeof(typ*));
if (!T) erreur();
for(i=0; i<N;i++) {
T[i] = (typ *) malloc(M*sizeof(typ));
if (!T[i]) erreur();
}
Taille 7 x 3
0
1
2
0,0
0,1
0,2
1,0
1,1
1,2
2,0
2,1
2,2
3,0
3,1
3,2
3
4
5
6
4,0
4,1
4,2
5,0
6,0
5,1
6,1
5,2
6,2
Tableau version deux
int **t = (int **)malloc(N*sizeof(int*));
if (!t) erreur();
t[0] = (int *) malloc(N*M*sizeof(int));
for (i=1; i < N; i++) {
t[i]=t[i-1]+M;
if (!t[i]) erreur();
}
Fichiers et entrées / sorties
FILE * = type de descripteur de fichier
FILE *stdin,*stdout,*stderr sont ouverts
automatiquement au début du programme
exit (de n'importe ou) et return dans main font
automatiquement fclose de tous les fichiers ouverts.
(ce n'est pas le cas lors d'un plantage en cours du
programme  utiliser fflush)
/* Filtre ^M  ^J */
#include <stdio.h>
int main(void) {
int c;
while ((c=getchar())!=EOF) putchar(c==13?10:c);
Ouverture et fermeture de fichier
FILE *fopen(const char * nom,const char *mode);
 nom=chemin d'accès au fichier
 mode=="r" pour ouvrir en lecture
 mode=="w" pour ouvrir en écriture (création ou remplacement)
 mode=="a" opur ouvrir en écriture à la fin du fichier
 retourne NULL si erreur
int fflush(FILE *);
 force l'écriture des tampons
 renvoie 0 ou EOF en cas d'erreur
int fclose(FILE *);
 ferme le fichier après un fflush éventuel
 renvoie 0 ou EOF en cas d'erreur
Lecture de fichiers
char getc(FILE *);
 lit un caractère et retourne sa valeur
 getchar() est équivalent à getc(stdin)
 renvoie EOF si fin de fichier ou erreur
int fscanf(FILE *,const char *format,…);
 scanf(…) est équivalent à fscanf(stdin,…)
 renvoie EOF si fin de fichier ou erreur
char *fgets(char *line,int max,FILE *);
 lit une ligne dans le buffer line de taille max
 renvoie NULL si fin de fichier ou erreur
int feof(FILE *);
 renvoie 1 si la fin de fichier est atteinte,0 sinon
Ecriture de fichiers
int putc(int c,FILE *f);
 Ecrit le caractère c
 renvoie sa valeur ou EOF si problème
 putchar(c) est équivalent à putc(c,stdout)
int fprintf(FILE *,const char *format,…);
 printf(…) est équivalent à fprintf(stdout,…)
 renvoie le nombre de caractères écrits ou
EOF si erreur
Divers
Arithmétique des pointeurs :
typ t[20],*p1=t+13,*p2=t+17;
int n=p2-p1;
/* n==4*/
Pointeurs sur des fonctions :
double x,y;
double (*f)(double);
f=sin;
y=f(x);
Permet le passage de fonctions en paramètre
static int add(int a, int b) {
return a+b;
}
static int mul(int a, int b) {
return a*b;
}
static int reduce(int *t,int n,int v, int (*f)(int,int)) {
int i;
for (i = 0; i < n; i++) v=f(v,t[i]);
return v;
}
int main(void) {
int t[] = {1,3,7};
printf("%d\n",reduce(t,3,0,add)); /* 11 */
printf("%d\n",reduce(t,3,1,mul)); /* 21 */
return 0;
}